diff --git a/meson.build b/meson.build index f93d28cf..e37fea32 100644 --- a/meson.build +++ b/meson.build @@ -25,6 +25,10 @@ conf.set10('HAVE_STRUCT_MCTP_FQ_ADDR', cc.has_type('struct mctp_fq_addr', prefix: '#include '), description: 'Is struct mctp_fq_addr available?' ) +conf.set10('HAVE_IFLA_MCTP_PHYS_BINDING', + cc.has_header_symbol('linux/if_link.h', 'IFLA_MCTP_PHYS_BINDING'), + description: 'Is IFLA_MCTP_PHYS_BINDING enum member available?' +) conf.set10('MCTPD_RECOVER_NIL_UUID', get_option('unsafe-recover-nil-uuid'), description: 'Consider a nil UUID to be valid for endpoint recovery purposes', diff --git a/src/mctp-control-spec.h b/src/mctp-control-spec.h index e3f2f626..03f95721 100644 --- a/src/mctp-control-spec.h +++ b/src/mctp-control-spec.h @@ -351,3 +351,21 @@ static inline void mctp_ctrl_msg_hdr_init_resp(struct mctp_ctrl_msg_hdr *resp, resp->command_code = req.command_code; resp->rq_dgram_inst = (req.rq_dgram_inst & RQDI_IID_MASK) | RQDI_RESP; } + +/* MCTP IDs and Codes from DMTF specification + * "DSP0239 Management Component Transport Protocol (MCTP) IDs and Codes" + * https://www.dmtf.org/sites/default/files/standards/documents/DSP0239_1.11.1.pdf + */ +enum mctp_phys_binding { + MCTP_PHYS_BINDING_UNSPEC = 0x00, + MCTP_PHYS_BINDING_SMBUS = 0x01, + MCTP_PHYS_BINDING_PCIE_VDM = 0x02, + MCTP_PHYS_BINDING_USB = 0x03, + MCTP_PHYS_BINDING_KCS = 0x04, + MCTP_PHYS_BINDING_SERIAL = 0x05, + MCTP_PHYS_BINDING_I3C = 0x06, + MCTP_PHYS_BINDING_MMBI = 0x07, + MCTP_PHYS_BINDING_PCC = 0x08, + MCTP_PHYS_BINDING_UCIE = 0x09, + MCTP_PHYS_BINDING_VENDOR = 0xFF, +}; diff --git a/src/mctp-netlink.c b/src/mctp-netlink.c index ed91d932..6fe767e6 100644 --- a/src/mctp-netlink.c +++ b/src/mctp-netlink.c @@ -15,6 +15,7 @@ #include #include +#include "mctp-control-spec.h" #include "mctp-netlink.h" #include "mctp.h" #include "mctp-util.h" @@ -29,6 +30,7 @@ struct linkmap_entry { uint32_t min_mtu; uint32_t max_mtu; uint32_t hwaddr_len; + uint8_t phys_binding; mctp_eid_t *local_eids; size_t num_local; @@ -59,7 +61,8 @@ static void sort_linkmap(mctp_nl *nl); static int linkmap_add_entry(mctp_nl *nl, struct ifinfomsg *info, const char *ifname, size_t ifname_len, uint32_t net, bool up, uint32_t min_mtu, - uint32_t max_mtu, size_t hwaddr_len); + uint32_t max_mtu, size_t hwaddr_len, + uint8_t phys_binding); static struct linkmap_entry *entry_byindex(const mctp_nl *nl, int index); static int open_nl_socket(void) @@ -727,6 +730,7 @@ static int parse_getlink_dump(mctp_nl *nl, struct nlmsghdr *nlh, uint32_t len) char *ifname = NULL; size_t ifname_len, rlen, nlen, mlen, hwaddr_len; uint32_t net, min_mtu = 0, max_mtu = 0; + uint8_t phys_binding; bool up; if (nlh->nlmsg_type == NLMSG_DONE) @@ -785,11 +789,14 @@ static int parse_getlink_dump(mctp_nl *nl, struct nlmsghdr *nlh, uint32_t len) hwaddr_len = 0; mctp_get_rtnlmsg_attr(IFLA_ADDRESS, rta, rlen, &hwaddr_len); - /* TODO: media type */ + /* Treat missing physical binding type attribute as UNSPEC */ + phys_binding = MCTP_PHYS_BINDING_UNSPEC; + mctp_get_rtnlmsg_attr_u8(IFLA_MCTP_PHYS_BINDING, rt_mctp, mlen, + &phys_binding); up = info->ifi_flags & IFF_UP; linkmap_add_entry(nl, info, ifname, ifname_len, net, up, - min_mtu, max_mtu, hwaddr_len); + min_mtu, max_mtu, hwaddr_len, phys_binding); } // Not done. return 1; @@ -1068,6 +1075,15 @@ int mctp_nl_hwaddr_len_byindex(const mctp_nl *nl, int index, return 0; } +uint8_t mctp_nl_phys_binding_byindex(const mctp_nl *nl, int index) +{ + struct linkmap_entry *entry = entry_byindex(nl, index); + if (!entry) { + return MCTP_PHYS_BINDING_UNSPEC; + } + return entry->phys_binding; +} + mctp_eid_t *mctp_nl_addrs_byindex(const mctp_nl *nl, int index, size_t *ret_num) { struct linkmap_entry *entry = entry_byindex(nl, index); @@ -1151,7 +1167,8 @@ bool mctp_nl_if_exists(const mctp_nl *nl, int ifindex) static int linkmap_add_entry(mctp_nl *nl, struct ifinfomsg *info, const char *ifname, size_t ifname_len, uint32_t net, bool up, uint32_t min_mtu, - uint32_t max_mtu, size_t hwaddr_len) + uint32_t max_mtu, size_t hwaddr_len, + uint8_t phys_binding) { struct linkmap_entry *entry; size_t newsz; @@ -1192,6 +1209,7 @@ static int linkmap_add_entry(mctp_nl *nl, struct ifinfomsg *info, entry->max_mtu = max_mtu; entry->min_mtu = min_mtu; entry->hwaddr_len = hwaddr_len; + entry->phys_binding = phys_binding; return 0; } diff --git a/src/mctp-netlink.h b/src/mctp-netlink.h index a8120f5d..f7d02f09 100644 --- a/src/mctp-netlink.h +++ b/src/mctp-netlink.h @@ -6,6 +6,7 @@ #include #include +#include "mctp-control-spec.h" #include "mctp.h" struct mctp_nl; @@ -83,6 +84,8 @@ uint32_t mctp_nl_max_mtu_byindex(const mctp_nl *nl, int index); /* Returns negative errno on failure */ int mctp_nl_hwaddr_len_byindex(const mctp_nl *nl, int index, size_t *ret_hwaddr_len); +/* Returns interface physical binding, or MCTP_PHYS_BINDING_UNSPEC if bad index */ +uint8_t mctp_nl_phys_binding_byindex(const mctp_nl *nl, int index); /* Caller to free */ mctp_eid_t *mctp_nl_addrs_byindex(const mctp_nl *nl, int index, size_t *ret_num); diff --git a/src/mctp.h b/src/mctp.h index e7627dfe..0641b4c4 100644 --- a/src/mctp.h +++ b/src/mctp.h @@ -82,11 +82,22 @@ struct sockaddr_mctp_ext { enum { IFLA_MCTP_UNSPEC, IFLA_MCTP_NET, + IFLA_MCTP_PHYS_BINDING, __IFLA_MCTP_MAX, }; #define IFLA_MCTP_MAX (__IFLA_MCTP_MAX - 1) +#else /* IFLA_MCTP_MAX */ + +#if !HAVE_IFLA_MCTP_PHYS_BINDING +enum { + IFLA_MCTP_PHYS_BINDING = IFLA_MCTP_NET + 1, +}; +#undef IFLA_MCTP_MAX +#define IFLA_MCTP_MAX IFLA_MCTP_PHYS_BINDING +#endif /* HAVE_IFLA_MCTP_PHYS_BINDING */ + #endif /* IFLA_MCTP_MAX */ /* setsockopt(2) options */ diff --git a/src/mctpd.c b/src/mctpd.c index d3ae63ca..563afb41 100644 --- a/src/mctpd.c +++ b/src/mctpd.c @@ -117,7 +117,14 @@ static const struct role roles[] = { }, }; +enum discovery_state { + DISCOVERY_UNSUPPORTED, + DISCOVERY_DISCOVERED, + DISCOVERY_UNDISCOVERED, +}; + struct link { + enum discovery_state discovered; bool published; int ifindex; enum endpoint_role role; @@ -777,6 +784,9 @@ static int handle_control_set_endpoint_id(struct ctx *ctx, int sd, warnx("ERR: cannot add bus owner to object lists"); } + if (link_data->discovered != DISCOVERY_UNSUPPORTED) { + link_data->discovered = DISCOVERY_DISCOVERED; + } resp->status = SET_MCTP_EID_ASSIGNMENT_STATUS(MCTP_SET_EID_ACCEPTED) | SET_MCTP_EID_ALLOCATION_STATUS(MCTP_SET_EID_POOL_NONE); @@ -786,6 +796,20 @@ static int handle_control_set_endpoint_id(struct ctx *ctx, int sd, return reply_message(ctx, sd, resp, resp_len, addr); case MCTP_SET_EID_DISCOVERED: + if (link_data->discovered == DISCOVERY_UNSUPPORTED) { + resp->completion_code = MCTP_CTRL_CC_ERROR_INVALID_DATA; + resp_len = sizeof(struct mctp_ctrl_resp); + return reply_message(ctx, sd, resp, resp_len, addr); + } + + link_data->discovered = DISCOVERY_DISCOVERED; + resp->status = + SET_MCTP_EID_ASSIGNMENT_STATUS(MCTP_SET_EID_REJECTED) | + SET_MCTP_EID_ALLOCATION_STATUS(MCTP_SET_EID_POOL_NONE); + resp->eid_set = req->eid; + resp->eid_pool_size = 0; + return reply_message(ctx, sd, resp, resp_len, addr); + case MCTP_SET_EID_RESET: // unsupported resp->completion_code = MCTP_CTRL_CC_ERROR_INVALID_DATA; @@ -948,6 +972,97 @@ handle_control_resolve_endpoint_id(struct ctx *ctx, int sd, return reply_message(ctx, sd, resp, resp_len, addr); } +static int handle_control_prepare_endpoint_discovery( + struct ctx *ctx, int sd, const struct sockaddr_mctp_ext *addr, + const uint8_t *buf, const size_t buf_size) +{ + struct mctp_ctrl_msg_hdr *req = NULL; + struct mctp_ctrl_resp_prepare_discovery respi = { 0 }, *resp = &respi; + struct link *link_data; + + if (buf_size < sizeof(*req)) { + warnx("short Prepare for Endpoint Discovery message"); + return -ENOMSG; + } + + link_data = mctp_nl_get_link_userdata(ctx->nl, addr->smctp_ifindex); + if (!link_data) { + bug_warn("unconfigured interface %d", addr->smctp_ifindex); + return -ENOENT; + } + + if (link_data->role == ENDPOINT_ROLE_BUS_OWNER) { + // ignore message if we are bus owner + return 0; + } + + req = (void *)buf; + resp = (void *)resp; + mctp_ctrl_msg_hdr_init_resp(&resp->ctrl_hdr, *req); + + if (link_data->discovered == DISCOVERY_UNSUPPORTED) { + warnx("received prepare for discovery request to unsupported interface %d", + addr->smctp_ifindex); + resp->completion_code = MCTP_CTRL_CC_ERROR_UNSUPPORTED_CMD; + return reply_message_phys(ctx, sd, resp, + sizeof(struct mctp_ctrl_resp), addr); + } + + if (link_data->discovered == DISCOVERY_DISCOVERED) { + link_data->discovered = DISCOVERY_UNDISCOVERED; + warnx("clear discovered flag of interface %d", + addr->smctp_ifindex); + } + + // we need to send using physical addressing, no entry in routing table yet + resp->completion_code = MCTP_CTRL_CC_SUCCESS; + return reply_message_phys(ctx, sd, resp, sizeof(*resp), addr); +} + +static int +handle_control_endpoint_discovery(struct ctx *ctx, int sd, + const struct sockaddr_mctp_ext *addr, + const uint8_t *buf, const size_t buf_size) +{ + struct mctp_ctrl_msg_hdr *req = NULL; + struct mctp_ctrl_resp_endpoint_discovery respi = { 0 }, *resp = &respi; + struct link *link_data; + + if (buf_size < sizeof(*req)) { + warnx("short Endpoint Discovery message"); + return -ENOMSG; + } + + link_data = mctp_nl_get_link_userdata(ctx->nl, addr->smctp_ifindex); + if (!link_data) { + bug_warn("unconfigured interface %d", addr->smctp_ifindex); + return -ENOENT; + } + + if (link_data->role == ENDPOINT_ROLE_BUS_OWNER) { + // ignore message if we are bus owner + return 0; + } + + if (link_data->discovered == DISCOVERY_UNSUPPORTED) { + resp->completion_code = MCTP_CTRL_CC_ERROR_INVALID_DATA; + return reply_message(ctx, sd, resp, + sizeof(struct mctp_ctrl_resp), addr); + } + + if (link_data->discovered == DISCOVERY_DISCOVERED) { + // if we are already discovered (i.e, assigned an EID), then no reply + return 0; + } + + req = (void *)buf; + resp = (void *)resp; + mctp_ctrl_msg_hdr_init_resp(&resp->ctrl_hdr, *req); + + // we need to send using physical addressing, no entry in routing table yet + return reply_message_phys(ctx, sd, resp, sizeof(*resp), addr); +} + static int handle_control_unsupported(struct ctx *ctx, int sd, const struct sockaddr_mctp_ext *addr, const uint8_t *buf, const size_t buf_size) @@ -1030,6 +1145,14 @@ static int cb_listen_control_msg(sd_event_source *s, int sd, uint32_t revents, rc = handle_control_resolve_endpoint_id(ctx, sd, &addr, buf, buf_size); break; + case MCTP_CTRL_CMD_PREPARE_ENDPOINT_DISCOVERY: + rc = handle_control_prepare_endpoint_discovery(ctx, sd, &addr, + buf, buf_size); + break; + case MCTP_CTRL_CMD_ENDPOINT_DISCOVERY: + rc = handle_control_endpoint_discovery(ctx, sd, &addr, buf, + buf_size); + break; default: if (ctx->verbose) { warnx("Ignoring unsupported command code 0x%02x", @@ -3905,10 +4028,13 @@ 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; + link->discovered = DISCOVERY_UNSUPPORTED; link->published = false; link->ifindex = ifindex; link->ctx = ctx; @@ -3937,6 +4063,10 @@ static int add_interface(struct ctx *ctx, int ifindex) bus_link_owner_vtable, link); } + if (phys_binding == MCTP_PHYS_BINDING_PCIE_VDM) { + link->discovered = DISCOVERY_UNDISCOVERED; + } + link->published = true; rc = emit_interface_added(link); if (rc < 0) { diff --git a/tests/mctpenv/__init__.py b/tests/mctpenv/__init__.py index 8f82bf96..fdb1d676 100644 --- a/tests/mctpenv/__init__.py +++ b/tests/mctpenv/__init__.py @@ -1,5 +1,6 @@ import array +import enum import errno import os import signal @@ -37,13 +38,27 @@ def to_nlmsg(self, seq = 0): resp['attrs'] = [['NLMSGERR_ATTR_MSG', self.msg]] return resp +class PhysicalBinding(enum.Enum): + UNSPEC = 0x00 + SMBUS = 0x01 + PCIE_VDM = 0x02 + USB = 0x03 + KCS = 0x04 + SERIAL = 0x05 + I3C = 0x06 + MMBI = 0x07 + PCC = 0x08 + UCIE = 0x09 + VENDOR = 0xFF + class System: class Interface: """Interface constructor. Initial mtu is set to max_mtu. """ - def __init__(self, name, ifindex, net, lladdr, min_mtu, max_mtu, up = False): + def __init__(self, name, ifindex, net, lladdr, min_mtu, max_mtu, + up = False, phys_binding = PhysicalBinding.UNSPEC): self.name = name self.ifindex = ifindex self.net = net @@ -52,10 +67,16 @@ def __init__(self, name, ifindex, net, lladdr, min_mtu, max_mtu, up = False): self.max_mtu = max_mtu self.mtu = max_mtu self.up = up + self.phys_binding = phys_binding def __str__(self): lladdrstr = ':'.join('%02x' % b for b in self.lladdr) - return f"{self.name}: net {self.net} lladdr {lladdrstr}" + return ( + f"{self.name}: " + f"net {self.net} " + f"lladdr {lladdrstr} " + f"binding {self.phys_binding.name}" + ) class Address: def __init__(self, iface, eid): @@ -420,6 +441,7 @@ class af_spec_mctp(netlink.nla): nla_map = ( ('IFLA_MCTP_UNSPEC', 'none'), ('IFLA_MCTP_NET', 'uint32'), + ('IFLA_MCTP_PHYS_BINDING', 'uint8'), ) class l2addr(netlink.nla_base): @@ -751,7 +773,13 @@ def _format_link(self, msg, iface): ['IFLA_MAX_MTU', iface.max_mtu], ['IFLA_AF_SPEC', { 'attrs': [['AF_MCTP', { - 'attrs': [['IFLA_MCTP_NET', iface.net]], + 'attrs': [ + ['IFLA_MCTP_NET', iface.net], + [ + 'IFLA_MCTP_PHYS_BINDING', + iface.phys_binding.value, + ], + ], }]], }], ] diff --git a/tests/test_mctpd_endpoint.py b/tests/test_mctpd_endpoint.py index 5c2b125b..82ecf28c 100644 --- a/tests/test_mctpd_endpoint.py +++ b/tests/test_mctpd_endpoint.py @@ -15,10 +15,15 @@ def config(): mode = "endpoint" """ + @pytest.fixture -async def sysnet(): +async def iface(): + return System.Interface("mctp0", 1, 1, bytes([0x1D]), 68, 254, True) + + +@pytest.fixture +async def sysnet(iface): system = System() - iface = System.Interface("mctp0", 1, 1, bytes([0x1D]), 68, 254, True) await system.add_interface(iface) network = Network() network.add_endpoint(Endpoint(iface, bytes([0x10]), eid=8)) @@ -101,3 +106,50 @@ async def test_accept_multiple_set_eids_for_single_interface(dbus, mctpd): # expect new EID on D-Bus assert await mctpd_mctp_endpoint_control_obj(dbus, f"/au/com/codeconstruct/mctp1/networks/1/endpoints/{second_eid}") + + +class TestDiscovery: + @pytest.fixture + async def iface(self): + return System.Interface("mctp0", 1, 1, bytes([0x1D]), 68, 254, True, PhysicalBinding.PCIE_VDM) + + """ Test simple Discovery sequence """ + async def test_simple_discovery_sequence(self, dbus, mctpd): + bo = mctpd.network.endpoints[0] + + assert len(mctpd.system.addresses) == 0 + + # no EID yet + rsp = await bo.send_control(mctpd.network.mctp_socket, MCTPControlCommand(True, 0, 0x02)) + assert rsp.hex(' ') == '00 02 00 00 02 00' + + # BMC response to Prepare for Discovery + rsp = await bo.send_control(mctpd.network.mctp_socket, MCTPControlCommand(True, 0, 0x0B)) + assert rsp.hex(' ') == '00 0b 00' + + # BMC response to Endpoint Discovery + rsp = await bo.send_control(mctpd.network.mctp_socket, MCTPControlCommand(True, 0, 0x0C)) + assert rsp.hex(' ') == '00 0c 00' + + # set EID = 42 + eid = 42 + rsp = await bo.send_control(mctpd.network.mctp_socket, MCTPControlCommand(True, 0, 0x01, bytes([0x00, eid]))) + assert rsp.hex(' ') == f'00 01 00 00 {eid:02x} 00' + + # BMC should contains two object paths: bus owner and itself + assert await mctpd_mctp_endpoint_control_obj(dbus, f"/au/com/codeconstruct/mctp1/networks/1/endpoints/{bo.eid}") + assert await mctpd_mctp_endpoint_control_obj(dbus, f"/au/com/codeconstruct/mctp1/networks/1/endpoints/{eid}") + + +class TestUnsupportedDiscovery: + @pytest.fixture + async def iface(self): + return System.Interface("mctp0", 1, 1, bytes([0x1D]), 68, 254, True, PhysicalBinding.SMBUS) + + """ Discovery command on unsupported interface """ + async def test_simple(self, dbus, mctpd): + bo = mctpd.network.endpoints[0] + + # BMC response ERROR_UNSUPPORTED_CMD to Prepare for Discovery + rsp = await bo.send_control(mctpd.network.mctp_socket, MCTPControlCommand(True, 0, 0x0B)) + assert rsp.hex(' ') == '00 0b 05'