As under vanilla Linux the kernel can't handle parallel dump queries and returns EBUSY, it makes not much sense to use them. Disable parallel queries by default to basically restore original behavior, improving performance.
303 lines
6.5 KiB
C
303 lines
6.5 KiB
C
/*
|
|
* Copyright (C) 2014 Martin Willi
|
|
* Copyright (C) 2014 revosec AG
|
|
*
|
|
* 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. See <http://www.fsf.org/copyleft/gpl.txt>.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*/
|
|
|
|
#include <test_suite.h>
|
|
|
|
#include <threading/thread.h>
|
|
|
|
#include "../kernel_netlink_shared.h"
|
|
|
|
/**
|
|
* Netlink message drop configuration
|
|
*/
|
|
static int drop_interval = 0;
|
|
|
|
/**
|
|
* Netlink message drop hook
|
|
*/
|
|
bool netlink_msg_loss(struct nlmsghdr *hdr)
|
|
{
|
|
static refcount_t i;
|
|
|
|
if (drop_interval)
|
|
{
|
|
return ref_get(&i) % drop_interval == drop_interval - 1;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
START_TEST(test_echo)
|
|
{
|
|
netlink_socket_t *s;
|
|
struct nlmsghdr *out;
|
|
struct rtmsg *msg;
|
|
char dst[] = {
|
|
127,0,0,1
|
|
};
|
|
size_t len;
|
|
netlink_buf_t request = {
|
|
.hdr = {
|
|
.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)),
|
|
.nlmsg_flags = NLM_F_REQUEST,
|
|
.nlmsg_type = RTM_GETROUTE,
|
|
},
|
|
};
|
|
|
|
msg = NLMSG_DATA(&request.hdr);
|
|
msg->rtm_family = AF_INET;
|
|
netlink_add_attribute(&request.hdr, RTA_DST,
|
|
chunk_from_thing(dst), sizeof(request));
|
|
|
|
s = netlink_socket_create(NETLINK_ROUTE, NULL, _i != 0);
|
|
|
|
ck_assert(s->send(s, &request.hdr, &out, &len) == SUCCESS);
|
|
ck_assert_int_eq(out->nlmsg_type, RTM_NEWROUTE);
|
|
free(out);
|
|
s->destroy(s);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_echo_dump)
|
|
{
|
|
netlink_socket_t *s;
|
|
struct nlmsghdr *out, *current;
|
|
struct rtgenmsg *msg;
|
|
size_t len;
|
|
netlink_buf_t request = {
|
|
.hdr = {
|
|
.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)),
|
|
.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH | NLM_F_ROOT,
|
|
.nlmsg_type = RTM_GETLINK,
|
|
},
|
|
};
|
|
|
|
s = netlink_socket_create(NETLINK_ROUTE, NULL, _i != 0);
|
|
msg = NLMSG_DATA(&request.hdr);
|
|
msg->rtgen_family = AF_UNSPEC;
|
|
|
|
ck_assert(s->send(s, &request.hdr, &out, &len) == SUCCESS);
|
|
current = out;
|
|
while (TRUE)
|
|
{
|
|
ck_assert(NLMSG_OK(current, len));
|
|
if (current->nlmsg_type == NLMSG_DONE)
|
|
{
|
|
break;
|
|
}
|
|
ck_assert_int_eq(current->nlmsg_type, RTM_NEWLINK);
|
|
current = NLMSG_NEXT(current, len);
|
|
}
|
|
free(out);
|
|
s->destroy(s);
|
|
}
|
|
END_TEST
|
|
|
|
CALLBACK(stress, void*,
|
|
netlink_socket_t *s)
|
|
{
|
|
struct nlmsghdr *out;
|
|
struct rtmsg *msg;
|
|
char dst[] = {
|
|
127,0,0,1
|
|
};
|
|
size_t len;
|
|
int i;
|
|
netlink_buf_t request = {
|
|
.hdr = {
|
|
.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)),
|
|
.nlmsg_flags = NLM_F_REQUEST,
|
|
.nlmsg_type = RTM_GETROUTE,
|
|
},
|
|
};
|
|
|
|
for (i = 0; i < 10; i++)
|
|
{
|
|
msg = NLMSG_DATA(&request.hdr);
|
|
msg->rtm_family = AF_INET;
|
|
netlink_add_attribute(&request.hdr, RTA_DST,
|
|
chunk_from_thing(dst), sizeof(request));
|
|
|
|
ck_assert(s->send(s, &request.hdr, &out, &len) == SUCCESS);
|
|
ck_assert_int_eq(out->nlmsg_type, RTM_NEWROUTE);
|
|
free(out);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
CALLBACK(stress_dump, void*,
|
|
netlink_socket_t *s)
|
|
{
|
|
struct nlmsghdr *out, *current;
|
|
struct rtgenmsg *msg;
|
|
size_t len;
|
|
int i;
|
|
netlink_buf_t request = {
|
|
.hdr = {
|
|
.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)),
|
|
.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH | NLM_F_ROOT,
|
|
.nlmsg_type = RTM_GETLINK,
|
|
},
|
|
};
|
|
|
|
msg = NLMSG_DATA(&request.hdr);
|
|
msg->rtgen_family = AF_UNSPEC;
|
|
|
|
for (i = 0; i < 10; i++)
|
|
{
|
|
ck_assert(s->send(s, &request.hdr, &out, &len) == SUCCESS);
|
|
current = out;
|
|
while (TRUE)
|
|
{
|
|
ck_assert(NLMSG_OK(current, len));
|
|
if (current->nlmsg_type == NLMSG_DONE)
|
|
{
|
|
break;
|
|
}
|
|
ck_assert_int_eq(current->nlmsg_type, RTM_NEWLINK);
|
|
current = NLMSG_NEXT(current, len);
|
|
}
|
|
free(out);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
START_TEST(test_stress)
|
|
{
|
|
thread_t *threads[10];
|
|
netlink_socket_t *s;
|
|
int i;
|
|
|
|
s = netlink_socket_create(NETLINK_ROUTE, NULL, _i != 0);
|
|
for (i = 0; i < countof(threads); i++)
|
|
{
|
|
threads[i] = thread_create(stress, s);
|
|
}
|
|
for (i = 0; i < countof(threads); i++)
|
|
{
|
|
threads[i]->join(threads[i]);
|
|
}
|
|
s->destroy(s);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_stress_dump)
|
|
{
|
|
thread_t *threads[10];
|
|
netlink_socket_t *s;
|
|
int i;
|
|
|
|
s = netlink_socket_create(NETLINK_ROUTE, NULL, _i != 0);
|
|
for (i = 0; i < countof(threads); i++)
|
|
{
|
|
threads[i] = thread_create(stress_dump, s);
|
|
}
|
|
for (i = 0; i < countof(threads); i++)
|
|
{
|
|
threads[i]->join(threads[i]);
|
|
}
|
|
s->destroy(s);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_retransmit_success)
|
|
{
|
|
netlink_socket_t *s;
|
|
struct nlmsghdr *out;
|
|
struct rtgenmsg *msg;
|
|
size_t len;
|
|
netlink_buf_t request = {
|
|
.hdr = {
|
|
.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)),
|
|
.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH | NLM_F_ROOT,
|
|
.nlmsg_type = RTM_GETLINK,
|
|
},
|
|
};
|
|
|
|
drop_interval = 2;
|
|
|
|
lib->settings->set_int(lib->settings,
|
|
"%s.plugins.kernel-netlink.timeout", 100, lib->ns);
|
|
lib->settings->set_int(lib->settings,
|
|
"%s.plugins.kernel-netlink.retries", 1, lib->ns);
|
|
|
|
s = netlink_socket_create(NETLINK_ROUTE, NULL, _i != 0);
|
|
msg = NLMSG_DATA(&request.hdr);
|
|
msg->rtgen_family = AF_UNSPEC;
|
|
|
|
ck_assert(s->send(s, &request.hdr, &out, &len) == SUCCESS);
|
|
free(out);
|
|
s->destroy(s);
|
|
|
|
drop_interval = 0;
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_retransmit_fail)
|
|
{
|
|
netlink_socket_t *s;
|
|
struct nlmsghdr *out;
|
|
struct rtgenmsg *msg;
|
|
size_t len;
|
|
netlink_buf_t request = {
|
|
.hdr = {
|
|
.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)),
|
|
.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH | NLM_F_ROOT,
|
|
.nlmsg_type = RTM_GETLINK,
|
|
},
|
|
};
|
|
|
|
drop_interval = 1;
|
|
|
|
lib->settings->set_int(lib->settings,
|
|
"%s.plugins.kernel-netlink.timeout", 50, lib->ns);
|
|
lib->settings->set_int(lib->settings,
|
|
"%s.plugins.kernel-netlink.retries", 3, lib->ns);
|
|
|
|
s = netlink_socket_create(NETLINK_ROUTE, NULL, _i != 0);
|
|
msg = NLMSG_DATA(&request.hdr);
|
|
msg->rtgen_family = AF_UNSPEC;
|
|
|
|
ck_assert(s->send(s, &request.hdr, &out, &len) == OUT_OF_RES);
|
|
s->destroy(s);
|
|
|
|
drop_interval = 0;
|
|
}
|
|
END_TEST
|
|
|
|
Suite *socket_suite_create()
|
|
{
|
|
Suite *s;
|
|
TCase *tc;
|
|
|
|
s = suite_create("netlink socket");
|
|
|
|
tc = tcase_create("echo");
|
|
tcase_add_loop_test(tc, test_echo, 0, 2);
|
|
tcase_add_loop_test(tc, test_echo_dump, 0, 2);
|
|
suite_add_tcase(s, tc);
|
|
|
|
tc = tcase_create("stress");
|
|
tcase_add_loop_test(tc, test_stress, 0, 2);
|
|
tcase_add_loop_test(tc, test_stress_dump, 0, 2);
|
|
suite_add_tcase(s, tc);
|
|
|
|
tc = tcase_create("retransmit");
|
|
tcase_add_loop_test(tc, test_retransmit_success, 0, 2);
|
|
tcase_add_loop_test(tc, test_retransmit_fail, 0, 2);
|
|
suite_add_tcase(s, tc);
|
|
|
|
return s;
|
|
}
|