l3cfg: add support for IPv4 link local addresses (ipv4ll) to NML3Cfg

NML3Cfg already handles IPv4 ACD. IPv4LL is just a small additional
layer on top of that, so it makes sense that it also is handled by
NML3Cfg.

Also, the overall goal is that multiple NMDevice and NMVpnConnection
instances can cooperate independently. So if multiple "users" enable
IPv4LL on an interface, then we should only run it once. This is
achieved by NML3IPv4LL's API where users register what they want,
and NML3IPv4LL figures out what that means as a whole.

Also, we thus will no longer need to use sd_ipv4ll/n-ipv4ll, because
we implement it ourself.
This commit is contained in:
Thomas Haller 2020-09-16 19:14:20 +02:00
parent 3caf419df6
commit 17269b0520
No known key found for this signature in database
GPG key ID: 29C2366E4DFC5728
7 changed files with 1427 additions and 6 deletions

View file

@ -2124,6 +2124,8 @@ src_libNetworkManagerBase_la_SOURCES = \
src/nm-netns.h \
src/nm-l3-config-data.c \
src/nm-l3-config-data.h \
src/nm-l3-ipv4ll.c \
src/nm-l3-ipv4ll.h \
src/nm-l3cfg.c \
src/nm-l3cfg.h \
src/nm-ip-config.c \

View file

