Following on from the MCTP introduction, this document describes one of the newer features of the MCTP stack: extended addressing. This allows utilities to directly address specific physical endpoints; for example, when no endpoint IDs have been assigned.

One note on usage: typical MCTP applications won't need to use this extended addressing facility. This is mostly used for hardware-specific operations, like an MCTP Control Protocol implementation, or MCTP applications that need to deal with endpoint hardware in unusual states.

Standard addressing: struct sockaddr_mctp🔗

The initial MCTP patches defined our socket addressing scheme (struct sockaddr_mctp) as roughly:

struct sockaddr_mctp {
    uint16_t            smctp_family;
    uint32_t            smctp_network;
    struct mctp_addr    smctp_addr;
    uint8_t             smctp_type;
    uint8_t             smctp_tag;
};

(some types have been changed for simplicity here, but this definition will work as-is)

Where you'd typically address a remote peer through its EID, by setting smctp_addr:

    addr.smctp_family = AF_MCTP;
    addr.smctp_addr.s_addr = 8;   /* send to EID 8 */

However, we might have a scenario where the remote endpoint doesn't yet have an EID, and we want to assign one by sending a MCTP Control Protocol message — the MCTP equivalent to the DHCP process for IP. In this case, we don't have an EID to populate smctp_addr, so we will need a different method of addressing this peer.

For this, we have introduced an "extended" addressing facility, allowing physical addresses to be used instead:

Extended addressing: struct sockadr_mctp_ext🔗

The sendto() and recvfrom() calls on a MCTP socket can be passed an alternate addressing scheme: struct sockaddr_mctp_ext. This is defined as:

struct sockaddr_mctp_ext {
    struct sockaddr_mctp    smctp_base;
    int                     smctp_ifindex;
    uint8_t                 smctp_halen;
    uint8_t                 __smctp_pad0[3];
    uint8_t                 smctp_haddr[MAX_ADDR_LEN];
};

Importantly, this structure has an embedded struct sockaddr_mctp as its first member, so the standard addressing fields are available, and are in a compatible layout. This allows a sockaddr_mctp_ext to be converted to a sockaddr_mctp if necessary.

With this new struct, we can provide three new bits of address data:

We can then pass a struct sockaddr_mctp_ext to the recvfrom() and sendto() calls, and retrieve (for recvfrom) or specify (for sendto) the low-level addressing information.

This is a little like the struct in_pktinfo ancillary message used for IPv4/IPv6 datagrams, but instead of being contained in the msg_control data of a struct msghdr, it's available directly in the sockaddr structures.

Using extended addresses is enabled through a new socket option for MCTP: MCTP_OPT_ADDR_EXT, which takes a single int value to enable or disable the option:

    /* 1 to enable extended addressing, 0 to disable */
    int opt = 1

    rc = setsockopt(sd, SOL_MCTP, MCTP_OPT_ADDR_EXT, &opt, sizeof(opt));
    if (rc < 0)
        errx(EXIT_FAILURE,
             "Kernel does not support MCTP extended addressing");

The extended addressing facility was introduced in kernel v5.16. As with any setsockopt call, this will fail if not supported by the kernel.

As a more complete example, to retrieve extended addressing details for incoming messages (assuming our socket (as sd) is already opened and bound to receive):

    struct sockaddr_mctp_ext peer_addr;
    socklen_t peer_addr_len;
    int rc, opt;

    /* enable extended addressing */
    opt = 1;

    rc = setsockopt(sd, SOL_MCTP, MCTP_OPT_ADDR_EXT, &opt, sizeof(opt));
    if (rc < 0) {
        warn("Kernel does not support MCTP extended addressing");
        return -1;
    }

    /* receive a message */
    peer_addr_len = sizeof(peer_addr);
    rc = recvfrom(sd, rxbuf, len, MSG_TRUNC,
                  (struct sockaddr *)&peer_addr, &peer_addr_len);

    /* check that we received OK */
    if (rc < 0)
        return -1;

    /* check that we have an extended address returned */
    if (peer_addr_len < sizeof(peer_addr))
        return -1;

    /* we have info about the incoming MCTP interface... */
    printf("packet received on interface %d\n",
            peer_addr->smctp_ifindex);

    /* ... and the physical address - assuming one-byte
     * addresses (eg i2c) for this example */
    printf("hardware address: 0x%02x\n", peer_addr->smctp_haddr[0]);

With the sockaddr_mctp_ext returned from recvmsg, we have access to specific physical addressing data if necessary, or we can treat it as an opaque structure and pass it back to a sendto() call when replying to the message.

For example, for a reply to a message received above:

    /* clear the tag-owner bit for a reply */
    peer_addr.smctp_base.smctp_tag &= ~MCTP_TAG_OWNER;

    /* send using the original message's physical addressing info */
    rc = sendto(sd, respbuf, resplen, 0,
                  (struct sockaddr *)&peer_addr, sizeof(peer_addr));

This will transmit the message back to the sender's interface and physical address, as passed from the originally-received message.

And that's it! Note that you'll generally be fine using the standard addressing structures for most MCTP-messaging purposes, but the extended addressing may be useful if you need more control over the low-level message parameters.

If you have any questions about extended address, or the MCTP stack on Linux in general, feel free to email me on jk@codeconstruct.com.au.