This was done by running a locally built clang-format with https://github.com/llvm/llvm-project/pull/137617 and https://github.com/llvm/llvm-project/pull/137840 applied on all .c and .h files.
401 lines
14 KiB
C
401 lines
14 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include <linux/if_bridge.h>
|
|
#include <net/if.h>
|
|
|
|
#include "netlink-util.h"
|
|
#include "networkd-bridge-mdb.h"
|
|
#include "networkd-link.h"
|
|
#include "networkd-manager.h"
|
|
#include "networkd-network.h"
|
|
#include "networkd-queue.h"
|
|
#include "networkd-util.h"
|
|
#include "string-util.h"
|
|
#include "vlan-util.h"
|
|
|
|
#define STATIC_BRIDGE_MDB_ENTRIES_PER_NETWORK_MAX 1024U
|
|
|
|
static BridgeMDB* bridge_mdb_free(BridgeMDB *mdb) {
|
|
if (!mdb)
|
|
return NULL;
|
|
|
|
if (mdb->network) {
|
|
assert(mdb->section);
|
|
hashmap_remove(mdb->network->bridge_mdb_entries_by_section, mdb->section);
|
|
}
|
|
|
|
config_section_free(mdb->section);
|
|
|
|
return mfree(mdb);
|
|
}
|
|
|
|
DEFINE_SECTION_CLEANUP_FUNCTIONS(BridgeMDB, bridge_mdb_free);
|
|
|
|
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
|
|
bridge_mdb_hash_ops_by_section,
|
|
ConfigSection, config_section_hash_func, config_section_compare_func,
|
|
BridgeMDB, bridge_mdb_free);
|
|
|
|
static int bridge_mdb_new_static(
|
|
Network *network,
|
|
const char *filename,
|
|
unsigned section_line,
|
|
BridgeMDB **ret) {
|
|
|
|
_cleanup_(config_section_freep) ConfigSection *n = NULL;
|
|
_cleanup_(bridge_mdb_freep) BridgeMDB *mdb = NULL;
|
|
int r;
|
|
|
|
assert(network);
|
|
assert(ret);
|
|
assert(filename);
|
|
assert(section_line > 0);
|
|
|
|
r = config_section_new(filename, section_line, &n);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
/* search entry in hashmap first. */
|
|
mdb = hashmap_get(network->bridge_mdb_entries_by_section, n);
|
|
if (mdb) {
|
|
*ret = TAKE_PTR(mdb);
|
|
return 0;
|
|
}
|
|
|
|
if (hashmap_size(network->bridge_mdb_entries_by_section) >= STATIC_BRIDGE_MDB_ENTRIES_PER_NETWORK_MAX)
|
|
return -E2BIG;
|
|
|
|
/* allocate space for an MDB entry. */
|
|
mdb = new(BridgeMDB, 1);
|
|
if (!mdb)
|
|
return -ENOMEM;
|
|
|
|
/* init MDB structure. */
|
|
*mdb = (BridgeMDB) {
|
|
.network = network,
|
|
.section = TAKE_PTR(n),
|
|
.type = _BRIDGE_MDB_ENTRY_TYPE_INVALID,
|
|
};
|
|
|
|
r = hashmap_ensure_put(&network->bridge_mdb_entries_by_section, &bridge_mdb_hash_ops_by_section, mdb->section, mdb);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
/* return allocated MDB structure. */
|
|
*ret = TAKE_PTR(mdb);
|
|
return 0;
|
|
}
|
|
|
|
static int bridge_mdb_configure_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, void *userdata) {
|
|
int r;
|
|
|
|
assert(m);
|
|
assert(link);
|
|
|
|
r = sd_netlink_message_get_errno(m);
|
|
if (r < 0 && r != -EEXIST) {
|
|
log_link_message_warning_errno(link, m, r, "Could not add MDB entry");
|
|
link_enter_failed(link);
|
|
return 1;
|
|
}
|
|
|
|
if (link->static_bridge_mdb_messages == 0) {
|
|
link->static_bridge_mdb_configured = true;
|
|
link_check_ready(link);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* send a request to the kernel to add an MDB entry */
|
|
static int bridge_mdb_configure(BridgeMDB *mdb, Link *link, Request *req) {
|
|
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
|
|
struct br_mdb_entry entry;
|
|
int r;
|
|
|
|
assert(mdb);
|
|
assert(link);
|
|
assert(link->manager);
|
|
assert(req);
|
|
|
|
if (DEBUG_LOGGING)
|
|
log_link_debug(link, "Configuring bridge MDB entry: MulticastGroupAddress=%s, VLANId=%u",
|
|
IN_ADDR_TO_STRING(mdb->family, &mdb->group_addr), mdb->vlan_id);
|
|
|
|
entry = (struct br_mdb_entry) {
|
|
/* If MDB entry is added on bridge master, then the state must be MDB_TEMPORARY,
|
|
* except on L2 routes, where they must always be permanent.
|
|
* See br_mdb_add_group() in net/bridge/br_mdb.c of kernel. */
|
|
.state = link->master_ifindex <= 0 && mdb->type == BRIDGE_MDB_ENTRY_TYPE_L3 ? MDB_TEMPORARY : MDB_PERMANENT,
|
|
.ifindex = link->ifindex,
|
|
.vid = mdb->vlan_id,
|
|
};
|
|
|
|
switch (mdb->type) {
|
|
case BRIDGE_MDB_ENTRY_TYPE_L2:
|
|
memcpy(entry.addr.u.mac_addr, &mdb->l2_addr.ether_addr_octet, ETH_ALEN);
|
|
entry.addr.proto = 0;
|
|
break;
|
|
case BRIDGE_MDB_ENTRY_TYPE_L3:
|
|
switch (mdb->family) {
|
|
case AF_INET:
|
|
entry.addr.u.ip4 = mdb->group_addr.in.s_addr;
|
|
entry.addr.proto = htobe16(ETH_P_IP);
|
|
break;
|
|
|
|
case AF_INET6:
|
|
entry.addr.u.ip6 = mdb->group_addr.in6;
|
|
entry.addr.proto = htobe16(ETH_P_IPV6);
|
|
break;
|
|
|
|
default:
|
|
assert_not_reached();
|
|
}
|
|
break;
|
|
default:
|
|
assert_not_reached();
|
|
}
|
|
|
|
r = sd_rtnl_message_new_mdb(link->manager->rtnl, &m, RTM_NEWMDB,
|
|
link->master_ifindex > 0 ? link->master_ifindex : link->ifindex);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = sd_netlink_message_append_data(m, MDBA_SET_ENTRY, &entry, sizeof(entry));
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return request_call_netlink_async(link->manager->rtnl, m, req);
|
|
}
|
|
|
|
static bool bridge_mdb_is_ready_to_configure(Link *link) {
|
|
Link *master;
|
|
|
|
assert(link);
|
|
|
|
if (!link_is_ready_to_configure(link, false))
|
|
return false;
|
|
|
|
if (!link->master_set)
|
|
return false;
|
|
|
|
if (link->master_ifindex <= 0 && streq_ptr(link->kind, "bridge"))
|
|
return true; /* The interface is bridge master. */
|
|
|
|
if (link_get_master(link, &master) < 0)
|
|
return false;
|
|
|
|
if (!streq_ptr(master->kind, "bridge"))
|
|
return false;
|
|
|
|
if (!IN_SET(master->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED))
|
|
return false;
|
|
|
|
if (master->set_flags_messages > 0)
|
|
return false;
|
|
|
|
if (!link_has_carrier(master))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static int bridge_mdb_process_request(Request *req, Link *link, void *userdata) {
|
|
BridgeMDB *mdb = ASSERT_PTR(userdata);
|
|
int r;
|
|
|
|
assert(req);
|
|
assert(link);
|
|
|
|
if (!bridge_mdb_is_ready_to_configure(link))
|
|
return 0;
|
|
|
|
r = bridge_mdb_configure(mdb, link, req);
|
|
if (r < 0)
|
|
return log_link_warning_errno(link, r, "Failed to configure bridge MDB: %m");
|
|
|
|
return 1;
|
|
}
|
|
|
|
int link_request_static_bridge_mdb(Link *link) {
|
|
BridgeMDB *mdb;
|
|
int r;
|
|
|
|
assert(link);
|
|
assert(link->manager);
|
|
|
|
link->static_bridge_mdb_configured = false;
|
|
|
|
if (!link->network)
|
|
return 0;
|
|
|
|
if (hashmap_isempty(link->network->bridge_mdb_entries_by_section))
|
|
goto finish;
|
|
|
|
HASHMAP_FOREACH(mdb, link->network->bridge_mdb_entries_by_section) {
|
|
r = link_queue_request_full(link, REQUEST_TYPE_BRIDGE_MDB,
|
|
mdb, NULL,
|
|
trivial_hash_func,
|
|
trivial_compare_func,
|
|
bridge_mdb_process_request,
|
|
&link->static_bridge_mdb_messages,
|
|
bridge_mdb_configure_handler,
|
|
NULL);
|
|
if (r < 0)
|
|
return log_link_error_errno(link, r, "Failed to request MDB entry to multicast group database: %m");
|
|
}
|
|
|
|
finish:
|
|
if (link->static_bridge_mdb_messages == 0) {
|
|
link->static_bridge_mdb_configured = true;
|
|
link_check_ready(link);
|
|
} else {
|
|
log_link_debug(link, "Setting bridge MDB entries.");
|
|
link_set_state(link, LINK_STATE_CONFIGURING);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bridge_mdb_verify(BridgeMDB *mdb) {
|
|
if (section_is_invalid(mdb->section))
|
|
return -EINVAL;
|
|
|
|
switch (mdb->type) {
|
|
case BRIDGE_MDB_ENTRY_TYPE_L2:
|
|
if (!ether_addr_is_multicast(&mdb->l2_addr))
|
|
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
|
|
"%s: MulticastGroupAddress= is not an L2 multicast address. "
|
|
"Ignoring [BridgeMDB] section from line %u.",
|
|
mdb->section->filename, mdb->section->line);
|
|
break;
|
|
case BRIDGE_MDB_ENTRY_TYPE_L3:
|
|
if (mdb->family == AF_UNSPEC)
|
|
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
|
|
"%s: [BridgeMDB] section without MulticastGroupAddress= field configured. "
|
|
"Ignoring [BridgeMDB] section from line %u.",
|
|
mdb->section->filename, mdb->section->line);
|
|
|
|
if (!in_addr_is_multicast(mdb->family, &mdb->group_addr))
|
|
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
|
|
"%s: MulticastGroupAddress= is not a multicast address. "
|
|
"Ignoring [BridgeMDB] section from line %u.",
|
|
mdb->section->filename, mdb->section->line);
|
|
|
|
switch (mdb->family) {
|
|
case AF_INET:
|
|
if (in4_addr_is_local_multicast(&mdb->group_addr.in))
|
|
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
|
|
"%s: MulticastGroupAddress= is a local multicast address. "
|
|
"Ignoring [BridgeMDB] section from line %u.",
|
|
mdb->section->filename, mdb->section->line);
|
|
break;
|
|
default:
|
|
if (in6_addr_is_link_local_all_nodes(&mdb->group_addr.in6))
|
|
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
|
|
"%s: MulticastGroupAddress= is the multicast all nodes address. "
|
|
"Ignoring [BridgeMDB] section from line %u.",
|
|
mdb->section->filename, mdb->section->line);
|
|
}
|
|
break;
|
|
default:
|
|
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
|
|
"%s: [BridgeMDB] section without MulticastGroupAddress= field configured. "
|
|
"Ignoring [BridgeMDB] section from line %u.",
|
|
mdb->section->filename, mdb->section->line);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void network_drop_invalid_bridge_mdb_entries(Network *network) {
|
|
BridgeMDB *mdb;
|
|
|
|
assert(network);
|
|
|
|
HASHMAP_FOREACH(mdb, network->bridge_mdb_entries_by_section)
|
|
if (bridge_mdb_verify(mdb) < 0)
|
|
bridge_mdb_free(mdb);
|
|
}
|
|
|
|
/* parse the VLAN Id from config files. */
|
|
int config_parse_mdb_vlan_id(
|
|
const char *unit,
|
|
const char *filename,
|
|
unsigned line,
|
|
const char *section,
|
|
unsigned section_line,
|
|
const char *lvalue,
|
|
int ltype,
|
|
const char *rvalue,
|
|
void *data,
|
|
void *userdata) {
|
|
|
|
_cleanup_(bridge_mdb_free_or_set_invalidp) BridgeMDB *mdb = NULL;
|
|
Network *network = userdata;
|
|
int r;
|
|
|
|
assert(filename);
|
|
assert(section);
|
|
assert(lvalue);
|
|
assert(rvalue);
|
|
assert(data);
|
|
|
|
r = bridge_mdb_new_static(network, filename, section_line, &mdb);
|
|
if (r < 0)
|
|
return log_oom();
|
|
|
|
r = config_parse_vlanid(unit, filename, line, section,
|
|
section_line, lvalue, ltype,
|
|
rvalue, &mdb->vlan_id, userdata);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
TAKE_PTR(mdb);
|
|
return 0;
|
|
}
|
|
|
|
/* parse the multicast group from config files. */
|
|
int config_parse_mdb_group_address(
|
|
const char *unit,
|
|
const char *filename,
|
|
unsigned line,
|
|
const char *section,
|
|
unsigned section_line,
|
|
const char *lvalue,
|
|
int ltype,
|
|
const char *rvalue,
|
|
void *data,
|
|
void *userdata) {
|
|
|
|
_cleanup_(bridge_mdb_free_or_set_invalidp) BridgeMDB *mdb = NULL;
|
|
Network *network = userdata;
|
|
int r;
|
|
|
|
assert(filename);
|
|
assert(section);
|
|
assert(lvalue);
|
|
assert(rvalue);
|
|
assert(data);
|
|
|
|
r = bridge_mdb_new_static(network, filename, section_line, &mdb);
|
|
if (r < 0)
|
|
return log_oom();
|
|
|
|
r = parse_ether_addr(rvalue, &mdb->l2_addr);
|
|
if (r >= 0)
|
|
mdb->type = BRIDGE_MDB_ENTRY_TYPE_L2;
|
|
else {
|
|
r = in_addr_from_string_auto(rvalue, &mdb->family, &mdb->group_addr);
|
|
if (r < 0) {
|
|
log_syntax(unit, LOG_WARNING, filename, line, r,
|
|
"Cannot parse multicast group address as either L2 MAC, IPv4 or IPv6, ignoring: %m");
|
|
return 0;
|
|
}
|
|
mdb->type = BRIDGE_MDB_ENTRY_TYPE_L3;
|
|
}
|
|
|
|
TAKE_PTR(mdb);
|
|
return 0;
|
|
}
|