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);
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:
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);
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;
static int link_drop_foreign_config(Link *link) {
Address *address;
+ Neighbor *neighbor;
Route *route;
Iterator i;
int r;
}
}
+ 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)
static int link_drop_config(Link *link) {
Address *address, *pool_address;
+ Neighbor *neighbor;
Route *route;
Iterator i;
int r;
}
}
+ 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)
Set *addresses;
Set *addresses_foreign;
+ Set *neighbors;
+ Set *neighbors_foreign;
Set *routes;
Set *routes_foreign;
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;
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;
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;
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);
#include "networkd-link.h"
#include "networkd-manager.h"
#include "networkd-neighbor.h"
+#include "set.h"
void neighbor_free(Neighbor *neighbor) {
if (!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);
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);
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");
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;
#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;
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);
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);
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");
--- /dev/null
+[Match]
+Name=dummy98
+
+[Network]
+IPv6AcceptRA=no
+
+[Neighbor]
+Address=192.168.10.1
+LinkLayerAddress=00:00:5e:00:02:66
'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',
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')