diff --git a/CHANGELOG.md b/CHANGELOG.md index b950d05..2d7e82b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 1. `mctpd` now queries endpoints for their vendor-defined message support, and publishes as the newly-specced `VendorDefinedMessageTypes` dbus property. +2. `mctpd` now supports configuration on individual links, without having + to perform dbus property updates. Links may be matched on physical transport + binding type, or by sysfs paths, allowing individual interface roles to be + specified by the configuration file. + ### Fixes 1. mctpd's interface objects now expose the BusOwner1 interface when set as a BusOwner via the Role property +### Changed + +1. `mctpd`'s `mode` configuration (setting bus owner vs. endpoint roles) is + now called `role`. Configuration parsing will still allow the `mode` setting, + but this will be deprecated in a later release. + ## [2.5] - 2026-02-17 ### Added diff --git a/conf/mctpd.conf b/conf/mctpd.conf index d505d11..b6f95f1 100644 --- a/conf/mctpd.conf +++ b/conf/mctpd.conf @@ -1,5 +1,5 @@ -# Mode: either bus-owner or endpoint or unknown -mode = "bus-owner" +# Role: either bus-owner or endpoint or unknown +role = "bus-owner" # MCTP protocol configuration. Used for both endpoint and bus-owner modes. [mctp] diff --git a/docs/mctpd.md b/docs/mctpd.md index 4e866e3..aacc1f7 100644 --- a/docs/mctpd.md +++ b/docs/mctpd.md @@ -315,17 +315,24 @@ The configuration file has a global section, plus function-specific sections. These apply to all modes of `mctpd` operation. One top-level setting is defined: -#### `mode`: mctpd mode of operation +#### `role`: local MCTP device role -* type: string enum: `bus-owner` or `endpoint` +* type: string enum: `bus-owner`, `endpoint` or `unknown` * default: `bus-owner` -This sets the overall mode of `mctpd`, either as a Bus Owner (`mode = -"bus-owner"`) or Endpoint (`mode = "endpoint"`). In bus owner mode, mctpd will +This sets the overall role of `mctpd`, either as a Bus Owner (`role = +"bus-owner"`) or Endpoint (`role = "endpoint"`). In bus owner mode, mctpd will assume responsibility for allocating addresses to other endpoints. In endpoint mode, mctpd will not allocate addresses, but instead accept allocations from an external bus owner. +A value of `unknown` allows per-interface settings; the dbus interface's +`au.com.codeconstruct.MCTP.Interface1.Role` property may be written to set +a specific role for each interface. + +Previous versions of `mctpd` used `mode` for this configuration, both `role` +and `mode` are accepted. + ### `[mctp]` section This section affects MCTP protocol behaviour, and any common values used for @@ -398,3 +405,62 @@ space. Value should be between [```0.5 * TRECLAIM (5)```- ```10```] seconds. Such periodic polling is common for all the briged endpoints among allocated pool space [`.PoolStart` - `.PoolEnd`] of the bridge. Polling could be provisioned to be disabled via setting the value as ```0```. + +### `[[interface]]`: per-interface configuration + +The `[[interface]]` table allows configuration to be applied to specific +interfaces. Each `[[interface]]` entry contains a "match" definition, which +determines which MCTP interfaces the table applies to. + +Matches are processed in the order they appear in the configuration file; +the first `[[interface]]` section that matches is applied. + +Other content of the interface table is configuration to be applied. The +only setting currently supported is `role`, to set mctpd's role as +either bus-owner or endpoint on this interface. + +```toml +role = "bus-owner" +[[interface]] +match = ... +role = "endpoint" +``` + +#### Match types + +Match on all interfaces: + +```toml +# match all interfaces +[[interface]] +match = "all" +``` + +Match on a physical transport binding type: + +```toml +# match only MCTP-over-i2c interfaces +[[interface]] +match = { phys-type = "i2c" } +``` + +Available binding types are: `SMBus` / `I2C`, `PCIe`, `USB`, `KCS`, `serial`, +`I3C`, `MMBI`, or `UCIE`. Matches are case-insensitive. + +Match on a sysfs device path: + +```toml +# match on sysfs path +[[interface]] +match = { path = "/devices/pci0000:00/0000:00:08.3/usb10/10-0:1.0" } +``` + +Paths may use glob expressions: + +```toml +# match on globbed sysfs path +[[interface]] +match = { path = "/devices/pci0000:00/0000:00:08.3/*" } +``` + +Paths have the `/sys` prefix stripped. diff --git a/src/mctp-ops.c b/src/mctp-ops.c index 7e68101..f2d970a 100644 --- a/src/mctp-ops.c +++ b/src/mctp-ops.c @@ -7,6 +7,9 @@ #define _GNU_SOURCE +#include +#include +#include #include #include #include @@ -52,6 +55,36 @@ static int mctp_op_close(int sd) return close(sd); } +static int mctp_op_link_sysfs_path(const char *ifname, char **path) +{ + char *dev_class_path = NULL, *dev_path = NULL; + int rc = 1; + + rc = asprintf(&dev_class_path, "/sys/class/net/%s/device", ifname); + if (rc < 0) + return -1; + + dev_path = realpath(dev_class_path, NULL); + if (!dev_path) { + warnx("no path data for interface %s", ifname); + goto out; + } + + if (!strncmp(dev_path, "/sys", strlen("/sys"))) { + warnx("malformed interface path for %s", ifname); + goto out; + } + + *path = strdup(dev_path + 4); + rc = 0; + +out: + free(dev_path); + free(dev_class_path); + return rc; + return -1; +} + static void mctp_bug_warn(const char *fmt, va_list args) { vwarnx(fmt, args); @@ -81,6 +114,7 @@ const struct mctp_ops mctp_ops = { }, #endif .bug_warn = mctp_bug_warn, + .link_sysfs_path = mctp_op_link_sysfs_path, }; void mctp_ops_init(void) diff --git a/src/mctp-ops.h b/src/mctp-ops.h index 39f9501..161bd68 100644 --- a/src/mctp-ops.h +++ b/src/mctp-ops.h @@ -41,6 +41,7 @@ struct mctp_ops { struct sd_event_ops sd_event; #endif void (*bug_warn)(const char *fmt, va_list args); + int (*link_sysfs_path)(const char *ifname, char **devpath); }; extern const struct mctp_ops mctp_ops; diff --git a/src/mctpd.c b/src/mctpd.c index 1f28346..28d842c 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -140,6 +141,8 @@ struct link { bool published; int ifindex; enum endpoint_role role; + uint8_t phys_binding; + char *sysfs_path; char *path; sd_bus_slot *slot_iface; @@ -243,6 +246,23 @@ struct vdm_type_support { sd_bus_track *source_peer; }; +struct interface_config { + struct interface_config_match { + enum { + IFACE_MATCH_ALL, + IFACE_MATCH_BINDING, + IFACE_MATCH_PATH, + } type; + union { + enum mctp_phys_binding binding; + char *path; + }; + } match; + + bool role_set; + enum endpoint_role role; +}; + struct ctx { sd_event *event; sd_bus *bus; @@ -290,6 +310,11 @@ struct ctx { // bus owner/bridge polling interval in usecs for // checking endpoint's accessibility. uint64_t endpoint_poll; + + // interface configuration (from config file), to be matched and + // applied on new interface events + struct interface_config *interface_configs; + size_t num_interface_configs; }; static int emit_endpoint_added(const struct peer *peer); @@ -568,13 +593,13 @@ static const char *path_from_peer(const struct peer *peer) return peer->path; } -static int get_role(const char *mode, struct role *role) +static int get_role(const char *role_str, struct role *role) { unsigned int i; for (i = 0; i < ARRAY_SIZE(roles); i++) { if (roles[i].dbus_val && - (strcmp(roles[i].dbus_val, mode) == 0)) { + (strcmp(roles[i].dbus_val, role_str) == 0)) { memcpy(role, &roles[i], sizeof(struct role)); return 0; } @@ -4716,6 +4741,7 @@ static void free_link(struct link *link) sd_bus_slot_unref(link->slot_iface); sd_bus_slot_unref(link->slot_busowner); free(link->path); + free(link->sysfs_path); free(link); } @@ -5064,6 +5090,55 @@ static void del_net(struct net *net) free(net); } +static bool config_link_match(struct interface_config_match *match, + struct link *link) +{ + switch (match->type) { + case IFACE_MATCH_ALL: + return true; + case IFACE_MATCH_BINDING: + return link->phys_binding == match->binding; + case IFACE_MATCH_PATH: + if (!link->sysfs_path) + return false; + return fnmatch(match->path, link->sysfs_path, 0) == 0; + } + return false; +} + +static struct interface_config *link_find_configuration(struct ctx *ctx, + struct link *link) +{ + unsigned int i; + + for (i = 0; i < ctx->num_interface_configs; i++) { + struct interface_config *config = &ctx->interface_configs[i]; + if (config_link_match(&config->match, link)) + return config; + } + + return NULL; +} + +static int link_apply_configuration(struct ctx *ctx, struct link *link) +{ + struct interface_config *config; + + config = link_find_configuration(ctx, link); + if (!config) + return 0; + + if (config->role_set) + link->role = config->role; + + return 0; +} + +static int link_resolve_sysfs_path(struct link *link, const char *ifname) +{ + return mctp_ops.link_sysfs_path(ifname, &link->sysfs_path); +} + static int add_interface(struct ctx *ctx, int ifindex) { int rc; @@ -5080,8 +5155,6 @@ static int add_interface(struct ctx *ctx, int ifindex) return -ENOENT; } - uint8_t phys_binding = mctp_nl_phys_binding_byindex(ctx->nl, ifindex); - struct link *link = calloc(1, sizeof(*link)); if (!link) return -ENOMEM; @@ -5090,14 +5163,23 @@ static int add_interface(struct ctx *ctx, int ifindex) link->published = false; link->ifindex = ifindex; link->ctx = ctx; - /* Use the `mode` setting in conf/mctp.conf */ + link->phys_binding = mctp_nl_phys_binding_byindex(ctx->nl, ifindex); + /* Use the `role` setting in conf/mctp.conf */ link->role = ctx->default_role; + link_resolve_sysfs_path(link, ifname); rc = asprintf(&link->path, "%s/%s", MCTP_DBUS_PATH_LINKS, ifname); if (rc < 0) { rc = -ENOMEM; goto err_free; } + rc = link_apply_configuration(ctx, link); + if (rc) { + warnx("Failed to apply link configuration for link index %d", + ifindex); + goto err_free; + } + rc = mctp_nl_set_link_userdata(ctx->nl, ifindex, link); if (rc < 0) { warnx("Failed to set UserData for link index %d", ifindex); @@ -5115,7 +5197,7 @@ static int add_interface(struct ctx *ctx, int ifindex) bus_link_owner_vtable, link); } - if (phys_binding == MCTP_PHYS_BINDING_PCIE_VDM) { + if (link->phys_binding == MCTP_PHYS_BINDING_PCIE_VDM) { link->discovered = DISCOVERY_UNDISCOVERED; } @@ -5207,21 +5289,51 @@ static int parse_args(struct ctx *ctx, int argc, char **argv) return 0; } -static int parse_config_mode(struct ctx *ctx, const char *mode) +static int parse_config_role(const char *str, enum endpoint_role *rolep) { unsigned int i; for (i = 0; i < ARRAY_SIZE(roles); i++) { const struct role *role = &roles[i]; - if (!role->conf_val || strcmp(role->conf_val, mode)) + if (!role->conf_val || strcmp(role->conf_val, str)) continue; - ctx->default_role = role->role; + *rolep = role->role; return 0; } - warnx("invalid value '%s' for mode configuration", mode); + warnx("invalid value '%s' for role configuration", str); + return -1; +} + +static struct { + const char *name; + enum mctp_phys_binding binding; +} phys_bindings[] = { + { "SMBus", MCTP_PHYS_BINDING_SMBUS }, + { "I2C", MCTP_PHYS_BINDING_SMBUS }, // alias + { "PCIe", MCTP_PHYS_BINDING_PCIE_VDM }, + { "USB", MCTP_PHYS_BINDING_USB }, + { "KCS", MCTP_PHYS_BINDING_KCS }, + { "serial", MCTP_PHYS_BINDING_SERIAL }, + { "I3C", MCTP_PHYS_BINDING_I3C }, + { "MMBI", MCTP_PHYS_BINDING_MMBI }, + { "UCIe", MCTP_PHYS_BINDING_UCIE }, +}; + +static int parse_config_phys_binding(const char *type, + enum mctp_phys_binding *binding) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(phys_bindings); i++) { + if (!strcasecmp(type, phys_bindings[i].name)) { + *binding = phys_bindings[i].binding; + return 0; + } + } + return -1; } @@ -5357,9 +5469,204 @@ static int parse_config_bus_owner(struct ctx *ctx, toml_table_t *bus_owner) return 0; } +enum match_result { + MATCH_RES_NONE, + MATCH_RES_OK, + MATCH_RES_ERR, +}; + +static enum match_result +parse_config_interface_match_phys_binding(toml_table_t *table, + struct interface_config_match *match) +{ + static const char *key = "phys-type"; + enum mctp_phys_binding binding; + toml_datum_t val; + int rc; + + if (!toml_key_exists(table, key)) + return MATCH_RES_NONE; + + val = toml_string_in(table, key); + if (!val.ok) { + warnx("invalid %s match", key); + return MATCH_RES_ERR; + } + + rc = parse_config_phys_binding(val.u.s, &binding); + if (rc) { + warnx("invalid %s value %s", key, val.u.s); + free(val.u.s); + return MATCH_RES_ERR; + } + free(val.u.s); + + match->type = IFACE_MATCH_BINDING; + match->binding = binding; + + return MATCH_RES_OK; +} + +static enum match_result +parse_config_interface_match_path(toml_table_t *table, + struct interface_config_match *match) +{ + static const char *key = "path"; + toml_datum_t val; + + if (!toml_key_exists(table, key)) + return MATCH_RES_NONE; + + val = toml_string_in(table, key); + if (!val.ok) { + warnx("invalid path match"); + return MATCH_RES_ERR; + } + + match->type = IFACE_MATCH_PATH; + match->path = val.u.s; + return MATCH_RES_OK; +} + +const struct match_parser { + enum match_result (*parse)(toml_table_t *, + struct interface_config_match *); +} match_parsers[] = { + { parse_config_interface_match_phys_binding }, + { parse_config_interface_match_path }, +}; + +static int parse_config_interface_match(struct ctx *ctx, unsigned int idx, + toml_table_t *interface, + struct interface_config_match *match) +{ + toml_table_t *match_conf; + toml_datum_t match_str; + bool match_set = false; + unsigned int i; + + /* match = "all" is special: no table, but a string */ + match_str = toml_string_in(interface, "match"); + if (match_str.ok) { + char *s = match_str.u.s; + int rc = -1; + + if (!strcmp(s, "all")) { + match->type = IFACE_MATCH_ALL; + rc = 0; + } else { + warnx("invalid interface match value %s", s); + } + + free(s); + return rc; + } + + match_conf = toml_table_in(interface, "match"); + if (!match_conf) { + warnx("no match section for interface index %d", idx); + return -1; + } + + for (i = 0; i < ARRAY_SIZE(match_parsers); i++) { + const struct match_parser *p = &match_parsers[i]; + enum match_result mr; + + mr = p->parse(match_conf, match); + if (mr == MATCH_RES_ERR) + return -1; + + if (mr == MATCH_RES_OK) { + if (match_set) { + warnx("multiple match types for interface index %d", + idx); + return -1; + } + match_set = true; + } + } + + return match_set ? 0 : -1; +} + +static int parse_config_interface(struct ctx *ctx, unsigned int idx, + toml_table_t *interface, + struct interface_config *config) +{ + toml_datum_t conf_str; + int rc; + + rc = parse_config_interface_match(ctx, idx, interface, &config->match); + if (rc) { + warnx("no valid match config for interface index %x", idx); + return -1; + } + + conf_str = toml_string_in(interface, "role"); + if (conf_str.ok) { + char *s = conf_str.u.s; + int rc = parse_config_role(conf_str.u.s, &config->role); + if (rc) { + warnx("invalid role %s in interface section", s); + } else if (config->role == ENDPOINT_ROLE_UNKNOWN) { + warnx("cannot set 'unknown' role in interface section"); + rc = -1; + } else { + config->role_set = true; + } + free(s); + if (rc) + return rc; + } + + return 0; +} + +static int parse_config_interfaces(struct ctx *ctx, toml_array_t *interfaces) +{ + struct interface_config *configs; + int rc, i, n; + + n = toml_array_nelem(interfaces); + if (n < 0) { + warnx("can't parse interfaces array"); + return -1; + } + if (!n) + return 0; + + configs = calloc(n, sizeof(*configs)); + if (!configs) { + warn("can't allocate %d interface configs", n); + return -1; + } + + for (i = 0; i < n; i++) { + toml_table_t *interface = toml_table_at(interfaces, i); + if (!interface) { + warnx("no interface config at %d?", i); + goto err_free; + } + + rc = parse_config_interface(ctx, i, interface, &configs[i]); + if (rc) + goto err_free; + } + + ctx->interface_configs = configs; + ctx->num_interface_configs = n; + + return 0; + +err_free: + free(configs); + return -1; +} + static int parse_config(struct ctx *ctx) { toml_table_t *conf_root, *mctp_tab, *bus_owner; + toml_array_t *interfaces; bool conf_file_specified; char errbuf[256] = { 0 }; const char *filename; @@ -5389,9 +5696,12 @@ static int parse_config(struct ctx *ctx) goto out_close; } - val = toml_string_in(conf_root, "mode"); + val = toml_string_in(conf_root, "role"); + if (!val.ok) { + val = toml_string_in(conf_root, "mode"); + } if (val.ok) { - rc = parse_config_mode(ctx, val.u.s); + rc = parse_config_role(val.u.s, &ctx->default_role); free(val.u.s); if (rc) goto out_free; @@ -5411,6 +5721,13 @@ static int parse_config(struct ctx *ctx) goto out_free; } + interfaces = toml_array_in(conf_root, "interface"); + if (interfaces) { + rc = parse_config_interfaces(ctx, interfaces); + if (rc) + goto out_free; + } + rc = 0; out_free: @@ -5463,7 +5780,15 @@ static void setup_config_defaults(struct ctx *ctx) static void free_config(struct ctx *ctx) { + unsigned int i; + free(ctx->config_filename); + for (i = 0; i < ctx->num_interface_configs; i++) { + struct interface_config *config = &ctx->interface_configs[i]; + if (config->match.type == IFACE_MATCH_PATH) + free(config->match.path); + } + free(ctx->interface_configs); } static void free_ctrl_cmd_defaults(struct ctx *ctx) diff --git a/tests/mctp-ops-test.c b/tests/mctp-ops-test.c index c58ff91..80cff5a 100644 --- a/tests/mctp-ops-test.c +++ b/tests/mctp-ops-test.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -333,6 +334,45 @@ static int mctp_op_sd_event_source_set_time_relative(sd_event_source *s, } #endif +static int mctp_op_link_sysfs_path(const char *ifname, char **path) +{ + struct { + uint8_t opcode; + char ifname[IFNAMSIZ]; + } req; + struct { + uint8_t len; + char path[256]; + } resp; + size_t len; + ssize_t rc; + + len = strlen(ifname); + if (len > sizeof(req.ifname)) + errx(EXIT_FAILURE, "invalid interface name"); + + req.opcode = 0x04; + memcpy(req.ifname, ifname, len); + rc = send(control_sd, &req, len + sizeof(req.opcode), 0); + if (rc < 0) + err(EXIT_FAILURE, "control send error"); + + rc = recv(control_sd, &resp, sizeof(resp), 0); + if (rc <= 0) + err(EXIT_FAILURE, "control receive error"); + + if (sizeof(resp.len) + resp.len != (size_t)rc) + err(EXIT_FAILURE, "control receive parse error"); + + if (!resp.len) + return -1; + + resp.path[resp.len] = '\0'; + + *path = strndup(resp.path, resp.len); + return 0; +} + const struct mctp_ops mctp_ops = { .mctp = { .socket = mctp_op_mctp_socket, @@ -357,6 +397,7 @@ const struct mctp_ops mctp_ops = { }, #endif .bug_warn = mctp_bug_warn, + .link_sysfs_path = mctp_op_link_sysfs_path, }; void mctp_ops_init(void) diff --git a/tests/mctpenv/__init__.py b/tests/mctpenv/__init__.py index d1de8d0..2219962 100644 --- a/tests/mctpenv/__init__.py +++ b/tests/mctpenv/__init__.py @@ -81,6 +81,7 @@ def __init__( self.mtu = max_mtu self.up = up self.phys_binding = phys_binding + self.sysfs_path = '/devices/virtual/' + name def __str__(self): lladdrstr = ':'.join('%02x' % b for b in self.lladdr) @@ -297,6 +298,12 @@ def find_endpoint(self, addr): return iface, lladdr + def lookup_link_path(self, ifname: str): + iface = self.find_interface_by_name(ifname) + if iface is None: + return None + return iface.sysfs_path + def dump(self): print("system:") if self.interfaces: @@ -1289,14 +1296,27 @@ async def handle_control(self, nursery): await send_fd(self.sock_local, remote.fileno()) remote.close() nursery.start_soon(sd.run) + + elif op == 0x04: + # Link sysfs lookup + ifname = data[1:].decode('utf-8') + path = self.system.lookup_link_path(ifname) + if path is None: + data = b'\0' + else: + b = path.encode('utf-8') + data = bytes([len(b)]) + b + await self.sock_local.send(data) + else: print(f"unknown op {op}") class MctpdWrapper(MctpProcessWrapper): - def __init__(self, bus, sysnet, binary=None, config=None): + def __init__(self, bus, sysnet, binary=None, args=None, config=None): super().__init__(sysnet) self.bus = bus + self.args = args or ['-v'] self.binary = binary or './test-mctpd' self.config = config @@ -1340,18 +1360,18 @@ def name_owner_changed(name, new_owner, old_owner): # start mctpd, passing our control socket env = os.environ.copy() env['MCTP_TEST_SOCK'] = str(self.sock_remote.fileno()) + args = self.args if self.config: config_file = tempfile.NamedTemporaryFile('w', prefix="mctp.conf.") config_file.write(self.config) config_file.flush() - command = [self.binary, '-v', '-c', config_file.name] + args += ['-c', config_file.name] else: config_file = None - command = [self.binary, '-v'] proc = await trio.lowlevel.open_process( - command=command, + command=[self.binary] + args, pass_fds=(1, 2, self.sock_remote.fileno()), env=env, ) @@ -1430,11 +1450,13 @@ async def main(): import asyncdbus binary = None + args = None if len(sys.argv) > 1: binary = sys.argv[1] + args = sys.argv[2:] async with asyncdbus.MessageBus().connect() as dbus: sysnet = await default_sysnet() - mctpd = MctpdWrapper(dbus, sysnet, binary=binary) + mctpd = MctpdWrapper(dbus, sysnet, binary=binary, args=args) async with trio.open_nursery() as nursery: nursery.start_soon(sighandler) await mctpd.start_mctpd(nursery) diff --git a/tests/test_mctpd.py b/tests/test_mctpd.py index 955ce38..06d65bd 100644 --- a/tests/test_mctpd.py +++ b/tests/test_mctpd.py @@ -4,12 +4,19 @@ from mctp_test_utils import ( mctpd_mctp_iface_obj, + mctpd_mctp_iface_control_obj, mctpd_mctp_network_obj, mctpd_mctp_endpoint_common_obj, mctpd_mctp_endpoint_control_obj, mctpd_mctp_base_iface_obj, ) -from mctpenv import Endpoint, MCTPSockAddr, MCTPControlCommand, MctpdWrapper +from mctpenv import ( + Endpoint, + MCTPSockAddr, + MCTPControlCommand, + MctpdWrapper, + PhysicalBinding, +) # DBus constant symbol suffixes: # @@ -1956,3 +1963,141 @@ async def handle_mctp_control(self, sock, src_addr, msg): res = await mctpd.stop_mctpd() assert res == 0 + + +async def test_iface_config_none(dbus, sysnet, nursery): + """Test that our interface config tests are functional""" + config = """ + role = "unknown" + """ + mctpd = MctpdWrapper(dbus, sysnet, config=config) + await mctpd.start_mctpd(nursery) + + iface = await mctpd_mctp_iface_control_obj(dbus, mctpd.system.interfaces[0]) + role = await iface.get_role() + assert role == "Unknown" + res = await mctpd.stop_mctpd() + assert res == 0 + + +async def test_iface_config_match_all(dbus, sysnet, nursery): + """Test that our interface config tests are functional""" + config = """ + role = "unknown" + [[interface]] + match = "all" + role = "bus-owner" + """ + mctpd = MctpdWrapper(dbus, sysnet, config=config) + await mctpd.start_mctpd(nursery) + + iface = await mctpd_mctp_iface_control_obj(dbus, mctpd.system.interfaces[0]) + role = await iface.get_role() + assert role == "BusOwner" + res = await mctpd.stop_mctpd() + assert res == 0 + + +async def test_iface_config_match_phys_binding(dbus, sysnet, nursery): + """Test that we can match an interface from a phys binding type""" + config = """ + role = "unknown" + [[interface]] + match = { phys-type = "i2c" } + role = "bus-owner" + """ + + mctpd = MctpdWrapper(dbus, sysnet, config=config) + iface = mctpd.system.interfaces[0] + iface.phys_binding = PhysicalBinding.SMBUS + + await mctpd.start_mctpd(nursery) + + iface = await mctpd_mctp_iface_control_obj(dbus, iface) + role = await iface.get_role() + assert role == "BusOwner" + + res = await mctpd.stop_mctpd() + assert res == 0 + + +async def test_iface_config_match_path_exact(dbus, sysnet, nursery): + """Test that we can match an interface from an exact path""" + config = """ + role = "unknown" + [[interface]] + match = { path = "/devices/virtual/mctp0" } + role = "bus-owner" + """ + + mctpd = MctpdWrapper(dbus, sysnet, config=config) + await mctpd.start_mctpd(nursery) + + iface = await mctpd_mctp_iface_control_obj(dbus, mctpd.system.interfaces[0]) + role = await iface.get_role() + assert role == "BusOwner" + + res = await mctpd.stop_mctpd() + assert res == 0 + + +async def test_iface_config_nomatch_path(dbus, sysnet, nursery): + """Test that we do not match an interface from an exact (non-matching) + path + """ + config = """ + role = "unknown" + [[interface]] + match = { path = "/devices/virtual/mctp1" } + role = "bus-owner" + """ + + mctpd = MctpdWrapper(dbus, sysnet, config=config) + await mctpd.start_mctpd(nursery) + + iface = await mctpd_mctp_iface_control_obj(dbus, mctpd.system.interfaces[0]) + role = await iface.get_role() + assert role == "Unknown" + res = await mctpd.stop_mctpd() + assert res == 0 + + +async def test_iface_config_match_path_glob(dbus, sysnet, nursery): + """Test that we can match an interface from a globbed path""" + config = """ + role = "unknown" + [[interface]] + match = { path = "/devices/virtual/mctp*" } + role = "bus-owner" + """ + + mctpd = MctpdWrapper(dbus, sysnet, config=config) + await mctpd.start_mctpd(nursery) + + iface = await mctpd_mctp_iface_control_obj(dbus, mctpd.system.interfaces[0]) + role = await iface.get_role() + assert role == "BusOwner" + + res = await mctpd.stop_mctpd() + assert res == 0 + + +async def test_iface_config_match_path_none(dbus, sysnet, nursery): + """Test that we can handle a missing sysfs path, not matching anything""" + config = """ + role = "unknown" + [[interface]] + match = { path = "*" } + role = "bus-owner" + """ + + mctpd = MctpdWrapper(dbus, sysnet, config=config) + mctpd.system.interfaces[0].sysfs_path = None + await mctpd.start_mctpd(nursery) + + iface = await mctpd_mctp_iface_control_obj(dbus, mctpd.system.interfaces[0]) + role = await iface.get_role() + assert role != "BusOwner" + + res = await mctpd.stop_mctpd() + assert res == 0 diff --git a/tests/test_mctpd_endpoint.py b/tests/test_mctpd_endpoint.py index 0785cae..f29a967 100644 --- a/tests/test_mctpd_endpoint.py +++ b/tests/test_mctpd_endpoint.py @@ -24,7 +24,7 @@ @pytest.fixture def config(): return """ - mode = "endpoint" + role = "endpoint" """