@ -45,6 +45,7 @@ sources = files(
'nm-dbus-utils.c',
'nm-netns.c',
'nm-l3-config-data.c',
'nm-l3-ipv4ll.c',
'nm-l3cfg.c',
'nm-ip-config.c',
'nm-ip4-config.c',

1001
src/nm-l3-ipv4ll.c Normal file

File diff suppressed because it is too large Load diff

103
src/nm-l3-ipv4ll.h Normal file
View file

@ -0,0 +1,103 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#ifndef __NM_L3_IPV4LL_H__
#define __NM_L3_IPV4LL_H__
#include "nm-l3cfg.h"
/*****************************************************************************/
typedef enum _nm_packed {
NM_L3_IPV4LL_STATE_UNKNOWN,
NM_L3_IPV4LL_STATE_DISABLED,
NM_L3_IPV4LL_STATE_WAIT_FOR_LINK,
NM_L3_IPV4LL_STATE_EXTERNAL,
NM_L3_IPV4LL_STATE_PROBING,
NM_L3_IPV4LL_STATE_READY,
NM_L3_IPV4LL_STATE_DEFENDING,
} NML3IPv4LLState;
const char *nm_l3_ipv4ll_state_to_string(NML3IPv4LLState val, char *buf, gsize len);
/*****************************************************************************/
typedef struct _NML3IPv4LL NML3IPv4LL;
static inline gboolean
NM_IS_L3_IPV4LL(const NML3IPv4LL *self)
{
nm_assert(!self
|| (NM_IS_L3CFG(*((NML3Cfg **) self))
&& (*((int *) (((char *) self) + sizeof(gpointer)))) > 0));
return !!self;
}
NML3IPv4LL *nm_l3_ipv4ll_new(NML3Cfg *self);
NML3IPv4LL *nm_l3_ipv4ll_ref(NML3IPv4LL *self);
void nm_l3_ipv4ll_unref(NML3IPv4LL *self);
NM_AUTO_DEFINE_FCN0(NML3IPv4LL *, _nm_auto_unref_l3ipv4ll, nm_l3_ipv4ll_unref);
#define nm_auto_unref_l3ipv4ll nm_auto(_nm_auto_unref_l3ipv4ll)
/*****************************************************************************/
NML3Cfg *nm_l3_ipv4ll_get_l3cfg(NML3IPv4LL *self);
int nm_l3_ipv4ll_get_ifindex(NML3IPv4LL *self);
NMPlatform *nm_l3_ipv4ll_get_platform(NML3IPv4LL *self);
/*****************************************************************************/
/* By default, NML3IPv4LL is disabled. You also need to register (enable) it.
* The intent of this API is that multiple users can enable/register their own
* settings, and NML3IPv4LL will mediate the different requests.
*
* Also, by setting timeout_msec to zero, NML3IPv4LL is disabled again (zero
* wins over all timeouts). This is useful if you do DHCP and IPv4LL on the
* same interface. You possibly want to disable IPv4LL if you have a valid
* DHCP lease. By registering a timeout_msec to zero, you can disable IPv4LL.
*
* Also, a registration keeps the NML3IPv4LL instance alive (it also takes
* a reference). */
typedef struct _NML3IPv4LLRegistration NML3IPv4LLRegistration;
NML3IPv4LLRegistration *nm_l3_ipv4ll_register_new(NML3IPv4LL *self, guint timeout_msec);
NML3IPv4LLRegistration *nm_l3_ipv4ll_register_update(NML3IPv4LLRegistration *reg,
guint timeout_msec);
NML3IPv4LLRegistration *nm_l3_ipv4ll_register_remove(NML3IPv4LLRegistration *reg);
NM_AUTO_DEFINE_FCN0(NML3IPv4LLRegistration *,
_nm_auto_remove_l3ipv4ll_registration,
nm_l3_ipv4ll_register_remove);
#define nm_auto_remove_l3ipv4ll_registration nm_auto(_nm_auto_remove_l3ipv4ll_registration)
static inline NML3IPv4LL *
nm_l3_ipv4ll_register_get_instance(NML3IPv4LLRegistration *reg)
{
NML3IPv4LL *ipv4ll;
if (!reg)
return NULL;
ipv4ll = *((NML3IPv4LL **) reg);
nm_assert(NM_IS_L3_IPV4LL(ipv4ll));
return ipv4ll;
}
/*****************************************************************************/
NML3IPv4LLState nm_l3_ipv4ll_get_state(NML3IPv4LL *self);
in_addr_t nm_l3_ipv4ll_get_addr(NML3IPv4LL *self);
const NML3ConfigData *nm_l3_ipv4ll_get_l3cd(NML3IPv4LL *self);
/*****************************************************************************/
void nm_l3_ipv4ll_restart(NML3IPv4LL *self);
#endif /* __NM_L3_IPV4LL_H__ */

View file

@ -12,6 +12,7 @@
#include "platform/nmp-object.h"
#include "nm-netns.h"
#include "n-acd/src/n-acd.h"
#include "nm-l3-ipv4ll.h"
/*****************************************************************************/
@ -294,6 +295,7 @@ static NM_UTILS_ENUM2STR_DEFINE(
_l3_config_notify_type_to_string,
NML3ConfigNotifyType,
NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT, "acd-event"),
NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT, "ipv4ll-event"),
NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE, "platform-change"),
NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE, "platform-change-on-idle"),
NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT, "post-commit"),
@ -338,9 +340,11 @@ _l3_config_notify_data_to_string(const NML3ConfigNotifyData *notify_data,
char * sbuf,
gsize sbuf_size)
{
char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
char *s = sbuf;
gsize l = sbuf_size;
char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
char sbuf100[100];
char * s = sbuf;
gsize l = sbuf_size;
in_addr_t addr4;
nm_assert(sbuf);
nm_assert(sbuf_size > 0);
@ -371,6 +375,19 @@ _l3_config_notify_data_to_string(const NML3ConfigNotifyData *notify_data,
", obj-type-flags=0x%x",
notify_data->platform_change_on_idle.obj_type_flags);
break;
case NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT:
nm_assert(NM_IS_L3_IPV4LL(notify_data->ipv4ll_event.ipv4ll));
addr4 = nm_l3_ipv4ll_get_addr(notify_data->ipv4ll_event.ipv4ll);
nm_utils_strbuf_append(
&s,
&l,
", ipv4ll=" NM_HASH_OBFUSCATE_PTR_FMT "%s%s, state=%s",
NM_HASH_OBFUSCATE_PTR(notify_data->ipv4ll_event.ipv4ll),
NM_PRINT_FMT_QUOTED2(addr4 != 0, ", addr=", _nm_utils_inet4_ntop(addr4, sbuf_addr), ""),
nm_l3_ipv4ll_state_to_string(nm_l3_ipv4ll_get_state(notify_data->ipv4ll_event.ipv4ll),
sbuf100,
sizeof(sbuf100)));
break;
default:
break;
}
@ -2651,7 +2668,10 @@ nm_l3cfg_add_config(NML3Cfg * self,
nm_assert(tag);
nm_assert(l3cd);
nm_assert(nm_l3_config_data_get_ifindex(l3cd) == self->priv.ifindex);
nm_assert(acd_timeout_msec < ACD_MAX_TIMEOUT_MSEC);
if (acd_timeout_msec > ACD_MAX_TIMEOUT_MSEC)
acd_timeout_msec = ACD_MAX_TIMEOUT_MSEC;
nm_assert(NM_IN_SET(acd_defend_type,
NM_L3_ACD_DEFEND_TYPE_NEVER,
NM_L3_ACD_DEFEND_TYPE_ONCE,

View file

@ -6,6 +6,8 @@
#include "platform/nmp-object.h"
#include "nm-l3-config-data.h"
#define NM_L3CFG_CONFIG_PRIORITY_IPV4LL 0
#define NM_TYPE_L3CFG (nm_l3cfg_get_type())
#define NM_L3CFG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_L3CFG, NML3Cfg))
#define NM_L3CFG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_L3CFG, NML3CfgClass))
@ -104,9 +106,13 @@ typedef enum {
* notifications without also subscribing directly to the platform. */
NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE,
NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT,
_NM_L3_CONFIG_NOTIFY_TYPE_NUM,
} NML3ConfigNotifyType;
struct _NML3IPv4LL;
typedef struct {
NML3ConfigNotifyType notify_type;
union {
@ -122,6 +128,10 @@ typedef struct {
struct {
guint32 obj_type_flags;
} platform_change_on_idle;
struct {
struct _NML3IPv4LL *ipv4ll;
} ipv4ll_event;
};
} NML3ConfigNotifyData;

View file

@ -3,6 +3,7 @@
#include "nm-default.h"
#include "nm-l3cfg.h"
#include "nm-l3-ipv4ll.h"
#include "nm-netns.h"
#include "platform/nm-platform.h"
@ -404,7 +405,7 @@ test_l3cfg(gconstpointer test_data)
NM_PLATFORM_IP6_ADDRESS_INIT(.address = *nmtst_inet6_from_string("1:2:3:4::45"),
.plen = 64, ));
if (nmtst_get_rand_bool())
if (nmtst_get_rand_one_case_in(2))
nm_l3_config_data_seal(l3cd);
l3cd_a = g_steal_pointer(&l3cd);
break;
@ -493,12 +494,293 @@ test_l3cfg(gconstpointer test_data)
/*****************************************************************************/
#define L3IPV4LL_ACD_TIMEOUT_MSEC 1500u
typedef struct {
const TestFixture1 * f;
NML3CfgCommitTypeHandle *l3cfg_commit_type_1;
guint acd_timeout_msec;
NML3IPv4LL * l3ipv4ll;
bool has_addr4_101;
gint8 ready_seen;
gint8 addr_commit;
in_addr_t addr_commit_addr;
bool add_conflict_checked : 1;
bool add_conflict_done;
} TestL3IPv4LLData;
static gconstpointer
TEST_L3_IPV4LL_TAG(const TestL3IPv4LLData *tdata, guint offset)
{
return (&(((const char *) tdata)[offset]));
}
static void
_test_l3_ipv4ll_maybe_add_addr_4(const TestL3IPv4LLData *tdata,
int ifindex,
guint one_case_in_num,
bool * has_addr,
const char * addr)
{
if (has_addr) {
if (*has_addr || !nmtst_get_rand_one_case_in(one_case_in_num))
return;
*has_addr = TRUE;
}
if (ifindex == 0)
ifindex = tdata->f->ifindex0;
g_assert_cmpint(ifindex, >, 0);
_LOGT("add test address: %s on ifindex=%d", addr, ifindex);
nmtstp_ip4_address_add(tdata->f->platform,
-1,
ifindex,
nmtst_inet4_from_string(addr),
24,
nmtst_inet4_from_string(addr),
100000,
0,
0,
NULL);
}
static void
_test_l3_ipv4ll_signal_notify(NML3Cfg * l3cfg,
const NML3ConfigNotifyData *notify_data,
TestL3IPv4LLData * tdata)
{
char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
g_assert(NM_IS_L3CFG(l3cfg));
g_assert(tdata);
g_assert(notify_data);
g_assert(_NM_INT_NOT_NEGATIVE(notify_data->notify_type));
g_assert(notify_data->notify_type < _NM_L3_CONFIG_NOTIFY_TYPE_NUM);
if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT) {
g_assert(tdata->l3ipv4ll == notify_data->ipv4ll_event.ipv4ll);
g_assert(NM_IN_SET(tdata->ready_seen, 0, 1));
g_assert(NM_IN_SET(tdata->addr_commit, 0, 1));
if (nm_l3_ipv4ll_get_state(tdata->l3ipv4ll) == NM_L3_IPV4LL_STATE_READY) {
g_assert_cmpint(tdata->ready_seen, ==, 0);
g_assert_cmpint(tdata->addr_commit, ==, 0);
tdata->ready_seen++;
if (tdata->f->test_idx == 2 && nmtst_get_rand_bool()) {
tdata->addr_commit++;
tdata->addr_commit_addr = nm_l3_ipv4ll_get_addr(tdata->l3ipv4ll);
g_assert(nm_utils_ip4_address_is_link_local(tdata->addr_commit_addr));
_LOGT("add address %s that passed ACD",
_nm_utils_inet4_ntop(tdata->addr_commit_addr, sbuf_addr));
if (!nm_l3cfg_add_config(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll),
TEST_L3_IPV4LL_TAG(tdata, 1),
nmtst_get_rand_bool(),
nm_l3_ipv4ll_get_l3cd(tdata->l3ipv4ll),
NM_L3CFG_CONFIG_PRIORITY_IPV4LL,
0,
0,
104,
105,
0,
0,
NM_L3_ACD_DEFEND_TYPE_ONCE,
nmtst_get_rand_bool() ? tdata->acd_timeout_msec : 0u,
NM_L3_CONFIG_MERGE_FLAGS_NONE))
g_assert_not_reached();
nm_l3cfg_commit_on_idle_schedule(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll));
tdata->l3cfg_commit_type_1 =
nm_l3cfg_commit_type_register(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll),
NM_L3_CFG_COMMIT_TYPE_UPDATE,
tdata->l3cfg_commit_type_1);
}
} else if (nm_l3_ipv4ll_get_state(tdata->l3ipv4ll) != NM_L3_IPV4LL_STATE_DEFENDING
&& tdata->ready_seen > 0) {
g_assert_cmpint(tdata->ready_seen, ==, 1);
tdata->ready_seen--;
if (tdata->addr_commit > 0) {
g_assert_cmpint(tdata->addr_commit, ==, 1);
tdata->addr_commit--;
g_assert(nm_utils_ip4_address_is_link_local(tdata->addr_commit_addr));
_LOGT("remove address %s that previously passed ACD",
_nm_utils_inet4_ntop(tdata->addr_commit_addr, sbuf_addr));
if (!nm_l3cfg_remove_config_all(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll),
TEST_L3_IPV4LL_TAG(tdata, 1),
FALSE))
g_assert_not_reached();
nm_l3cfg_commit_on_idle_schedule(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll));
nm_l3cfg_commit_type_unregister(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll),
g_steal_pointer(&tdata->l3cfg_commit_type_1));
}
}
return;
}
}
static void
test_l3_ipv4ll(gconstpointer test_data)
{
const int TEST_IDX = GPOINTER_TO_INT(test_data);
nm_auto(_test_fixture_1_teardown) TestFixture1 test_fixture = {};
const TestFixture1 * f;
gs_unref_object NML3Cfg *l3cfg0 = NULL;
TestL3IPv4LLData tdata_stack = {
.f = NULL,
};
TestL3IPv4LLData *const tdata = &tdata_stack;
NMTstpAcdDefender * acd_defender_1 = NULL;
NMTstpAcdDefender * acd_defender_2 = NULL;
nm_auto_unref_l3ipv4ll NML3IPv4LL * l3ipv4ll = NULL;
gint64 start_time_msec;
gint64 total_poll_time_msec;
nm_auto_remove_l3ipv4ll_registration NML3IPv4LLRegistration *l3ipv4ll_reg = NULL;
char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
_LOGD("test start (/l3-ipv4ll/%d)", TEST_IDX);
if (nmtst_test_quick()) {
gs_free char *msg =
g_strdup_printf("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n",
g_get_prgname() ?: "test-l3-ipv4ll");
g_test_skip(msg);
return;
}
f = _test_fixture_1_setup(&test_fixture, TEST_IDX);
tdata->f = f;
if (tdata->f->test_idx == 1)
tdata->acd_timeout_msec = 0;
else
tdata->acd_timeout_msec = L3IPV4LL_ACD_TIMEOUT_MSEC;
_test_l3_ipv4ll_maybe_add_addr_4(tdata, 0, 4, &tdata->has_addr4_101, "192.168.133.101");
l3cfg0 = _netns_access_l3cfg(f->netns, f->ifindex0);
g_signal_connect(l3cfg0,
NM_L3CFG_SIGNAL_NOTIFY,
G_CALLBACK(_test_l3_ipv4ll_signal_notify),
tdata);
l3ipv4ll = nm_l3_ipv4ll_new(l3cfg0);
tdata->l3ipv4ll = l3ipv4ll;
g_assert_cmpint(nm_l3_ipv4ll_get_ifindex(l3ipv4ll), ==, f->ifindex0);
g_assert_cmpint(nm_l3_ipv4ll_get_state(l3ipv4ll), ==, NM_L3_IPV4LL_STATE_DISABLED);
g_assert_cmpint(nm_l3_ipv4ll_get_addr(l3ipv4ll), ==, 0u);
if (tdata->f->test_idx == 1) {
if (nmtst_get_rand_one_case_in(2))
l3ipv4ll_reg = nm_l3_ipv4ll_register_new(l3ipv4ll, tdata->acd_timeout_msec);
} else
l3ipv4ll_reg = nm_l3_ipv4ll_register_new(l3ipv4ll, tdata->acd_timeout_msec);
g_assert(tdata->acd_timeout_msec == 0 || l3ipv4ll_reg);
g_assert(!l3ipv4ll_reg || l3ipv4ll == nm_l3_ipv4ll_register_get_instance(l3ipv4ll_reg));
if (tdata->acd_timeout_msec == 0) {
g_assert_cmpint(nm_l3_ipv4ll_get_state(l3ipv4ll), ==, NM_L3_IPV4LL_STATE_DISABLED);
g_assert_cmpint(nm_l3_ipv4ll_get_addr(l3ipv4ll), ==, 0u);
} else {
g_assert_cmpint(nm_l3_ipv4ll_get_state(l3ipv4ll), ==, NM_L3_IPV4LL_STATE_PROBING);
if (f->test_idx == 1) {
g_assert_cmpint(nm_l3_ipv4ll_get_addr(l3ipv4ll),
==,
nmtst_inet4_from_string("169.254.30.158"));
} else {
g_assert_cmpint(nm_l3_ipv4ll_get_addr(l3ipv4ll),
==,
nmtst_inet4_from_string("169.254.17.45"));
}
g_assert(nm_l3_ipv4ll_get_l3cd(l3ipv4ll));
}
_test_l3_ipv4ll_maybe_add_addr_4(tdata, 0, 4, &tdata->has_addr4_101, "192.168.133.101");
if (tdata->f->test_idx == 2 && nmtst_get_rand_one_case_in(3)) {
in_addr_t a = nm_l3_ipv4ll_get_addr(l3ipv4ll);
g_assert(nm_utils_ip4_address_is_link_local(a));
_test_l3_ipv4ll_maybe_add_addr_4(tdata,
tdata->f->ifindex1,
2,
&tdata->add_conflict_done,
_nm_utils_inet4_ntop(a, sbuf_addr));
g_assert_cmpint(tdata->f->hwaddr1.len, ==, sizeof(NMEtherAddr));
acd_defender_2 =
nmtstp_acd_defender_new(tdata->f->ifindex1, a, &tdata->f->hwaddr1.ether_addr);
}
start_time_msec = nm_utils_get_monotonic_timestamp_msec();
total_poll_time_msec =
(L3IPV4LL_ACD_TIMEOUT_MSEC * 3 / 2) + (nmtst_get_rand_uint32() % L3IPV4LL_ACD_TIMEOUT_MSEC);
_LOGT("poll 1 start (wait %" G_GINT64_FORMAT " msec)", total_poll_time_msec);
while (TRUE) {
gint64 next_timeout_msec;
next_timeout_msec =
start_time_msec + total_poll_time_msec - nm_utils_get_monotonic_timestamp_msec();
if (next_timeout_msec <= 0)
break;
next_timeout_msec = NM_MIN(next_timeout_msec, nmtst_get_rand_uint32() % 1000u);
nmtst_main_context_iterate_until(NULL, next_timeout_msec, FALSE);
_LOGT("poll 1 intermezzo");
_test_l3_ipv4ll_maybe_add_addr_4(tdata,
0,
1 + total_poll_time_msec / 1000,
&tdata->has_addr4_101,
"192.168.133.101");
if (tdata->addr_commit == 1 && !tdata->add_conflict_checked) {
tdata->add_conflict_checked = TRUE;
_test_l3_ipv4ll_maybe_add_addr_4(
tdata,
tdata->f->ifindex1,
2,
&tdata->add_conflict_done,
_nm_utils_inet4_ntop(tdata->addr_commit_addr, sbuf_addr));
if (tdata->add_conflict_done)
total_poll_time_msec += L3IPV4LL_ACD_TIMEOUT_MSEC / 2;
g_assert_cmpint(tdata->f->hwaddr1.len, ==, sizeof(NMEtherAddr));
acd_defender_2 = nmtstp_acd_defender_new(tdata->f->ifindex1,
tdata->addr_commit_addr,
&tdata->f->hwaddr1.ether_addr);
}
}
_LOGT("poll 1 end");
if (tdata->addr_commit || nmtst_get_rand_bool()) {
nm_l3cfg_remove_config_all(nm_l3_ipv4ll_get_l3cfg(l3ipv4ll),
TEST_L3_IPV4LL_TAG(tdata, 1),
FALSE);
}
nmtstp_acd_defender_destory(g_steal_pointer(&acd_defender_1));
nmtstp_acd_defender_destory(g_steal_pointer(&acd_defender_2));
nm_l3cfg_commit_type_unregister(l3cfg0, g_steal_pointer(&tdata->l3cfg_commit_type_1));
g_signal_handlers_disconnect_by_func(l3cfg0, G_CALLBACK(_test_l3_ipv4ll_signal_notify), tdata);
}
/*****************************************************************************/
NMTstpSetupFunc const _nmtstp_setup_platform_func = nm_linux_platform_setup;
void
_nmtstp_init_tests(int *argc, char ***argv)
{
nmtst_init_with_logging(argc, argv, NULL, "ALL");
nmtst_init_with_logging(argc, argv, "ERR", "ALL");
}
void
@ -508,4 +790,6 @@ _nmtstp_setup_tests(void)
g_test_add_data_func("/l3cfg/2", GINT_TO_POINTER(2), test_l3cfg);
g_test_add_data_func("/l3cfg/3", GINT_TO_POINTER(3), test_l3cfg);
g_test_add_data_func("/l3cfg/4", GINT_TO_POINTER(4), test_l3cfg);
g_test_add_data_func("/l3-ipv4ll/1", GINT_TO_POINTER(1), test_l3_ipv4ll);
g_test_add_data_func("/l3-ipv4ll/2", GINT_TO_POINTER(2), test_l3_ipv4ll);
}