networkd: Keep track of static neighbors

We need to keep track of the static neighbors that are configured on the
interface so that we can delete stale entries that were removed.
This commit is contained in:
William A. Kennington III 2019-04-19 00:53:34 -07:00 committed by Yu Watanabe
parent 4c94a4c2d6
commit d1bdafd214
11 changed files with 541 additions and 10 deletions

View File

@ -372,7 +372,7 @@ int sd_rtnl_message_new_neigh(sd_netlink *rtnl, sd_netlink_message **ret, uint16
int r;
assert_return(rtnl_message_type_is_neigh(nlmsg_type), -EINVAL);
assert_return(IN_SET(ndm_family, AF_INET, AF_INET6, PF_BRIDGE), -EINVAL);
assert_return(IN_SET(ndm_family, AF_UNSPEC, AF_INET, AF_INET6, PF_BRIDGE), -EINVAL);
assert_return(ret, -EINVAL);
r = message_new(rtnl, ret, nlmsg_type);

View File

@ -868,6 +868,13 @@ int sd_netlink_add_match(
if (r < 0)
return r;
break;
case RTM_NEWNEIGH:
case RTM_DELNEIGH:
r = socket_broadcast_group_ref(rtnl, RTNLGRP_NEIGH);
if (r < 0)
return r;
break;
case RTM_NEWROUTE:
case RTM_DELROUTE:

View File

@ -699,6 +699,9 @@ static Link *link_free(Link *link) {
link->routes = set_free_with_destructor(link->routes, route_free);
link->routes_foreign = set_free_with_destructor(link->routes_foreign, route_free);
link->neighbors = set_free_with_destructor(link->neighbors, neighbor_free);
link->neighbors_foreign = set_free_with_destructor(link->neighbors_foreign, neighbor_free);
link->addresses = set_free_with_destructor(link->addresses, address_free);
link->addresses_foreign = set_free_with_destructor(link->addresses_foreign, address_free);
@ -2347,6 +2350,22 @@ static bool link_is_static_address_configured(Link *link, Address *address) {
return false;
}
static bool link_is_neighbor_configured(Link *link, Neighbor *neighbor) {
Neighbor *net_neighbor;
assert(link);
assert(neighbor);
if (!link->network)
return false;
LIST_FOREACH(neighbors, net_neighbor, link->network->neighbors)
if (neighbor_equal(net_neighbor, neighbor))
return true;
return false;
}
static bool link_is_static_route_configured(Link *link, Route *route) {
Route *net_route;
@ -2392,6 +2411,7 @@ static bool link_address_is_dynamic(Link *link, Address *address) {
static int link_drop_foreign_config(Link *link) {
Address *address;
Neighbor *neighbor;
Route *route;
Iterator i;
int r;
@ -2418,6 +2438,18 @@ static int link_drop_foreign_config(Link *link) {
}
}
SET_FOREACH(neighbor, link->neighbors_foreign, i) {
if (link_is_neighbor_configured(link, neighbor)) {
r = neighbor_add(link, neighbor->family, &neighbor->in_addr, &neighbor->lladdr, neighbor->lladdr_size, NULL);
if (r < 0)
return r;
} else {
r = neighbor_remove(neighbor, link, NULL);
if (r < 0)
return r;
}
}
SET_FOREACH(route, link->routes_foreign, i) {
/* do not touch routes managed by the kernel */
if (route->protocol == RTPROT_KERNEL)
@ -2456,6 +2488,7 @@ static int link_drop_foreign_config(Link *link) {
static int link_drop_config(Link *link) {
Address *address, *pool_address;
Neighbor *neighbor;
Route *route;
Iterator i;
int r;
@ -2479,6 +2512,12 @@ static int link_drop_config(Link *link) {
}
}
SET_FOREACH(neighbor, link->neighbors, i) {
r = neighbor_remove(neighbor, link, NULL);
if (r < 0)
return r;
}
SET_FOREACH(route, link->routes, i) {
/* do not touch routes managed by the kernel */
if (route->protocol == RTPROT_KERNEL)

View File

@ -75,6 +75,8 @@ typedef struct Link {
Set *addresses;
Set *addresses_foreign;
Set *neighbors;
Set *neighbors_foreign;
Set *routes;
Set *routes_foreign;

View File

@ -488,6 +488,184 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, vo
return 1;
}
static int manager_rtnl_process_neighbor_lladdr(sd_netlink_message *message, union lladdr_union *lladdr, size_t *size, char **str) {
int r;
assert(message);
assert(lladdr);
assert(size);
assert(str);
*str = NULL;
r = sd_netlink_message_read(message, NDA_LLADDR, sizeof(lladdr->ip.in6), &lladdr->ip.in6);
if (r >= 0) {
*size = sizeof(lladdr->ip.in6);
if (in_addr_to_string(AF_INET6, &lladdr->ip, str) < 0)
log_warning_errno(r, "Could not print lower address: %m");
return r;
}
r = sd_netlink_message_read(message, NDA_LLADDR, sizeof(lladdr->mac), &lladdr->mac);
if (r >= 0) {
*size = sizeof(lladdr->mac);
*str = new(char, ETHER_ADDR_TO_STRING_MAX);
if (!*str) {
log_oom();
return r;
}
ether_addr_to_string(&lladdr->mac, *str);
return r;
}
r = sd_netlink_message_read(message, NDA_LLADDR, sizeof(lladdr->ip.in), &lladdr->ip.in);
if (r >= 0) {
*size = sizeof(lladdr->ip.in);
if (in_addr_to_string(AF_INET, &lladdr->ip, str) < 0)
log_warning_errno(r, "Could not print lower address: %m");
return r;
}
return r;
}
int manager_rtnl_process_neighbor(sd_netlink *rtnl, sd_netlink_message *message, void *userdata) {
Manager *m = userdata;
Link *link = NULL;
Neighbor *neighbor = NULL;
int ifindex, family, r;
uint16_t type, state;
union in_addr_union in_addr = IN_ADDR_NULL;
_cleanup_free_ char *addr_str = NULL;
union lladdr_union lladdr;
size_t lladdr_size = 0;
_cleanup_free_ char *lladdr_str = NULL;
assert(rtnl);
assert(message);
assert(m);
if (sd_netlink_message_is_error(message)) {
r = sd_netlink_message_get_errno(message);
if (r < 0)
log_warning_errno(r, "rtnl: failed to receive neighbor message, ignoring: %m");
return 0;
}
r = sd_netlink_message_get_type(message, &type);
if (r < 0) {
log_warning_errno(r, "rtnl: could not get message type, ignoring: %m");
return 0;
} else if (!IN_SET(type, RTM_NEWNEIGH, RTM_DELNEIGH)) {
log_warning("rtnl: received unexpected message type %u when processing neighbor, ignoring.", type);
return 0;
}
r = sd_rtnl_message_neigh_get_state(message, &state);
if (r < 0) {
log_link_warning_errno(link, r, "rtnl: received neighbor message with invalid state, ignoring: %m");
return 0;
} else if (!FLAGS_SET(state, NUD_PERMANENT)) {
log_debug("rtnl: received non-static neighbor, ignoring.");
return 0;
}
r = sd_rtnl_message_neigh_get_ifindex(message, &ifindex);
if (r < 0) {
log_warning_errno(r, "rtnl: could not get ifindex from message, ignoring: %m");
return 0;
} else if (ifindex <= 0) {
log_warning("rtnl: received neighbor message with invalid ifindex %d, ignoring.", ifindex);
return 0;
}
r = link_get(m, ifindex, &link);
if (r < 0 || !link) {
/* when enumerating we might be out of sync, but we will get the neighbor again, so just
* ignore it */
if (!m->enumerating)
log_warning("rtnl: received neighbor for link '%d' we don't know about, ignoring.", ifindex);
return 0;
}
r = sd_rtnl_message_neigh_get_family(message, &family);
if (r < 0 || !IN_SET(family, AF_INET, AF_INET6)) {
log_link_warning(link, "rtnl: received neighbor message with invalid family, ignoring.");
return 0;
}
switch (family) {
case AF_INET:
r = sd_netlink_message_read_in_addr(message, NDA_DST, &in_addr.in);
if (r < 0) {
log_link_warning_errno(link, r, "rtnl: received neighbor message without valid address, ignoring: %m");
return 0;
}
break;
case AF_INET6:
r = sd_netlink_message_read_in6_addr(message, NDA_DST, &in_addr.in6);
if (r < 0) {
log_link_warning_errno(link, r, "rtnl: received neighbor message without valid address, ignoring: %m");
return 0;
}
break;
default:
assert_not_reached("Received unsupported address family");
}
if (in_addr_to_string(family, &in_addr, &addr_str) < 0)
log_link_warning_errno(link, r, "Could not print address: %m");
r = manager_rtnl_process_neighbor_lladdr(message, &lladdr, &lladdr_size, &lladdr_str);
if (r < 0) {
log_link_warning_errno(link, r, "rtnl: received neighbor message with invalid lladdr, ignoring: %m");
return 0;
}
(void) neighbor_get(link, family, &in_addr, &lladdr, lladdr_size, &neighbor);
switch (type) {
case RTM_NEWNEIGH:
if (neighbor)
log_link_debug(link, "Remembering neighbor: %s->%s",
strnull(addr_str), strnull(lladdr_str));
else {
/* A neighbor appeared that we did not request */
r = neighbor_add_foreign(link, family, &in_addr, &lladdr, lladdr_size, &neighbor);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to remember foreign neighbor %s->%s, ignoring: %m",
strnull(addr_str), strnull(lladdr_str));
return 0;
} else
log_link_debug(link, "Remembering foreign neighbor: %s->%s",
strnull(addr_str), strnull(lladdr_str));
}
break;
case RTM_DELNEIGH:
if (neighbor) {
log_link_debug(link, "Forgetting neighbor: %s->%s",
strnull(addr_str), strnull(lladdr_str));
(void) neighbor_free(neighbor);
} else
log_link_info(link, "Kernel removed a neighbor we don't remember: %s->%s, ignoring.",
strnull(addr_str), strnull(lladdr_str));
break;
default:
assert_not_reached("Received invalid RTNL message type");
}
return 1;
}
int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, void *userdata) {
_cleanup_free_ char *buf = NULL;
Manager *m = userdata;
@ -1014,6 +1192,14 @@ static int manager_connect_rtnl(Manager *m) {
if (r < 0)
return r;
r = sd_netlink_add_match(m->rtnl, NULL, RTM_NEWNEIGH, &manager_rtnl_process_neighbor, NULL, m, "network-rtnl_process_neighbor");
if (r < 0)
return r;
r = sd_netlink_add_match(m->rtnl, NULL, RTM_DELNEIGH, &manager_rtnl_process_neighbor, NULL, m, "network-rtnl_process_neighbor");
if (r < 0)
return r;
r = sd_netlink_add_match(m->rtnl, NULL, RTM_NEWROUTE, &manager_rtnl_process_route, NULL, m, "network-rtnl_process_route");
if (r < 0)
return r;
@ -1555,6 +1741,41 @@ int manager_rtnl_enumerate_addresses(Manager *m) {
return r;
}
int manager_rtnl_enumerate_neighbors(Manager *m) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
sd_netlink_message *neigh;
int r;
assert(m);
assert(m->rtnl);
r = sd_rtnl_message_new_neigh(m->rtnl, &req, RTM_GETNEIGH, 0, AF_UNSPEC);
if (r < 0)
return r;
r = sd_netlink_message_request_dump(req, true);
if (r < 0)
return r;
r = sd_netlink_call(m->rtnl, req, 0, &reply);
if (r < 0)
return r;
for (neigh = reply; neigh; neigh = sd_netlink_message_next(neigh)) {
int k;
m->enumerating = true;
k = manager_rtnl_process_neighbor(m->rtnl, neigh, m);
if (k < 0)
r = k;
m->enumerating = false;
}
return r;
}
int manager_rtnl_enumerate_routes(Manager *m) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
sd_netlink_message *route;

View File

@ -77,10 +77,12 @@ bool manager_should_reload(Manager *m);
int manager_rtnl_enumerate_links(Manager *m);
int manager_rtnl_enumerate_addresses(Manager *m);
int manager_rtnl_enumerate_neighbors(Manager *m);
int manager_rtnl_enumerate_routes(Manager *m);
int manager_rtnl_enumerate_rules(Manager *m);
int manager_rtnl_process_address(sd_netlink *nl, sd_netlink_message *message, void *userdata);
int manager_rtnl_process_neighbor(sd_netlink *nl, sd_netlink_message *message, void *userdata);
int manager_rtnl_process_route(sd_netlink *nl, sd_netlink_message *message, void *userdata);
int manager_rtnl_process_rule(sd_netlink *nl, sd_netlink_message *message, void *userdata);

View File

@ -11,6 +11,7 @@
#include "networkd-link.h"
#include "networkd-manager.h"
#include "networkd-neighbor.h"
#include "set.h"
void neighbor_free(Neighbor *neighbor) {
if (!neighbor)
@ -21,10 +22,15 @@ void neighbor_free(Neighbor *neighbor) {
assert(neighbor->network->n_neighbors > 0);
neighbor->network->n_neighbors--;
if (neighbor->section) {
if (neighbor->section)
hashmap_remove(neighbor->network->neighbors_by_section, neighbor->section);
network_config_section_free(neighbor->section);
}
}
network_config_section_free(neighbor->section);
if (neighbor->link) {
set_remove(neighbor->link->neighbors, neighbor);
set_remove(neighbor->link->neighbors_foreign, neighbor);
}
free(neighbor);
@ -81,9 +87,10 @@ static int neighbor_new_static(Network *network, const char *filename, unsigned
return 0;
}
static int neighbor_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
static int neighbor_configure_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
int r;
assert(m);
assert(link);
assert(link->neighbor_messages > 0);
@ -137,7 +144,7 @@ int neighbor_configure(Neighbor *neighbor, Link *link, link_netlink_message_hand
if (r < 0)
return log_error_errno(r, "Could not append NDA_DST attribute: %m");
r = netlink_call_async(link->manager->rtnl, NULL, req, callback ?: neighbor_handler,
r = netlink_call_async(link->manager->rtnl, NULL, req, callback ?: neighbor_configure_handler,
link_netlink_destroy_callback, link);
if (r < 0)
return log_error_errno(r, "Could not send rtnetlink message: %m");
@ -145,9 +152,217 @@ int neighbor_configure(Neighbor *neighbor, Link *link, link_netlink_message_hand
link->neighbor_messages++;
link_ref(link);
r = neighbor_add(link, neighbor->family, &neighbor->in_addr, &neighbor->lladdr, neighbor->lladdr_size, NULL);
if (r < 0)
return log_link_error_errno(link, r, "Could not add neighbor: %m");
return 0;
}
static int neighbor_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
int r;
assert(m);
assert(link);
if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
return 1;
r = sd_netlink_message_get_errno(m);
if (r < 0 && r != -ESRCH)
/* Neighbor may not exist because it already got deleted, ignore that. */
log_link_warning_errno(link, r, "Could not remove neighbor: %m");
return 1;
}
int neighbor_remove(Neighbor *neighbor, Link *link, link_netlink_message_handler_t callback) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
int r;
assert(neighbor);
assert(link);
assert(link->ifindex > 0);
assert(link->manager);
assert(link->manager->rtnl);
r = sd_rtnl_message_new_neigh(link->manager->rtnl, &req, RTM_DELNEIGH,
link->ifindex, neighbor->family);
if (r < 0)
return log_error_errno(r, "Could not allocate RTM_DELNEIGH message: %m");
r = netlink_message_append_in_addr_union(req, NDA_DST, neighbor->family, &neighbor->in_addr);
if (r < 0)
return log_error_errno(r, "Could not append NDA_DST attribute: %m");
r = netlink_call_async(link->manager->rtnl, NULL, req, callback ?: neighbor_remove_handler,
link_netlink_destroy_callback, link);
if (r < 0)
return log_error_errno(r, "Could not send rtnetlink message: %m");
link_ref(link);
return 0;
}
static void neighbor_hash_func(const Neighbor *neighbor, struct siphash *state) {
assert(neighbor);
siphash24_compress(&neighbor->family, sizeof(neighbor->family), state);
switch (neighbor->family) {
case AF_INET:
case AF_INET6:
/* Equality of neighbors are given by the pair (addr,lladdr) */
siphash24_compress(&neighbor->in_addr, FAMILY_ADDRESS_SIZE(neighbor->family), state);
siphash24_compress(&neighbor->lladdr, neighbor->lladdr_size, state);
break;
default:
/* treat any other address family as AF_UNSPEC */
break;
}
}
static int neighbor_compare_func(const Neighbor *a, const Neighbor *b) {
int r;
r = CMP(a->family, b->family);
if (r != 0)
return r;
r = CMP(a->lladdr_size, b->lladdr_size);
if (r != 0)
return r;
switch (a->family) {
case AF_INET:
case AF_INET6:
r = memcmp(&a->in_addr, &b->in_addr, FAMILY_ADDRESS_SIZE(a->family));
if (r != 0)
return r;
}
return memcmp(&a->lladdr, &b->lladdr, a->lladdr_size);
}
DEFINE_PRIVATE_HASH_OPS(neighbor_hash_ops, Neighbor, neighbor_hash_func, neighbor_compare_func);
int neighbor_get(Link *link, int family, const union in_addr_union *addr, const union lladdr_union *lladdr, size_t lladdr_size, Neighbor **ret) {
Neighbor neighbor, *existing;
assert(link);
assert(addr);
assert(lladdr);
neighbor = (Neighbor) {
.family = family,
.in_addr = *addr,
.lladdr = *lladdr,
.lladdr_size = lladdr_size,
};
existing = set_get(link->neighbors, &neighbor);
if (existing) {
if (ret)
*ret = existing;
return 1;
}
existing = set_get(link->neighbors_foreign, &neighbor);
if (existing) {
if (ret)
*ret = existing;
return 0;
}
return -ENOENT;
}
static int neighbor_add_internal(Link *link, Set **neighbors, int family, const union in_addr_union *addr, const union lladdr_union *lladdr, size_t lladdr_size, Neighbor **ret) {
_cleanup_(neighbor_freep) Neighbor *neighbor = NULL;
int r;
assert(link);
assert(neighbors);
assert(addr);
assert(lladdr);
neighbor = new(Neighbor, 1);
if (!neighbor)
return -ENOMEM;
*neighbor = (Neighbor) {
.family = family,
.in_addr = *addr,
.lladdr = *lladdr,
.lladdr_size = lladdr_size,
};
r = set_ensure_allocated(neighbors, &neighbor_hash_ops);
if (r < 0)
return r;
r = set_put(*neighbors, neighbor);
if (r < 0)
return r;
if (r == 0)
return -EEXIST;
neighbor->link = link;
if (ret)
*ret = neighbor;
neighbor = NULL;
return 0;
}
int neighbor_add(Link *link, int family, const union in_addr_union *addr, const union lladdr_union *lladdr, size_t lladdr_size, Neighbor **ret) {
Neighbor *neighbor;
int r;
r = neighbor_get(link, family, addr, lladdr, lladdr_size, &neighbor);
if (r == -ENOENT) {
/* Neighbor doesn't exist, make a new one */
r = neighbor_add_internal(link, &link->neighbors, family, addr, lladdr, lladdr_size, &neighbor);
if (r < 0)
return r;
} else if (r == 0) {
/* Neighbor is foreign, claim it as recognized */
r = set_ensure_allocated(&link->neighbors, &neighbor_hash_ops);
if (r < 0)
return r;
r = set_put(link->neighbors, neighbor);
if (r < 0)
return r;
set_remove(link->neighbors_foreign, neighbor);
} else if (r == 1) {
/* Neighbor already exists */
} else
return r;
if (ret)
*ret = neighbor;
return 0;
}
int neighbor_add_foreign(Link *link, int family, const union in_addr_union *addr, const union lladdr_union *lladdr, size_t lladdr_size, Neighbor **ret) {
return neighbor_add_internal(link, &link->neighbors_foreign, family, addr, lladdr, lladdr_size, ret);
}
bool neighbor_equal(const Neighbor *n1, const Neighbor *n2) {
if (n1 == n2)
return true;
if (!n1 || !n2)
return false;
return neighbor_compare_func(n1, n2) == 0;
}
int neighbor_section_verify(Neighbor *neighbor) {
if (section_is_invalid(neighbor->section))
return -EINVAL;

View File

@ -15,6 +15,11 @@ typedef struct Neighbor Neighbor;
#include "networkd-network.h"
#include "networkd-util.h"
union lladdr_union {
struct ether_addr mac;
union in_addr_union ip;
};
struct Neighbor {
Network *network;
Link *link;
@ -22,10 +27,7 @@ struct Neighbor {
int family;
union in_addr_union in_addr;
union {
struct ether_addr mac;
union in_addr_union ip;
} lladdr;
union lladdr_union lladdr;
size_t lladdr_size;
LIST_FIELDS(Neighbor, neighbors);
@ -36,6 +38,12 @@ void neighbor_free(Neighbor *neighbor);
DEFINE_NETWORK_SECTION_FUNCTIONS(Neighbor, neighbor_free);
int neighbor_configure(Neighbor *neighbor, Link *link, link_netlink_message_handler_t callback);
int neighbor_remove(Neighbor *neighbor, Link *link, link_netlink_message_handler_t callback);
int neighbor_get(Link *link, int family, const union in_addr_union *addr, const union lladdr_union *lladdr, size_t lladdr_size, Neighbor **ret);
int neighbor_add(Link *link, int family, const union in_addr_union *addr, const union lladdr_union *lladdr, size_t lladdr_size, Neighbor **ret);
int neighbor_add_foreign(Link *link, int family, const union in_addr_union *addr, const union lladdr_union *lladdr, size_t lladdr_size, Neighbor **ret);
bool neighbor_equal(const Neighbor *n1, const Neighbor *n2);
int neighbor_section_verify(Neighbor *neighbor);

View File

@ -95,6 +95,10 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return log_error_errno(r, "Could not enumerate addresses: %m");
r = manager_rtnl_enumerate_neighbors(m);
if (r < 0)
return log_error_errno(r, "Could not enumerate neighbors: %m");
r = manager_rtnl_enumerate_routes(m);
if (r < 0)
return log_error_errno(r, "Could not enumerate routes: %m");

View File

@ -0,0 +1,9 @@
[Match]
Name=dummy98
[Network]
IPv6AcceptRA=no
[Neighbor]
Address=192.168.10.1
LinkLayerAddress=00:00:5e:00:02:66

View File

@ -1422,6 +1422,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
'25-ip6gre-tunnel-remote-any.netdev',
'25-ipv6-address-label-section.network',
'25-neighbor-section.network',
'25-neighbor-next.network',
'25-neighbor-ipv6.network',
'25-neighbor-ip-dummy.network',
'25-neighbor-ip.network',
@ -1699,11 +1700,34 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
start_networkd()
self.wait_online(['dummy98:degraded'], timeout='40s')
print('### ip neigh list dev dummy98')
output = check_output('ip neigh list dev dummy98')
print(output)
self.assertRegex(output, '192.168.10.1.*00:00:5e:00:02:65.*PERMANENT')
self.assertRegex(output, '2004:da8:1::1.*00:00:5e:00:02:66.*PERMANENT')
def test_neighbor_reconfigure(self):
copy_unit_to_networkd_unit_path('25-neighbor-section.network', '12-dummy.netdev')
start_networkd()
self.wait_online(['dummy98:degraded'], timeout='40s')
print('### ip neigh list dev dummy98')
output = check_output('ip neigh list dev dummy98')
print(output)
self.assertRegex(output, '192.168.10.1.*00:00:5e:00:02:65.*PERMANENT')
self.assertRegex(output, '2004:da8:1::1.*00:00:5e:00:02:66.*PERMANENT')
remove_unit_from_networkd_path(['25-neighbor-section.network'])
copy_unit_to_networkd_unit_path('25-neighbor-next.network')
restart_networkd(3)
self.wait_online(['dummy98:degraded'], timeout='40s')
print('### ip neigh list dev dummy98')
output = check_output('ip neigh list dev dummy98')
print(output)
self.assertNotRegex(output, '192.168.10.1.*00:00:5e:00:02:65.*PERMANENT')
self.assertRegex(output, '192.168.10.1.*00:00:5e:00:02:66.*PERMANENT')
self.assertNotRegex(output, '2004:da8:1::1.*PERMANENT')
def test_neighbor_gre(self):
copy_unit_to_networkd_unit_path('25-neighbor-ip.network', '25-neighbor-ipv6.network', '25-neighbor-ip-dummy.network',
'12-dummy.netdev', '25-gre-tunnel-remote-any.netdev', '25-ip6gre-tunnel-remote-any.netdev')