├── .github └── workflows │ └── pr.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── conf ├── mctp-local.target ├── mctp.target ├── mctpd-dbus.conf ├── mctpd.conf └── mctpd.service ├── docs ├── endpoint-recovery.md └── mctpd.md ├── examples └── tun-forward.sh ├── lib └── tomlc99 │ ├── LICENSE │ ├── README │ ├── toml.c │ └── toml.h ├── meson.build ├── meson_options.txt ├── src ├── mctp-bench.c ├── mctp-control-spec.h ├── mctp-echo.c ├── mctp-netlink.c ├── mctp-netlink.h ├── mctp-ops.c ├── mctp-ops.h ├── mctp-req.c ├── mctp-util.c ├── mctp-util.h ├── mctp.c ├── mctp.h └── mctpd.c └── tests ├── conftest.py ├── mctp-ops-test.c ├── mctp_test_utils.py ├── mctpd └── __init__.py ├── pytest.ini.in ├── requirements.txt ├── test-proto.h ├── test_mctpd.py └── test_mctpd_endpoint.py /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: pull-request-ci 2 | on: [push, pull_request] 3 | jobs: 4 | run-tests: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v4 8 | 9 | - name: Install system build dependencies 10 | run: > 11 | sudo apt-get update && 12 | sudo apt-get install libsystemd-dev ninja-build 13 | 14 | - name: Install meson 15 | run: pip install --user meson 16 | 17 | - name: Install python dependencies for mctp tests 18 | run: pip install --user -r tests/requirements.txt 19 | 20 | - name: Configure mctp build 21 | run: meson setup build -Db_sanitize=address 22 | 23 | - name: Build mctp 24 | run: meson compile -C build 25 | 26 | - name: Test mctp 27 | run: meson test -C build 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 6 | 7 | ## [Unreleased] 8 | 9 | ### Fixed 10 | 11 | 1. Fixed an issue where peer pointers are kept over a potential realloc() 12 | 13 | 2. Netlink handling now handles interface deletion correctly; we no longer 14 | lose sync with the internal linkmap 15 | 16 | ### Added 17 | 18 | 1. New debug tool, `mctp-bench`, for sending and receiving a stream of MCTP 19 | messages between two processes. 20 | 21 | 2. mctpd: Add `au.com.codeconstruct.MCTP.Network1` interface, including 22 | - a `LocalEIDs` property, representing the local EIDs assigned on this 23 | network 24 | - a `LearnEndpoint` method, for enumerating endpoints that already have an 25 | address assigned, and are routable 26 | 27 | 3. tests: the fake mctp environment can now be run standalone, allowing 28 | experimentation with different system and network configurations 29 | 30 | 4. mctpd: Add a `NetworkId` property to the 31 | `au.com.codecontruct.MCTP.Interface1` interface, allowing link-to-network 32 | lookups 33 | 34 | ### Changed 35 | 36 | 1. tests are now run with address sanitizer enabled (-fsanitize=address) 37 | 38 | ### Removed 39 | 40 | 1. mctpd: Test mode (`-N`) has been removed, as we have a more comprehensive 41 | test environment with the python mctpd wrapper code. 42 | 43 | To run using the wrapper: 44 | 45 | (cd obj; python3 ../tests/mctpd/__init__.py) 46 | 47 | 3. mctp-bench, mctp-req, mctp-echo: Message format has changed to use a 48 | vendor-defined message type, rather than MCTP type 1. 49 | 50 | ## [2.1] - 2024-12-16 51 | 52 | ### Fixed 53 | 54 | 1. Fixed build on musl; we were relying on an implicit definition for `AF_MCTP` 55 | 56 | 2. Fixed some header includes where we were previously assuming a glibc layout 57 | 58 | 3. Fixed incorrect setup of peer message type data, where peer endpoints would 59 | report no types supported over dbus. 60 | 61 | 4. Interface objects (MCTP.Interface1) are now under au.com.codeconstruct 62 | prefix, to be consistent with the other interface names. 63 | 64 | ### Changed 65 | 66 | 1. We now enforce IID checks on MCTP control protocol responses; this 67 | prevents odd behaviour from delayed or invalid responses. 68 | 69 | 2. In mctpd the initial route MTU for an endpoint is now set to the minimum MTU 70 | of the interface. This allows better compatibility with devices that 71 | have a low initial allowed packet size and require application negotiation 72 | to increase that packet size. Previously the initial MTU was left as the 73 | interface default (normally the maximum MTU). 74 | The .SetMTU method can be used to set the endpoint route MTU. 75 | 76 | 3. Hardware address formatting has been improved in cases where the address 77 | size is something other than a 1-byte value. 78 | 79 | ## [2.0] - 2024-09-19 80 | 81 | ### Added 82 | 83 | 1. mctpd: Add support for endpoint recovery 84 | 2. mctpd: Allow recovery of devices reporting a nil UUID for development 85 | 3. mctpd: Allow configuring .Connectivity as writable for development 86 | 4. mctpd: Add AssignEndpointStatic for static EID allocations 87 | 5. mctpd: New test infrastructure for control procotol messaging to mctpd 88 | 6. mctpd: Add a configuration file facility, defaulting to /etc/mctpd.conf. 89 | 7. mctpd: Add mctp/interfaces/ D-Bus object 90 | 91 | ### Changed 92 | 93 | 1. dbus interface: the NetworkID field is now a `u` rather than an `i`, to 94 | match OpenBMC's MCTP endpoint specification 95 | 96 | 2. Use Github Actions for CI 97 | 98 | Note that this bumps the meson requirement from >=0.47.0 to >=0.59.0. The 99 | bump allows us to exploit some features helpful for chaining the solution 100 | together. 101 | 102 | 3. The `tests` option has changed type from `feature` to `boolean`. Tests are 103 | enabled by default. 104 | 105 | 4. The dbus interface has undergone a major rework, using standard prefixes 106 | and version interface, bus owner and entry-point object names. See 107 | docs/mctpd.md for full details on the new interface. 108 | 109 | 5. In line with the above, bus-owner related dbus methods (SetupEndpoint and 110 | friends) now exist on the MCTP interface objects, and only when those 111 | interface objects have the bus owner role. Because those methods are 112 | now associated with the interface object, they no longer take the 113 | interface name as their first argument. 114 | 115 | ### Fixed 116 | 117 | 1. mctpd: EID assignments now work in the case where a new endpoint has a 118 | pre-configured EID that would conflict with other (already enumerated) 119 | endpoints. The new endpoint will get a non-conflicting address assigned. 120 | 121 | 2. mctpd: fix incorrect error detection on control socket reads 122 | 123 | ## [1.1] - 2023-04-13 124 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | GNU GENERAL PUBLIC LICENSE 3 | Version 2, June 1991 4 | 5 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 6 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 7 | Everyone is permitted to copy and distribute verbatim copies 8 | of this license document, but changing it is not allowed. 9 | 10 | Preamble 11 | 12 | The licenses for most software are designed to take away your 13 | freedom to share and change it. By contrast, the GNU General Public 14 | License is intended to guarantee your freedom to share and change free 15 | software--to make sure the software is free for all its users. This 16 | General Public License applies to most of the Free Software 17 | Foundation's software and to any other program whose authors commit to 18 | using it. (Some other Free Software Foundation software is covered by 19 | the GNU Lesser General Public License instead.) You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | this service if you wish), that you receive source code or can get it 26 | if you want it, that you can change the software or use pieces of it 27 | in new free programs; and that you know you can do these things. 28 | 29 | To protect your rights, we need to make restrictions that forbid 30 | anyone to deny you these rights or to ask you to surrender the rights. 31 | These restrictions translate to certain responsibilities for you if you 32 | distribute copies of the software, or if you modify it. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must give the recipients all the rights that 36 | you have. You must make sure that they, too, receive or can get the 37 | source code. And you must show them these terms so they know their 38 | rights. 39 | 40 | We protect your rights with two steps: (1) copyright the software, and 41 | (2) offer you this license which gives you legal permission to copy, 42 | distribute and/or modify the software. 43 | 44 | Also, for each author's protection and ours, we want to make certain 45 | that everyone understands that there is no warranty for this free 46 | software. If the software is modified by someone else and passed on, we 47 | want its recipients to know that what they have is not the original, so 48 | that any problems introduced by others will not reflect on the original 49 | authors' reputations. 50 | 51 | Finally, any free program is threatened constantly by software 52 | patents. We wish to avoid the danger that redistributors of a free 53 | program will individually obtain patent licenses, in effect making the 54 | program proprietary. To prevent this, we have made it clear that any 55 | patent must be licensed for everyone's free use or not licensed at all. 56 | 57 | The precise terms and conditions for copying, distribution and 58 | modification follow. 59 | 60 | GNU GENERAL PUBLIC LICENSE 61 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 62 | 63 | 0. This License applies to any program or other work which contains 64 | a notice placed by the copyright holder saying it may be distributed 65 | under the terms of this General Public License. The "Program", below, 66 | refers to any such program or work, and a "work based on the Program" 67 | means either the Program or any derivative work under copyright law: 68 | that is to say, a work containing the Program or a portion of it, 69 | either verbatim or with modifications and/or translated into another 70 | language. (Hereinafter, translation is included without limitation in 71 | the term "modification".) Each licensee is addressed as "you". 72 | 73 | Activities other than copying, distribution and modification are not 74 | covered by this License; they are outside its scope. The act of 75 | running the Program is not restricted, and the output from the Program 76 | is covered only if its contents constitute a work based on the 77 | Program (independent of having been made by running the Program). 78 | Whether that is true depends on what the Program does. 79 | 80 | 1. You may copy and distribute verbatim copies of the Program's 81 | source code as you receive it, in any medium, provided that you 82 | conspicuously and appropriately publish on each copy an appropriate 83 | copyright notice and disclaimer of warranty; keep intact all the 84 | notices that refer to this License and to the absence of any warranty; 85 | and give any other recipients of the Program a copy of this License 86 | along with the Program. 87 | 88 | You may charge a fee for the physical act of transferring a copy, and 89 | you may at your option offer warranty protection in exchange for a fee. 90 | 91 | 2. You may modify your copy or copies of the Program or any portion 92 | of it, thus forming a work based on the Program, and copy and 93 | distribute such modifications or work under the terms of Section 1 94 | above, provided that you also meet all of these conditions: 95 | 96 | a) You must cause the modified files to carry prominent notices 97 | stating that you changed the files and the date of any change. 98 | 99 | b) You must cause any work that you distribute or publish, that in 100 | whole or in part contains or is derived from the Program or any 101 | part thereof, to be licensed as a whole at no charge to all third 102 | parties under the terms of this License. 103 | 104 | c) If the modified program normally reads commands interactively 105 | when run, you must cause it, when started running for such 106 | interactive use in the most ordinary way, to print or display an 107 | announcement including an appropriate copyright notice and a 108 | notice that there is no warranty (or else, saying that you provide 109 | a warranty) and that users may redistribute the program under 110 | these conditions, and telling the user how to view a copy of this 111 | License. (Exception: if the Program itself is interactive but 112 | does not normally print such an announcement, your work based on 113 | the Program is not required to print an announcement.) 114 | 115 | These requirements apply to the modified work as a whole. If 116 | identifiable sections of that work are not derived from the Program, 117 | and can be reasonably considered independent and separate works in 118 | themselves, then this License, and its terms, do not apply to those 119 | sections when you distribute them as separate works. But when you 120 | distribute the same sections as part of a whole which is a work based 121 | on the Program, the distribution of the whole must be on the terms of 122 | this License, whose permissions for other licensees extend to the 123 | entire whole, and thus to each and every part regardless of who wrote it. 124 | 125 | Thus, it is not the intent of this section to claim rights or contest 126 | your rights to work written entirely by you; rather, the intent is to 127 | exercise the right to control the distribution of derivative or 128 | collective works based on the Program. 129 | 130 | In addition, mere aggregation of another work not based on the Program 131 | with the Program (or with a work based on the Program) on a volume of 132 | a storage or distribution medium does not bring the other work under 133 | the scope of this License. 134 | 135 | 3. You may copy and distribute the Program (or a work based on it, 136 | under Section 2) in object code or executable form under the terms of 137 | Sections 1 and 2 above provided that you also do one of the following: 138 | 139 | a) Accompany it with the complete corresponding machine-readable 140 | source code, which must be distributed under the terms of Sections 141 | 1 and 2 above on a medium customarily used for software interchange; or, 142 | 143 | b) Accompany it with a written offer, valid for at least three 144 | years, to give any third party, for a charge no more than your 145 | cost of physically performing source distribution, a complete 146 | machine-readable copy of the corresponding source code, to be 147 | distributed under the terms of Sections 1 and 2 above on a medium 148 | customarily used for software interchange; or, 149 | 150 | c) Accompany it with the information you received as to the offer 151 | to distribute corresponding source code. (This alternative is 152 | allowed only for noncommercial distribution and only if you 153 | received the program in object code or executable form with such 154 | an offer, in accord with Subsection b above.) 155 | 156 | The source code for a work means the preferred form of the work for 157 | making modifications to it. For an executable work, complete source 158 | code means all the source code for all modules it contains, plus any 159 | associated interface definition files, plus the scripts used to 160 | control compilation and installation of the executable. However, as a 161 | special exception, the source code distributed need not include 162 | anything that is normally distributed (in either source or binary 163 | form) with the major components (compiler, kernel, and so on) of the 164 | operating system on which the executable runs, unless that component 165 | itself accompanies the executable. 166 | 167 | If distribution of executable or object code is made by offering 168 | access to copy from a designated place, then offering equivalent 169 | access to copy the source code from the same place counts as 170 | distribution of the source code, even though third parties are not 171 | compelled to copy the source along with the object code. 172 | 173 | 4. You may not copy, modify, sublicense, or distribute the Program 174 | except as expressly provided under this License. Any attempt 175 | otherwise to copy, modify, sublicense or distribute the Program is 176 | void, and will automatically terminate your rights under this License. 177 | However, parties who have received copies, or rights, from you under 178 | this License will not have their licenses terminated so long as such 179 | parties remain in full compliance. 180 | 181 | 5. You are not required to accept this License, since you have not 182 | signed it. However, nothing else grants you permission to modify or 183 | distribute the Program or its derivative works. These actions are 184 | prohibited by law if you do not accept this License. Therefore, by 185 | modifying or distributing the Program (or any work based on the 186 | Program), you indicate your acceptance of this License to do so, and 187 | all its terms and conditions for copying, distributing or modifying 188 | the Program or works based on it. 189 | 190 | 6. Each time you redistribute the Program (or any work based on the 191 | Program), the recipient automatically receives a license from the 192 | original licensor to copy, distribute or modify the Program subject to 193 | these terms and conditions. You may not impose any further 194 | restrictions on the recipients' exercise of the rights granted herein. 195 | You are not responsible for enforcing compliance by third parties to 196 | this License. 197 | 198 | 7. If, as a consequence of a court judgment or allegation of patent 199 | infringement or for any other reason (not limited to patent issues), 200 | conditions are imposed on you (whether by court order, agreement or 201 | otherwise) that contradict the conditions of this License, they do not 202 | excuse you from the conditions of this License. If you cannot 203 | distribute so as to satisfy simultaneously your obligations under this 204 | License and any other pertinent obligations, then as a consequence you 205 | may not distribute the Program at all. For example, if a patent 206 | license would not permit royalty-free redistribution of the Program by 207 | all those who receive copies directly or indirectly through you, then 208 | the only way you could satisfy both it and this License would be to 209 | refrain entirely from distribution of the Program. 210 | 211 | If any portion of this section is held invalid or unenforceable under 212 | any particular circumstance, the balance of the section is intended to 213 | apply and the section as a whole is intended to apply in other 214 | circumstances. 215 | 216 | It is not the purpose of this section to induce you to infringe any 217 | patents or other property right claims or to contest validity of any 218 | such claims; this section has the sole purpose of protecting the 219 | integrity of the free software distribution system, which is 220 | implemented by public license practices. Many people have made 221 | generous contributions to the wide range of software distributed 222 | through that system in reliance on consistent application of that 223 | system; it is up to the author/donor to decide if he or she is willing 224 | to distribute software through any other system and a licensee cannot 225 | impose that choice. 226 | 227 | This section is intended to make thoroughly clear what is believed to 228 | be a consequence of the rest of this License. 229 | 230 | 8. If the distribution and/or use of the Program is restricted in 231 | certain countries either by patents or by copyrighted interfaces, the 232 | original copyright holder who places the Program under this License 233 | may add an explicit geographical distribution limitation excluding 234 | those countries, so that distribution is permitted only in or among 235 | countries not thus excluded. In such case, this License incorporates 236 | the limitation as if written in the body of this License. 237 | 238 | 9. The Free Software Foundation may publish revised and/or new versions 239 | of the General Public License from time to time. Such new versions will 240 | be similar in spirit to the present version, but may differ in detail to 241 | address new problems or concerns. 242 | 243 | Each version is given a distinguishing version number. If the Program 244 | specifies a version number of this License which applies to it and "any 245 | later version", you have the option of following the terms and conditions 246 | either of that version or of any later version published by the Free 247 | Software Foundation. If the Program does not specify a version number of 248 | this License, you may choose any version ever published by the Free Software 249 | Foundation. 250 | 251 | 10. If you wish to incorporate parts of the Program into other free 252 | programs whose distribution conditions are different, write to the author 253 | to ask for permission. For software which is copyrighted by the Free 254 | Software Foundation, write to the Free Software Foundation; we sometimes 255 | make exceptions for this. Our decision will be guided by the two goals 256 | of preserving the free status of all derivatives of our free software and 257 | of promoting the sharing and reuse of software generally. 258 | 259 | NO WARRANTY 260 | 261 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 262 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 263 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 264 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 265 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 266 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 267 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 268 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 269 | REPAIR OR CORRECTION. 270 | 271 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 272 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 273 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 274 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 275 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 276 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 277 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 278 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 279 | POSSIBILITY OF SUCH DAMAGES. 280 | 281 | END OF TERMS AND CONDITIONS 282 | 283 | How to Apply These Terms to Your New Programs 284 | 285 | If you develop a new program, and you want it to be of the greatest 286 | possible use to the public, the best way to achieve this is to make it 287 | free software which everyone can redistribute and change under these terms. 288 | 289 | To do so, attach the following notices to the program. It is safest 290 | to attach them to the start of each source file to most effectively 291 | convey the exclusion of warranty; and each file should have at least 292 | the "copyright" line and a pointer to where the full notice is found. 293 | 294 | 295 | Copyright (C) 296 | 297 | This program is free software; you can redistribute it and/or modify 298 | it under the terms of the GNU General Public License as published by 299 | the Free Software Foundation; either version 2 of the License, or 300 | (at your option) any later version. 301 | 302 | This program is distributed in the hope that it will be useful, 303 | but WITHOUT ANY WARRANTY; without even the implied warranty of 304 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 305 | GNU General Public License for more details. 306 | 307 | You should have received a copy of the GNU General Public License along 308 | with this program; if not, write to the Free Software Foundation, Inc., 309 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Lesser General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mctp: Userspace tools for MCTP stack management 2 | =============================================== 3 | 4 | This project contains two utilities for running a MCTP network from the local 5 | machine: 6 | 7 | - `mctp`: A small command-line utility to query and manage the state of the 8 | kernel MCTP stack, in a similar way to iproute2's `ip` utility. 9 | 10 | - `mctpd`: A daemon implementing the MCTP control protocol; you'll need this 11 | for the local host to perform as a bus-owner. The main function of `mctpd` 12 | is to assign EIDs to remote endpoints, and manage the resulting routes and 13 | neighbour-table entries for those endpoints. 14 | 15 | Building & installing 16 | --------------------- 17 | 18 | This project uses meson for building. To configure and compile: 19 | 20 | $ meson setup obj 21 | $ ninja -C obj 22 | 23 | to install to the default prefix (/usr/local), with optional `DESTDIR`: 24 | 25 | $ meson install -C obj 26 | 27 | For integration with systemd, there are a few example configuration files and 28 | systemd target definitions under the `conf/` directory. These are not installed 29 | by default. 30 | 31 | By default, `meson` is configured to enable tests, which requires a few extra 32 | dependencies (mainly `pytest`, python libraries, and `dbus-run-session`). In 33 | cases where the tests are not required, you can avoid these dependencies by 34 | configuring the build tree with `-Dtests=false`: 35 | 36 | $ meson setup obj -Dtests=false 37 | 38 | `mctp` Usage 39 | ------------- 40 | 41 | Use `mctp help` for the list of available commands: 42 | 43 | $ mctp help 44 | mctp link 45 | mctp link show [ifname] 46 | mctp link set [up|down] [mtu ] [network ] [bus-owner ] 47 | 48 | mctp address 49 | mctp address show [IFNAME] 50 | mctp address add dev 51 | mctp address del dev 52 | 53 | mctp route 54 | mctp route show [net ] 55 | mctp route add via [mtu ] 56 | mctp route del via 57 | 58 | mctp neigh 59 | mctp neigh show [dev ] 60 | mctp neigh add dev lladdr 61 | mctp neigh del dev 62 | 63 | `mctpd` Usage 64 | ------------- 65 | 66 | `mctpd` should be run as a system service, once the local MCTP stack has been 67 | configured (ie., interfaces are enabled, and local addresses have been 68 | assigned). There are two sample systemd unit files under the conf/ directory, to 69 | coordinate the local setup and the supervision of the mctpd process. 70 | 71 | `mctpd` can read some basic configuration from a file, by default 72 | `/etc/mctpd.conf`, but other files can be specified with the `-c FILE` argument. 73 | An example configuration is in [`conf/mctpd.conf`](conf/mctpd.conf). 74 | 75 | The `mctpd` daemon will expose a dbus interface, claiming the bus name 76 | `au.com.codeconstruct.MCTP1` and object path `/au/com/codeconstruct/mctp1`. 77 | 78 | Each detected MCTP interface on the system provides a few functions for 79 | configuring remote endpoints on that bus: 80 | 81 | # busctl introspect au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1/interfaces/mctpi2c1 82 | NAME TYPE SIGNATURE RESULT/VALUE FLAGS 83 | au.com.codeconstruct.MCTP.Interface1 interface - - - 84 | .AssignEndpoint method ay yisb - 85 | .AssignEndpointStatic method ayy yisb - 86 | .LearnEndpoint method ay yisb - 87 | .SetupEndpoint method ay yisb - 88 | 89 | Results of mctpd enumeration are also represented as dbus objects, using the 90 | OpenBMC-specified MCTP endpoint format. Each endpoint appears on the bus at the 91 | object path: 92 | 93 | /au/com/codeconstruct/mctp/networks//endpoints/ 94 | 95 | where `mctpd` exposes three dbus interfaces for each: 96 | 97 | - `xyz.openbmc_project.MCTP.Endpoint`: Provides MCTP address information 98 | (`EID` and `NetworkID` properties) and message-type support 99 | `SupportedMessageTypes` property). 100 | 101 | This interface is defined by the MCTP.Endpoint phosphor-dbus specification. 102 | 103 | - `xyz.openbmc_project.Common.UUID`: MCTP UUID of the discovered endpoint 104 | (`UUID` property). 105 | 106 | This interface is defined by the Common.UUID phosphor-dbus specification. 107 | 108 | - `au.com.codeconstruct.MCTP1.Endpoint1`: Additional control methods for the 109 | endpoint - for example, `Remove` 110 | 111 | Testing 112 | ------- 113 | 114 | We have an initial test suite under tests/. To run: 115 | 116 | ```sh 117 | meson setup obj 118 | ninja -C obj test 119 | ``` 120 | 121 | Alternatively, you can run `pytest` directly; this may be more useful 122 | during development: 123 | 124 | ```sh 125 | cd obj 126 | pytest 127 | ``` 128 | 129 | To run without an existing dbus session: 130 | ```sh 131 | dbus-run-session env DBUS_STARTER_BUS_TYPE=user pytest 132 | ``` 133 | 134 | The test infrastructure depends on a few python packages, including the pytest 135 | binary. You can use a python `venv` to provide these: 136 | 137 | ```sh 138 | python3 -m venv venv 139 | venv/bin/pip install -r tests/requirements.txt 140 | ``` 141 | 142 | Then run the tests using the new `venv`'s `pytest`: 143 | 144 | ```sh 145 | PATH=$PWD/venv/bin/:$PATH meson setup obj 146 | ninja -C obj test 147 | ``` 148 | -------------------------------------------------------------------------------- /conf/mctp-local.target: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=MCTP local network configuration 3 | -------------------------------------------------------------------------------- /conf/mctp.target: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=MCTP infrastructure active 3 | Wants=mctp-local.target 4 | 5 | [Install] 6 | WantedBy=multi-user.target 7 | -------------------------------------------------------------------------------- /conf/mctpd-dbus.conf: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /conf/mctpd.conf: -------------------------------------------------------------------------------- 1 | # Mode: either bus-owner or endpoint or unknown 2 | mode = "bus-owner" 3 | 4 | # MCTP protocol configuration. Used for both endpoint and bus-owner modes. 5 | [mctp] 6 | message_timeout_ms = 30 7 | 8 | # Specify a UUID: not generally required - mctpd will query the system UUID 9 | # where available. 10 | # uuid = "21f0f554-7f7c-4211-9ca1-6d0f000ea9e7" 11 | -------------------------------------------------------------------------------- /conf/mctpd.service: -------------------------------------------------------------------------------- 1 | 2 | [Unit] 3 | Description=MCTP control protocol daemon 4 | Wants=mctp-local.target 5 | After=mctp-local.target 6 | 7 | [Service] 8 | Type=dbus 9 | BusName=au.com.codeconstruct.MCTP1 10 | ExecStart=/usr/sbin/mctpd 11 | 12 | [Install] 13 | WantedBy=mctp.target 14 | -------------------------------------------------------------------------------- /docs/endpoint-recovery.md: -------------------------------------------------------------------------------- 1 | # Endpoint Recovery 2 | 3 | 4 | - [Endpoint Recovery](#endpoint-recovery) 5 | - [References](#references) 6 | - [Relevant components](#relevant-components) 7 | - [Concerns](#concerns) 8 | - [Behaviours](#behaviours) 9 | - [Relevant MCTP commands, behaviours, and requirement constraints](#relevant-mctp-commands-behaviours-and-requirement-constraints) 10 | - [Requesting endpoint recovery from `mctpd`](#requesting-endpoint-recovery-from-mctpd) 11 | - [Endpoint health determinations and consequences](#endpoint-health-determinations-and-consequences) 12 | - [D-Bus interface discussion](#d-bus-interface-discussion) 13 | - [Proposed Design](#proposed-design) 14 | - [`.Recover` method design considerations](#recover-method-design-considerations) 15 | - [Application behaviours over an endpoint recovery sequence](#application-behaviours-over-an-endpoint-recovery-sequence) 16 | - [`mctpd` behaviours over an endpoint recovery sequence](#mctpd-behaviours-over-an-endpoint-recovery-sequence) 17 | - [MCTP device lifecycle and consuming events from `mctpd` D-Bus objects](#mctp-device-lifecycle-and-consuming-events-from-mctpd-d-bus-objects) 18 | - [An example order of MCTP device events, consumed by `nvmesensor`](#an-example-order-of-mctp-device-events-consumed-by-nvmesensor) 19 | 20 | 21 | Here we capture some reasoning around a design and implementation of device 22 | presence detection and EID reclaim. 23 | 24 | ## References 25 | 26 | 1. [DSP0236 - Management Component Transport Protocol (MCTP) Base Specification][dmtf-dsp0236] 27 | 2. [DSP0237 - Management Component Transport Protocol (MCTP) SMBus/I2C Transport Binding Specification][dmtf-dsp0237] 28 | 3. [DSP0239 - Management Component Transport Protocol (MCTP) IDs and Codes][dmtf-dsp0239] 29 | 3. [Freedesktop: D-Bus Specification][dbus-spec] 30 | 31 | [dmtf-dsp0236]: https://www.dmtf.org/sites/default/files/standards/documents/DSP0236_1.3.1.pdf 32 | [dmtf-dsp0237]: https://www.dmtf.org/sites/default/files/standards/documents/DSP0237_1.2.0.pdf 33 | [dmtf-dsp0239]: https://www.dmtf.org/sites/default/files/standards/documents/DSP0239_1.10.0.pdf 34 | [dbus-spec]: https://dbus.freedesktop.org/doc/dbus-specification.html 35 | 36 | ## Relevant components 37 | 38 | 1. Endpoint devices, such as NVMe drives 39 | 2. I2C buses 40 | 3. `fru-device` 41 | 4. `entity-manager` 42 | 5. Client applications, such as `nvmesensor` 43 | 6. `mctpd` 44 | 45 | ## Concerns 46 | 47 | It's reasonable for clients like `nvmesensor` to identify issues with the NVMe 48 | subsystem, but it's possible that communication errors could be a result of 49 | issues on the underlying transport. 50 | 51 | We try to place the detection and recovery effort at the appropriate layer in 52 | the communication stack. 53 | 54 | Concretely, `nvmesensor` periodically issues a `Controller Health Status Poll` 55 | to monitored drives. Polling may fail due to issues with the subsystem or due to 56 | more fundamental issues like the endpoint being removed from the bus. In the 57 | cases where `nvmesensor` determines it shouldn't own the problem of monitoring 58 | for resumed communications it should punt the polling to `mctpd`. 59 | 60 | ## Behaviours 61 | 62 | Reasons for temporary loss of e.g. NVMe-MI communication at the MCTP layer 63 | include: 64 | 65 | 1. I2C bus recovery by the controller 66 | 2. I2C bus contention and loss of arbitration 67 | 3. Transparent MCTP proxy hardware dropping communication 68 | 4. Hot-(un)plug of the endpoint device 69 | 70 | ## Relevant MCTP commands, behaviours, and requirement constraints 71 | 72 | 1. Use of `Get Endpoint ID` for polling 73 | 2. Use of `Get Endpoint UUID` to track device exchange 74 | 75 | Substatiating 1, from [DSP0236 v1.3.1 8.17.6 Reclaiming EIDs from hot-plug 76 | devices][dmtf-dsp0236] we have: 77 | 78 | > - The bus owner shall wait at least `Treclaim` seconds before reassigning a 79 | > given EID (where `Treclaim` is specified in the physical transport binding 80 | > specification for the medium used to access the endpoint). 81 | > 82 | > - Reclaimed EIDs shall only be reassigned after all unused EIDs in the EID 83 | > pool have been assigned to endpoints. Optionally, additional robustness 84 | > can be achieved if the bus owner maintains a short FIFO list of reclaimed 85 | > EIDs (and their associated physical addresses) and allocates the older EIDs 86 | > first. 87 | > 88 | > - A bus owner shall confirm that an endpoint has been removed by attempting 89 | > to access it after `Treclaim` has expired. It can do this by issuing a `Get 90 | > Endpoint ID` command to the endpoint to verify that the endpoint is still 91 | > non-responsive. It is recommended that this be done at least three times, 92 | > with a delay of at least `1/2 * Treclaim` between tries if possible. If the 93 | > endpoint continues to be non-responsive, it can be assumed that it is safe 94 | > to return its EID to the pool of EIDs available for assignment. 95 | 96 | We're concerned with the `Treclaim` relevant to NVMe-MI devices for now, which 97 | leads us to DSP0237. [DSP0237 v1.2.0 6.19][dmtf-dsp0237] defines `Treclaim` as 5 98 | seconds. The same section also defines `MN1` (Number of request retries) with a 99 | minimum of `2`. A retry is specifically a transmission attempt after the initial 100 | transmission, so an `MN1` minimum of 2 implies at least three messages will be 101 | sent, in line with the suggestion from DSP0236. 102 | 103 | ## Requesting endpoint recovery from `mctpd` 104 | 105 | We wish to avoid continuous polling of devices by `mctpd` for a few reasons: 106 | 107 | 1. Interleaving of commands to simple endpoints 108 | 2. The increase bus utilisation increases the probability of contention and loss 109 | of arbitration 110 | 111 | Given this, `nvmesensor` and any other daemons using MCTP as a transport need 112 | some way to request polling be performed. `mctpd` already exposes D-Bus APIs for 113 | endpoint management - we will add another for this purpose. 114 | 115 | ## Endpoint health determinations and consequences 116 | 117 | There are several possible outcomes upon an application such as `nvmesensor` 118 | requesting `mctpd` recover communication with an endpoint: 119 | 120 | 1. `mctpd` issues `Get Endpoint ID` to the device and finds no sign of failure - 121 | the device responds without requiring a retry. 122 | 123 | 2. `mctpd` issues `Get Endpoint ID` to the device and initially finds it 124 | unresponsive, but the device recovers and responds to a retry attempt before 125 | `Treclaim`. 126 | 127 | 3. `mctpd` issues `Get Endpoint ID` to the device. The device fails to respond 128 | to the initial query and all subsequent retries up to `Treclaim`. 129 | 130 | In the case of the third scenario MCTP defines the device as removed until some 131 | later event re-establishes its presence. If a device is considered removed then 132 | the D-Bus object representing the device must also be removed. 133 | 134 | Conversely, in scenarios 1 and 2 the device should not be considered removed 135 | as communication was re-established prior to `Treclaim`. Until `Treclaim` the 136 | device exists in an intermediate state where it's expected to be present but 137 | its presence is undetermined. As such, its D-Bus object should also remain, but 138 | we need to acknowledge to interested applications that there are communication 139 | issues. This may include applications in addition to the process which requested 140 | `mctpd` recover communication with the endpoint. 141 | 142 | ## D-Bus interface discussion 143 | 144 | `mctpd` currently structures its D-Bus objects as follows: 145 | 146 | ``` 147 | root@cc-nvme-mi:~# busctl tree au.com.codeconstruct.MCTP1 148 | └─/au 149 | └─/au/com 150 | └─/au/com/codeconstruct 151 | └─/au/com/codeconstruct/mctp1 152 | ├─/au/com/codeconstruct/mctp1/interfaces/mctpi2c0 153 | ├─/au/com/codeconstruct/mctp1/networks/1 154 | │ └─/au/com/codeconstruct/mctp1/networks/1/endpoints/12 155 | ├─/au/com/codeconstruct/mctp1/networks/1/endpoints/8 156 | └─/au/com/codeconstruct/mctp1/networks/1/endpoints/9 157 | ``` 158 | 159 | ``` 160 | root@cc-nvme-mi:~# busctl introspect au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1 161 | NAME TYPE SIGNATURE RESULT/VALUE FLAGS 162 | org.freedesktop.DBus.Introspectable interface - - - 163 | .Introspect method - s - 164 | org.freedesktop.DBus.ObjectManager interface - - - 165 | .GetManagedObjects method - a{oa{sa{sv}}} - 166 | .InterfacesAdded signal oa{sa{sv}} - - 167 | .InterfacesRemoved signal oas - - 168 | org.freedesktop.DBus.Peer interface - - - 169 | .GetMachineId method - s - 170 | .Ping method - - - 171 | org.freedesktop.DBus.Properties interface - - - 172 | .Get method ss v - 173 | .GetAll method s a{sv} - 174 | .Set method ssv - - 175 | .PropertiesChanged signal sa{sv}as - - 176 | ``` 177 | 178 | ``` 179 | root@cc-nvme-mi:~# busctl introspect au.com.codeconstruct.MCTP1 /au/com/codeconstruct/mctp1/interfaces/mctpi2c0 180 | NAME TYPE SIGNATURE RESULT/VALUE FLAGS 181 | au.com.codeconstruct.Interface1 interface - - - 182 | .AssignEndpoint method ay yisb - 183 | .LearnEndpoint method ay yisb - 184 | .SetupEndpoint method ay yisb - 185 | org.freedesktop.DBus.Introspectable interface - - - 186 | .Introspect method - s - 187 | org.freedesktop.DBus.Peer interface - - - 188 | .GetMachineId method - s - 189 | .Ping method - - - 190 | org.freedesktop.DBus.Properties interface - - - 191 | .Get method ss v - 192 | .GetAll method s a{sv} - 193 | .Set method ssv - - 194 | .PropertiesChanged signal sa{sv}as - - 195 | ``` 196 | 197 | ``` 198 | root@cc-nvme-mi:~# busctl introspect au.com.codeconstruct.MCTP1 /au/com/codecontrust/mctp1/networks/1 199 | NAME TYPE SIGNATURE RESULT/VALUE FLAGS 200 | org.freedesktop.DBus.Introspectable interface - - - 201 | .Introspect method - s - 202 | org.freedesktop.DBus.Peer interface - - - 203 | .GetMachineId method - s - 204 | .Ping method - - - 205 | org.freedesktop.DBus.Properties interface - - - 206 | .Get method ss v - 207 | .GetAll method s a{sv} - 208 | .Set method ssv - - 209 | .PropertiesChanged signal sa{sv}as - - 210 | ``` 211 | 212 | ``` 213 | root@cc-nvme-mi:~# busctl introspect au.com.codeconstruct.MCTP1 /au/com/codecontrust/mctp1/networks/1/endpoints/9 214 | NAME TYPE SIGNATURE RESULT/VALUE FLAGS 215 | au.com.codeconstruct.MCTP.Endpoint1 interface - - - 216 | .Remove method - - - 217 | .SetMTU method u - - 218 | org.freedesktop.DBus.Introspectable interface - - - 219 | .Introspect method - s - 220 | org.freedesktop.DBus.Peer interface - - - 221 | .GetMachineId method - s - 222 | .Ping method - - - 223 | org.freedesktop.DBus.Properties interface - - - 224 | .Get method ss v - 225 | .GetAll method s a{sv} - 226 | .Set method ssv - - 227 | .PropertiesChanged signal sa{sv}as - - 228 | xyz.openbmc_project.Common.UUID interface - - - 229 | .UUID property s "595a522d-77c5-4628-8400-37bfb41b710c" const 230 | xyz.openbmc_project.MCTP.Endpoint interface - - - 231 | .EID property y 9 const 232 | .NetworkId property i 1 const 233 | .SupportedMessageTypes property ay 1 0 const 234 | ``` 235 | 236 | The problem we have deals with specific endpoints. To satisfy the third scenario 237 | the object `/au/com/codeconstruct/mctp1/networks/1/endpoints/9` would be removed. 238 | 239 | For scenarios 1 and 2 we need to add capabilities on the endpoint object. The 240 | `au.com.codeconstruct.MCTP.Endpoint` already exposes endpoint control APIs, and 241 | given we're not removing capabilities we can change it. 242 | 243 | ## Proposed Design 244 | 245 | The approach is to add a `.Recover` method and a `.Connectivity` property to the 246 | `au.com.codeconstruct.MCTP.Endpoint1` interface. `.Recover` takes no arguments, 247 | produces no result, and returns immediately. `.Connectivity` takes one of two 248 | values: 249 | 250 | - `Available` 251 | - `Degraded` 252 | 253 | When `.Recover` is invoked `mctpd` transitions `.Connectivity` to `Degraded`, 254 | then queries the device with `Get Endpoint ID`. `.Recover` responds on D-Bus 255 | after `Get Endpoint ID` has been issued and before a response is received. 256 | A valid response received from a `Get Endpoint ID` query transmitted inside 257 | `Treclaim` leads to `.Connectivity` transitioning to `Available`. If a response 258 | is not received to a `Get Endpoint ID` query issued inside `Treclaim` then 259 | `mctpd` removes the endpoint's D-Bus object. 260 | 261 | ```mermaid 262 | stateDiagram-v2 263 | [*] --> Available 264 | Available --> Degraded 265 | Degraded --> Available 266 | Degraded --> [*] 267 | ``` 268 | 269 | ### `.Recover` method design considerations 270 | 271 | There are several possible design points for `.Recover`: 272 | 273 | 1. Withold a response until communication with the device is recovered or the 274 | device is considered removed after `Treclaim`, and respond with the overall 275 | result 276 | 277 | 2. Withold a response until the first `Get Endpoint ID` query has resolved, 278 | either via a timely response or via a timeout, and respond with the result of 279 | this first query 280 | 281 | 3. Immediately respond prior to resolving the first `Get Endpoint ID query` 282 | without providing a result 283 | 284 | Option 1 is not feasible for two reasons: 285 | 286 | - DSP0236 punts definition of `Treclaim` to the transport binding 287 | specifications, and 288 | 289 | - D-Bus punts the entire concept of method call timeouts to the 290 | implementation[^1], which are often configurable 291 | 292 | [^1]: As best I can see there's no definition of timeouts in the [method call 293 | section of the specification][dbus-spec-message-protocol-types-method]. 294 | [`4a85d321b451`][dbus-reference-timeout-commit] is the first introduction 295 | of the concept of method call timeouts in the D-Bus reference 296 | implementation, back in 2003. 297 | 298 | [dbus-spec-message-protocol-types-method]: https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-types-method 299 | [dbus-reference-timeout-commit]: https://gitlab.freedesktop.org/dbus/dbus/-/commit/4a85d321b4516c6a663e8bdd530ba59018c974df 300 | 301 | As we can't assume appropriate values for either of those behaviours we should 302 | design an interfaces that avoids putting constraints on them. This drives 303 | introducing the `.Connectivity` member as an asynchronous signal of recovery 304 | progress. 305 | 306 | Option 3 requires we rely entirely on signals from `.Connectivity` to drive 307 | a state machine in interested applications. This may feel heavy-handed and 308 | thus tempting to implement option 2 for the happy case where recovery 309 | immediately succeeds. However, option 2 requires interested applications 310 | implement two separate code paths to handle recovery, which seems questionable 311 | from the perspective of correctness, robustness and maintenance. 312 | 313 | ### Application behaviours over an endpoint recovery sequence 314 | 315 | The impact of changes to `.Connectivity` can be divided across two classes of 316 | application: 317 | 318 | 1. The application invoking `.Recover` on a given endpoint `A` 319 | 2. Applications communicating with `A` that have not entered a state where 320 | recovery was considered necessary 321 | 322 | For an application to invoke `.Recover` it must already consider the endpoint 323 | unresponsive, therefore it seems reasonable to assume it won't continue 324 | communicating with the endpoint unless the recovery succeeds. As such there's 325 | no action required when `.Connectivity` transitions to `Degraded`. However, 326 | if recovery succeeds, the transition of `.Connectivity` from `Degraded` to 327 | `Available` provides the signal to restart communicating with the endpoint. 328 | 329 | For applications in the second class it is likely the case that they 330 | haven't themselves invoked `.Recover` because they are yet to observe a 331 | communication failure with the endpoint. As such there's also no requirement 332 | for action when `.Connectivity` transitions to `Degraded` on behalf of another 333 | application. However, it may be the case that communication failures are 334 | subsequently observed. It's not necessary to invoke `.Recover` if 335 | `.Connectivity` is already in `Degraded`, though there should also be no 336 | significant consequences if it occurs. If no communication failures are observed 337 | while `.Connectivity` is in `Degraded` then there's also no action required if 338 | it transitions to `Available`. 339 | 340 | ### `mctpd` behaviours over an endpoint recovery sequence 341 | 342 | For hotplug devices recovering from a reset `Get Endpoint ID` should yield an 343 | EID of 0. 344 | 345 | Note that while we're under the `Treclaim` period the device's allocated EID has 346 | not been quarantined to the reuse FIFO, and is also not a member of the dynamic 347 | allocation pool. For the purposes of allocation, the device is still assigned 348 | the EID it had prior to taking a reset. If a device at the physical address of 349 | the device in `Degraded` responds to `Get Endpoint ID` with an EID of 0 inside 350 | the `Treclaim` period and has a UUID that matches the device in `Degraded`, then 351 | it should be reassigned its allocated EID. At the end of this sequence the 352 | reassignment should not be observable to MCTP-application-layer beyond the 353 | endpoint's `.Connectivity` member transitioning from `Degraded` to `Available`. 354 | 355 | ## MCTP device lifecycle and consuming events from `mctpd` D-Bus objects 356 | 357 | The general strategy for tracking endpoint lifecycles is [as follows] 358 | [dbus-spec-standard-interfaces-objectmanager]: 359 | 360 | [dbus-spec-standard-interfaces-objectmanager]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager 361 | 362 | 1. Add a match of the following form for event-driven updates to local 363 | data-structures 364 | 365 | ``` 366 | type='signal',sender='xyz.openbmc_project.MCTP',path_namespace='/au/com/codeconstruct/mctp1' 367 | ``` 368 | 369 | 2. Issue a call to `GetManagedObjects` `org.freedesktop.DBus.ObjectManager` on 370 | `/au/com/codeconstruct/mctp1` for initial population of local data-structures 371 | 372 | ### An example order of MCTP device events, consumed by `nvmesensor` 373 | 374 | 1. A series of events yields a new FRU EEPROM device on an I2C bus already 375 | registered as an MCTP endpoint. 376 | 377 | 2. The FRU data is decoded and `SetupEndpoint` on the 378 | `au.com.codeconstruct.MCTP.BusOwner1` interface of the 379 | `/au/com/codeconstruct/mctp1/interfaces/` 380 | object hosted by `mctpd` is invoked. 381 | 382 | 3. The device was not previously configured and has no programmed static EID. 383 | `mctpd` allocates an EID (9) from its dynamic pool and assigns it to the 384 | device. 385 | 386 | 4. `mctpd` [emits an `InterfacesAdded` signal][dbus-spec-standard-interfaces-objectmanager] 387 | with the following contents (represented as JSON, trimmed to relevant 388 | interfaces): 389 | 390 | ``` 391 | "/au.com.codeconstruct/mctp1/networks/1/endpoints/9": { 392 | "au.com.codeconstruct.MCTP.Endpoint1": { }, 393 | "xyz.openbmc_project.Common.UUID": { 394 | "UUID": "..." 395 | }, 396 | "xyz.openbmc_project.MCTP.Endpoint": { 397 | "EID": 9, 398 | "NetworkId": 1, 399 | "SupportedMessageTypes": [ 0, 4 ], 400 | } 401 | } 402 | ``` 403 | 404 | 5. The device indicates support for message type `4`, ["NVM Express Management 405 | Messages over MCTP"][dmtf-dsp0239]. `nvmesensor` updates its local data-structures to 406 | capture the appearance of a new NVMe device. 407 | 408 | 6. `nvmesensor` begins managing the NVMe device via NVMe-MI. 409 | 410 | 7. MCTP endpoint 9 stops responding to NVMe-MI commands from `nvmesensor`. 411 | 412 | 8. `nvmesensor` calls the `Recover` method on the 413 | `au.com.codeconstruct.MCTP.Endpoint1` of the 414 | `/au/com/codeconstruct/mctp1/networks/1/endpoints/9` 415 | D-Bus object hosted by `mctpd`. This may occur via e.g. failure to regularly 416 | poll the drive CTEMP. 417 | 418 | 9. `mctpd` [emits a `PropertiesChanged` signal][dbus-spec-standard-interfaces-properties] 419 | from `/au/com/codeconstruct/mctp/networks/1/endpoints/9` with the following 420 | contents: 421 | 422 | ``` 423 | [ 424 | "au.com.codeconstruct.MCTP.Endpoint1", 425 | { "Connectivity": "Degraded" }, 426 | { } 427 | ] 428 | ``` 429 | 430 | 10. `nvmesensor` receives the `Degraded` `PropertiesChanged` event moves the 431 | drive's NVMe subsystem into the `Status::Stop` state 432 | 433 | 11. `mctpd` polls for the presence of the device using `Get Endpoint ID` in accordance with 434 | [8.17.6][dmtf-dsp0236] 435 | 436 | 12. The device fails to respond to all (retried) queries issued inside `Treclaim`. 437 | 438 | 13. `mctpd` removes `/au/com/codeconstruct/mctp1/networks/1/endpoints/9` from 439 | D-Bus, emitting an `InterfacesRemoved` signal with the following content 440 | 441 | ``` 442 | "/au/com/codeconstruct/mctp1/networks/1/endpoints/9": [ 443 | "au.com.codeconstruct.MCTP.Endpoint1", 444 | "xyz.openbmc_project.MCTP.Endpoint", 445 | ... 446 | ] 447 | ``` 448 | 449 | 14. `nvmesensor` receives the `InterfacesRemoved` signal and cleans up its 450 | representation of the device from its idle state. 451 | 452 | [dbus-spec-standard-interfaces-properties]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties 453 | -------------------------------------------------------------------------------- /docs/mctpd.md: -------------------------------------------------------------------------------- 1 | # `mctpd` 2 | 3 | ## D-Bus 4 | 5 | `mctpd` provides a D-Bus service named `au.com.codeconstruct.MCTP1`, and a base 6 | object path of `/au/com/codeconstruct/mctp1`. This tree represents both the 7 | local MCTP stack state and the results of remote device enumeration. 8 | 9 | ``` 10 | Service au.com.codeconstruct.MCTP1: 11 | └─ /au 12 | └─ /au/com 13 | └─ /au/com/codeconstruct 14 | └─ /au/com/codeconstruct/mctp1 15 | ├─ /au/com/codeconstruct/mctp1/interfaces 16 | │ ├─ /au/com/codeconstruct/mctp1/interfaces/lo 17 | │ └─ /au/com/codeconstruct/mctp1/interfaces/mctpi2c1 18 | └─ /au/com/codeconstruct/mctp1/networks 19 | └─ /au/com/codeconstruct/mctp1/networks/1 20 | └─ /au/com/codeconstruct/mctp1/networks/1/endpoints 21 | ├─ /au/com/codeconstruct/mctp1/networks/1/endpoints/8 22 | └─ /au/com/codeconstruct/mctp1/networks/1/endpoints/10 23 | ``` 24 | 25 | 26 | ## Top-level object: `/au/com/codeconstruct/mctp1` 27 | 28 | This object serves as the global MCTP daemon namespace; it doesn't contain 29 | much at present, but hosts two trees of MCTP objects: 30 | 31 | * Interfaces: Local hardware transport bindings that connect us to a MCTP bus 32 | * Endpoints: MCTP endpoints that `mctpd` knows about, both remote and local 33 | 34 | This object implements the `org.freedesktop.DBus.ObjectManager` interface, 35 | allowing enumeration of managed networks, endpoints and interfaces. 36 | 37 | ## MCTP Interface objects: `/au/com/codeconstruct/interfaces/` 38 | 39 | The interface objects represent a connection to a MCTP bus; these will be 40 | 1:1 with the MCTP network interfaces on the system. 41 | 42 | ### MCTP interface interface: `au.com.codeconstruct.MCTP.Interface1` 43 | 44 | All MCTP interface objects host the `au.com.codeconstruct.Interface1` dbus 45 | interface: 46 | 47 | ``` 48 | NAME TYPE SIGNATURE RESULT/VALUE FLAGS 49 | au.com.codeconstruct.MCTP.Interface1 interface - - - 50 | .NetworkId property u 1 emits-change 51 | .Role property s "BusOwner" emits-change writable 52 | ``` 53 | 54 | The D-Bus interface includes the `Role` property which reports BMC roles 55 | in the link. The possible value of `Role` are: 56 | 57 | * `BusOwner`: this link is the owner of the attached bus, 58 | * `Endpoint`: this link is not the owner of the attached bus; and 59 | * `Unknown`: not yet configured. 60 | 61 | The `Role` property is writable, but it can only be changed when the current 62 | configured value is `Unknown`. Other platform setup infrastructure may use 63 | this to configure the initial MCTP state of the platform. 64 | 65 | When the interface `Role` is `BusOwner`, the MCTP interface object will 66 | also host the `BusOwner1` dbus interface: 67 | 68 | The `NetworkId` property represents the network on which this interface is 69 | present. 70 | 71 | ### Bus-owner interface: `au.com.codeconstruct.MCTP.BusOwner1` interface 72 | 73 | This interface exposes bus-owner level functions, on each interface object that 74 | represents the bus-owner side of a transport. 75 | 76 | ``` 77 | NAME TYPE SIGNATURE RESULT/VALUE FLAGS 78 | au.com.codeconstruct.MCTP.Interface1 interface - - - 79 | .NetworkId property u 1 emits-change 80 | .Role property s "BusOwner" emits-change writable 81 | au.com.codeconstruct.MCTP.BusOwner1 interface - - - 82 | .AssignEndpoint method ay yisb - 83 | .AssignEndpointStatic method ayy yisb - 84 | .LearnEndpoint method ay yisb - 85 | .SetupEndpoint method ay yisb - 86 | ``` 87 | 88 | Those BusOwner methods are: 89 | 90 | #### `.SetupEndpoint`: `ay` → `yisb` 91 | 92 | This method is the normal method used to add a MCTP endpoint on this interface. 93 | The endpoint is identified by physical address. `mctpd` will query for the 94 | endpoint's current EID, and assign an EID to the endpoint if needed. `mctpd` 95 | will add local MCTP routes and neighbour table entries for endpoints as they are 96 | added. 97 | 98 | `SetupEndpoint ` 99 | 100 | Returns 101 | ``` 102 | eid (byte) 103 | net (integer) 104 | path (string) 105 | new (bool) - true if a new EID was assigned 106 | ``` 107 | 108 | `` is an interface such as `mctpi2c6`. 109 | 110 | `` depends on the transport type - for i2c it is a 1 byte client address 111 | (7-bit, the same as other Linux tools like `i2cdetect`). 112 | 113 | 114 | An example: 115 | 116 | ```shell 117 | busctl call au.com.codeconstruct.MCTP1 \ 118 | /au/com/codeconstruct/mctp1/interfaces/mctpi2c6 \ 119 | au.com.codeconstruct.MCTP.BusOwner1 \ 120 | SetupEndpoint ay 1 0x1d 121 | ``` 122 | 123 | `1` is the length of the hwaddr array. 124 | 125 | #### `.AssignEndpoint`: `ay` → `yisb` 126 | 127 | Similar to SetupEndpoint, but will always assign an EID rather than querying for 128 | existing ones. Will return `new = false` when an endpoint is already known to 129 | `mctpd`. 130 | 131 | #### `.AssignEndpointStatic`: `ayy` → `yisb` 132 | 133 | Similar to AssignEndpoint, but takes an additional EID argument: 134 | 135 | ``` 136 | AssignEndpointStatic 137 | ``` 138 | 139 | to assign `` to the endpoint with hardware address `hwaddr`. 140 | 141 | This call will fail if the endpoint already has an EID, and that EID is 142 | different from `static-EID`, or if `static-EID` is already assigned to another 143 | endpoint. 144 | 145 | #### `.LearnEndpoint`: `ay` → `yisb` 146 | 147 | Like SetupEndpoint but will not assign EIDs, will only query endpoints for a 148 | current EID. The `new` return value is set to `false` for an already known 149 | endpoint, or `true` when an endpoint's EID is newly discovered. 150 | 151 | ## Network objects: `/au/com/codeconstruct/networks/` 152 | 153 | These objects represent MCTP networks which have been added use `mctp link` 154 | commands. These will be 1:1 with the MCTP networks on the system. 155 | 156 | These objects host the interface `au.com.codeconstruct.MCTP.Network1`. 157 | 158 | ### MCTP network interface: `au.com.codeconstruct.MCTP.Network1` 159 | 160 | All MCTP networks objects host the `au.com.codeconstruct.MCTP.Network1` dbus 161 | interface: 162 | 163 | ``` 164 | NAME TYPE SIGNATURE RESULT/VALUE FLAGS 165 | au.com.codeconstruct.MCTP.Network1 interface - - - 166 | .LearnEndpoint method y sb - 167 | .LocalEIDs property ay 1 8 const 168 | ``` 169 | 170 | ### `.LearnEndpoint`: `y` 171 | 172 | The `LearnEndpoint` method allows a caller to perform enumeration of a 173 | static endpoint that we can already route to. This may be useful to discover 174 | bridged endpoints, where the EID assigment has already been handled by the 175 | bridge. 176 | 177 | `LearnEndpoint` takes an EID as its only argument, and returns the endpoint's 178 | path, and a boolean indicating whether the endpoint was newly discovered. 179 | 180 | The D-Bus interface includes the `LocalEIDs` property which reports BMC local EIDs 181 | in the network. 182 | 183 | ## Endpoint objects: `/au/com/codeconstruct/networks//endpoints/` 184 | 185 | These objects represent MCTP endpoints that `mctpd` has either discovered 186 | locally (typically: MCTP addresses assigned to the local stack), or remote 187 | interfaces discovered during device enumeration. 188 | 189 | These objects host the interface `xyz.openbmc_project.MCTP.Endpoint`, as per 190 | [OpenBMC 191 | documentation](https://github.com/openbmc/phosphor-dbus-interfaces/tree/master/yaml/xyz/openbmc_project/MCTP). 192 | 193 | Each endpoint object has methods to configure it, through the 194 | `au.com.codeconstruct.MCTP.Endpoint1` interface on each endpoint. 195 | 196 | ### `.SetMTU`: `u` 197 | 198 | Sets the MTU (maximum transmission unit) on the route for that endpoint. This 199 | must be within the MTU range allowed for the network device. For i2c that is 200 | [68, 254]. 201 | 202 | If a route-specific MTU has not been set (or set to 0), Linux will use the 203 | per-interface MTU, configurable with `mctp link set mtu `. 204 | 205 | An example, setting MTU of 80: 206 | 207 | ```shell 208 | busctl call au.com.codeconstruct.MCTP1 \ 209 | /au/com/codeconstruct/mctp1/networks/1/endpoints/11 \ 210 | au.com.codeconstruct.MCTP.Endpoint1 \ 211 | SetMTU u 80 212 | ``` 213 | 214 | ### `.Remove` 215 | 216 | Removes the MCTP endpoint from `mctpd`, and deletes routes and neighbour entries. 217 | -------------------------------------------------------------------------------- /examples/tun-forward.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # SPDX-License-Identifier: GPL-2.0 4 | # (c) 2022 Code Construct 5 | 6 | # Set up tunA and tunB MCTP devices with routing between them. 7 | # Each device is assigned to a different MCTP network so that packets 8 | # will be routed between interfaces on a single machine. 9 | 10 | # This script can be split to run Side A and Side B on separate machines 11 | IPA=127.0.0.1 12 | IPB=127.0.0.1 13 | 14 | # This would run on both sides 15 | mctp-echo& 16 | 17 | # Side A is eid 160 18 | socat tun,iff-up,tun-name=tunA udp-datagram:$IPB:9933,bind=$IPA:9922 & 19 | sleep 0.5 20 | mctp link set tunA net 10 21 | mctp addr add 160 dev tunA 22 | mctp route add 161 via tunA 23 | 24 | # Side B is eid 161 25 | socat tun,iff-up,tun-name=tunB udp-datagram:$IPA:9922,bind=$IPB:9933 & 26 | sleep 0.5 27 | mctp link set tunB net 11 28 | mctp addr add 161 dev tunB 29 | mctp route add 160 via tunB 30 | 31 | # Side A sending 32 | mctp-req eid 161 net 10 33 | 34 | # Side B sending 35 | mctp-req eid 160 net 11 36 | 37 | # socat continues running here, kill it to clean up devices 38 | -------------------------------------------------------------------------------- /lib/tomlc99/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) CK Tan 4 | https://github.com/cktan/tomlc99 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /lib/tomlc99/README: -------------------------------------------------------------------------------- 1 | This directory includes an export of the tomlc99 library, hosted at 2 | https://github.com/cktan/tomlc99, at commit 5221b3d3. We only use the toml.c 3 | and toml.h files from that repository. 4 | 5 | tomlc99 is released under the MIT license - see LICENSE in this directory for 6 | full license text. 7 | -------------------------------------------------------------------------------- /lib/tomlc99/toml.h: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) CK Tan 5 | https://github.com/cktan/tomlc99 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | #ifndef TOML_H 26 | #define TOML_H 27 | 28 | #ifdef _MSC_VER 29 | #pragma warning(disable : 4996) 30 | #endif 31 | 32 | #include 33 | #include 34 | 35 | #ifdef __cplusplus 36 | #define TOML_EXTERN extern "C" 37 | #else 38 | #define TOML_EXTERN extern 39 | #endif 40 | 41 | typedef struct toml_timestamp_t toml_timestamp_t; 42 | typedef struct toml_table_t toml_table_t; 43 | typedef struct toml_array_t toml_array_t; 44 | typedef struct toml_datum_t toml_datum_t; 45 | 46 | /* Parse a file. Return a table on success, or 0 otherwise. 47 | * Caller must toml_free(the-return-value) after use. 48 | */ 49 | TOML_EXTERN toml_table_t *toml_parse_file(FILE *fp, char *errbuf, int errbufsz); 50 | 51 | /* Parse a string containing the full config. 52 | * Return a table on success, or 0 otherwise. 53 | * Caller must toml_free(the-return-value) after use. 54 | */ 55 | TOML_EXTERN toml_table_t *toml_parse(char *conf, /* NUL terminated, please. */ 56 | char *errbuf, int errbufsz); 57 | 58 | /* Free the table returned by toml_parse() or toml_parse_file(). Once 59 | * this function is called, any handles accessed through this tab 60 | * directly or indirectly are no longer valid. 61 | */ 62 | TOML_EXTERN void toml_free(toml_table_t *tab); 63 | 64 | /* Timestamp types. The year, month, day, hour, minute, second, z 65 | * fields may be NULL if they are not relevant. e.g. In a DATE 66 | * type, the hour, minute, second and z fields will be NULLs. 67 | */ 68 | struct toml_timestamp_t { 69 | struct { /* internal. do not use. */ 70 | int year, month, day; 71 | int hour, minute, second, millisec; 72 | char z[10]; 73 | } __buffer; 74 | int *year, *month, *day; 75 | int *hour, *minute, *second, *millisec; 76 | char *z; 77 | }; 78 | 79 | /*----------------------------------------------------------------- 80 | * Enhanced access methods 81 | */ 82 | struct toml_datum_t { 83 | int ok; 84 | union { 85 | toml_timestamp_t *ts; /* ts must be freed after use */ 86 | char *s; /* string value. s must be freed after use */ 87 | int b; /* bool value */ 88 | int64_t i; /* int value */ 89 | double d; /* double value */ 90 | } u; 91 | }; 92 | 93 | /* on arrays: */ 94 | /* ... retrieve size of array. */ 95 | TOML_EXTERN int toml_array_nelem(const toml_array_t *arr); 96 | /* ... retrieve values using index. */ 97 | TOML_EXTERN toml_datum_t toml_string_at(const toml_array_t *arr, int idx); 98 | TOML_EXTERN toml_datum_t toml_bool_at(const toml_array_t *arr, int idx); 99 | TOML_EXTERN toml_datum_t toml_int_at(const toml_array_t *arr, int idx); 100 | TOML_EXTERN toml_datum_t toml_double_at(const toml_array_t *arr, int idx); 101 | TOML_EXTERN toml_datum_t toml_timestamp_at(const toml_array_t *arr, int idx); 102 | /* ... retrieve array or table using index. */ 103 | TOML_EXTERN toml_array_t *toml_array_at(const toml_array_t *arr, int idx); 104 | TOML_EXTERN toml_table_t *toml_table_at(const toml_array_t *arr, int idx); 105 | 106 | /* on tables: */ 107 | /* ... retrieve the key in table at keyidx. Return 0 if out of range. */ 108 | TOML_EXTERN const char *toml_key_in(const toml_table_t *tab, int keyidx); 109 | /* ... returns 1 if key exists in tab, 0 otherwise */ 110 | TOML_EXTERN int toml_key_exists(const toml_table_t *tab, const char *key); 111 | /* ... retrieve values using key. */ 112 | TOML_EXTERN toml_datum_t toml_string_in(const toml_table_t *arr, 113 | const char *key); 114 | TOML_EXTERN toml_datum_t toml_bool_in(const toml_table_t *arr, const char *key); 115 | TOML_EXTERN toml_datum_t toml_int_in(const toml_table_t *arr, const char *key); 116 | TOML_EXTERN toml_datum_t toml_double_in(const toml_table_t *arr, 117 | const char *key); 118 | TOML_EXTERN toml_datum_t toml_timestamp_in(const toml_table_t *arr, 119 | const char *key); 120 | /* .. retrieve array or table using key. */ 121 | TOML_EXTERN toml_array_t *toml_array_in(const toml_table_t *tab, 122 | const char *key); 123 | TOML_EXTERN toml_table_t *toml_table_in(const toml_table_t *tab, 124 | const char *key); 125 | 126 | /*----------------------------------------------------------------- 127 | * lesser used 128 | */ 129 | /* Return the array kind: 't'able, 'a'rray, 'v'alue, 'm'ixed */ 130 | TOML_EXTERN char toml_array_kind(const toml_array_t *arr); 131 | 132 | /* For array kind 'v'alue, return the type of values 133 | i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp, 'm'ixed 134 | 0 if unknown 135 | */ 136 | TOML_EXTERN char toml_array_type(const toml_array_t *arr); 137 | 138 | /* Return the key of an array */ 139 | TOML_EXTERN const char *toml_array_key(const toml_array_t *arr); 140 | 141 | /* Return the number of key-values in a table */ 142 | TOML_EXTERN int toml_table_nkval(const toml_table_t *tab); 143 | 144 | /* Return the number of arrays in a table */ 145 | TOML_EXTERN int toml_table_narr(const toml_table_t *tab); 146 | 147 | /* Return the number of sub-tables in a table */ 148 | TOML_EXTERN int toml_table_ntab(const toml_table_t *tab); 149 | 150 | /* Return the key of a table*/ 151 | TOML_EXTERN const char *toml_table_key(const toml_table_t *tab); 152 | 153 | /*-------------------------------------------------------------- 154 | * misc 155 | */ 156 | TOML_EXTERN int toml_utf8_to_ucs(const char *orig, int len, int64_t *ret); 157 | TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]); 158 | TOML_EXTERN void toml_set_memutil(void *(*xxmalloc)(size_t), 159 | void (*xxfree)(void *)); 160 | 161 | /*-------------------------------------------------------------- 162 | * deprecated 163 | */ 164 | /* A raw value, must be processed by toml_rto* before using. */ 165 | typedef const char *toml_raw_t; 166 | TOML_EXTERN toml_raw_t toml_raw_in(const toml_table_t *tab, const char *key); 167 | TOML_EXTERN toml_raw_t toml_raw_at(const toml_array_t *arr, int idx); 168 | TOML_EXTERN int toml_rtos(toml_raw_t s, char **ret); 169 | TOML_EXTERN int toml_rtob(toml_raw_t s, int *ret); 170 | TOML_EXTERN int toml_rtoi(toml_raw_t s, int64_t *ret); 171 | TOML_EXTERN int toml_rtod(toml_raw_t s, double *ret); 172 | TOML_EXTERN int toml_rtod_ex(toml_raw_t s, double *ret, char *buf, int buflen); 173 | TOML_EXTERN int toml_rtots(toml_raw_t s, toml_timestamp_t *ret); 174 | 175 | #endif /* TOML_H */ 176 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | # 3 | project( 4 | 'mctp', 'c', 5 | meson_version: '>= 0.59.0', 6 | version: 'v2.1', 7 | license: 'GPLv2', 8 | default_options: [ 9 | 'warning_level=2', 10 | ], 11 | ) 12 | 13 | cc = meson.get_compiler('c') 14 | 15 | add_project_arguments('-Wno-unused-parameter', language : 'c') 16 | 17 | libsystemd = dependency('libsystemd', version: '>=247', required: false) 18 | 19 | conf = configuration_data() 20 | conf.set10('HAVE_LINUX_MCTP_H', 21 | cc.has_header('linux/mctp.h'), 22 | description: 'Is linux/mctp.h available?' 23 | ) 24 | conf.set10('MCTPD_RECOVER_NIL_UUID', 25 | get_option('unsafe-recover-nil-uuid'), 26 | description: 'Consider a nil UUID to be valid for endpoint recovery purposes', 27 | ) 28 | conf.set10('MCTPD_WRITABLE_CONNECTIVITY', 29 | get_option('unsafe-writable-connectivity'), 30 | description: 'Allow writes to the Connectivity member of the au.com.codeconstruct.MCTP.Endpoint1 interface on endpoint objects') 31 | 32 | conf.set_quoted('MCTPD_CONF_FILE_DEFAULT', 33 | join_paths(get_option('prefix'), get_option('sysconfdir'), 'mctpd.conf'), 34 | description: 'Default configuration file path', 35 | ) 36 | 37 | config_h = configure_file( 38 | output: 'config.h', 39 | configuration: conf, 40 | ) 41 | 42 | util_sources = ['src/mctp-util.c'] 43 | netlink_sources = ['src/mctp-netlink.c'] 44 | ops_sources = ['src/mctp-ops.c'] 45 | 46 | toml_dep = declare_dependency( 47 | sources: ['lib/tomlc99/toml.c'], 48 | include_directories: include_directories('lib/tomlc99'), 49 | ) 50 | 51 | executable('mctp', 52 | sources: ['src/mctp.c'] + netlink_sources + util_sources + ops_sources, 53 | install: true, 54 | ) 55 | 56 | executable('mctp-req', 57 | sources: ['src/mctp-req.c'] + util_sources, 58 | ) 59 | 60 | executable('mctp-echo', 61 | sources: ['src/mctp-echo.c'] + util_sources, 62 | ) 63 | 64 | executable('mctp-bench', 65 | sources: ['src/mctp-bench.c'] + util_sources, 66 | ) 67 | 68 | if libsystemd.found() 69 | executable('mctpd', 70 | sources: [ 71 | 'src/mctpd.c', 72 | ] + netlink_sources + util_sources + ops_sources, 73 | dependencies: [libsystemd, toml_dep], 74 | install: true, 75 | install_dir: get_option('sbindir'), 76 | ) 77 | 78 | mctpd_test = executable('test-mctpd', 79 | sources: [ 80 | 'src/mctpd.c', 81 | 'tests/mctp-ops-test.c', 82 | ] + netlink_sources + util_sources, 83 | include_directories: include_directories('src'), 84 | dependencies: [libsystemd, toml_dep], 85 | ) 86 | endif 87 | 88 | tests = get_option('tests') 89 | if tests 90 | # The test suite contains integration-style tests that we wish to isolate. 91 | # The tests drive mctpd's D-Bus interfaces and navigate relevant code paths 92 | # by mocking kernel and MCTP device behaviour. The mock behaviours are 93 | # implemented by encapsulating netlink and MCTP messages over Unix domain 94 | # sockets bound to mock implementations in the test suite. 95 | # 96 | # Isolate the test suite under its own D-Bus session to prevent test 97 | # behaviours impacting applications connected to the usual buses. To 98 | # implement the isolation we need to work under some constraints: 99 | # 100 | # 1. mctpd's implementation connects to a bus by invoking `sd_bus_default()`, 101 | # which uses a combination of environment variables and session 102 | # properties to determine whether to connect to the system bus or the 103 | # session bus 104 | # 105 | # 2. dbus-run-session configures an isolated session bus and controls the 106 | # relevant environment variables for influencing the application to 107 | # connect to this isolated bus instance. 108 | # 109 | # `sd_bus_default()` selects the system bus if the application is 110 | # not running in a systemd user slice. From experience, in a Github 111 | # Action environment the system bus is selected by `sd_bus_default()`, 112 | # implying that they aren't exploiting systemd slices. However, 113 | # `sd_bus_default()` can be influenced to select the user session by setting 114 | # `DBUS_STARTER_BUS_TYPE=user` in the environment. At this point with a 115 | # naive approach we run up against 2, where `dbus-run-session` sanitises 116 | # that configuration away 117 | # 118 | # Invoke pytest via a shell script under `dbus-run-session` so we can 119 | # override the sanitation of `DBUS_STARTER_BUS_TYPE`, ensuring `test-mctpd` 120 | # connects to the isolated session bus prepared by `dbus-run-session`. 121 | # 122 | # On newer meson versions, we can use meson test -C build --interactive 123 | # to allow pytest to print output directly onto the terminal without 124 | # redirecting to a file. We can detect this if stdout is an terminal, and 125 | # disable TAP protocol. 126 | pytest = find_program('pytest') 127 | script = '''export DBUS_STARTER_BUS_TYPE=user; if [ ! -t 1 ]; then PYTEST_FLAGS="--tap"; fi; @0@ $PYTEST_FLAGS $@'''.format(pytest.full_path()) 128 | sh = find_program('sh') 129 | dbus_run_session = find_program('dbus-run-session') 130 | 131 | test_conf_data = configuration_data() 132 | test_conf_data.set('testpaths', 133 | join_paths(meson.current_source_dir(), 'tests') 134 | ) 135 | configure_file( 136 | input: 'tests/pytest.ini.in', 137 | output: 'pytest.ini', 138 | configuration: test_conf_data, 139 | ) 140 | 141 | test('test-mctpd', dbus_run_session, 142 | depends: mctpd_test, 143 | args: [ sh.full_path(), '-c', script, '--' ], 144 | protocol: 'tap', 145 | ) 146 | endif 147 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('unsafe-recover-nil-uuid', type: 'boolean', value: false) 2 | option('unsafe-writable-connectivity', 3 | type: 'boolean', 4 | value: false, 5 | description: 'Allow writes to the Connectivity member of the au.com.codeconstruct.MCTP.Endpoint1 interface on endpoint objects') 6 | option('tests', type: 'boolean', value: true) 7 | -------------------------------------------------------------------------------- /src/mctp-bench.c: -------------------------------------------------------------------------------- 1 | #define _XOPEN_SOURCE 700 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "mctp.h" 17 | 18 | // Code Construct allocation 19 | static const uint8_t VENDOR_TYPE_BENCH[3] = { 0xcc, 0xde, 0xf1 }; 20 | static const uint8_t MCTP_TYPE_VENDOR_PCIE = 0x7e; 21 | 22 | struct mctp_bench_send_args { 23 | mctp_eid_t eid; 24 | size_t len; 25 | int net; 26 | }; 27 | 28 | struct msg_header { 29 | uint8_t vendor_prefix[sizeof(VENDOR_TYPE_BENCH)]; 30 | uint16_t magic; 31 | uint32_t seq_no; 32 | } __attribute__((packed)); 33 | 34 | struct mctp_stats { 35 | size_t total_received_len, curr_packet_len; 36 | uint32_t prev_seq_no; 37 | float elapsed_time; 38 | unsigned long msgs_dropped, msg_count, invalid_payloads; 39 | }; 40 | 41 | struct recv_ctx { 42 | struct mctp_stats stats; 43 | struct timespec start_time, current_time; 44 | unsigned char *buf; 45 | bool started_recv_flag; 46 | int sd; 47 | }; 48 | 49 | static const size_t MSG_HEADER_LEN = sizeof(struct msg_header); 50 | static const size_t MAX_LEN = 64 * 1024 - 1; 51 | static const uint32_t SEQ_START = UINT32_MAX - 5; 52 | static const uint16_t MAGIC_VAL = 0xbeca; 53 | static const int DEFAULT_NET = MCTP_NET_ANY; 54 | static const int DEFAULT_SECONDS_INTERVAL = 10; 55 | 56 | static float get_throughput(float total_len, float elapsed_time) 57 | { 58 | return total_len / (elapsed_time * 1024); 59 | } 60 | 61 | static void print_stats(struct recv_ctx *recv_ctx) 62 | { 63 | float throughput = get_throughput(recv_ctx->stats.total_received_len, 64 | recv_ctx->stats.elapsed_time); 65 | printf("Throughput: %.2f kB/s | Recevied: %lu msgs | " 66 | "Dropped: %lu msgs | " 67 | "Invalid: %lu msgs\n", 68 | throughput, recv_ctx->stats.msg_count, 69 | recv_ctx->stats.msgs_dropped, recv_ctx->stats.invalid_payloads); 70 | } 71 | 72 | static float get_elapsed_time(struct recv_ctx *recv_ctx) 73 | { 74 | return (recv_ctx->current_time.tv_sec - recv_ctx->start_time.tv_sec) + 75 | (recv_ctx->current_time.tv_nsec - recv_ctx->start_time.tv_nsec) / 76 | 1.0e9; 77 | } 78 | 79 | static int get_timeout(struct recv_ctx *recv_ctx) 80 | { 81 | int time_to_print_sec = 82 | (DEFAULT_SECONDS_INTERVAL) - 83 | (recv_ctx->current_time.tv_sec - recv_ctx->start_time.tv_sec); 84 | return (time_to_print_sec > 0) ? time_to_print_sec * 1000 : 0; 85 | } 86 | 87 | static bool valid_payload(unsigned char *buf, size_t buflen) 88 | { 89 | for (size_t i = MSG_HEADER_LEN; i < buflen; i++) { 90 | if (buf[i] != (i & 0xff)) 91 | return false; 92 | } 93 | return true; 94 | } 95 | 96 | static uint32_t get_packets_dropped(uint32_t curr, uint32_t prev) 97 | { 98 | if (prev < curr) { 99 | return curr - prev - 1; 100 | } else if (curr == prev) { 101 | return 0; 102 | } 103 | return UINT32_MAX - prev + curr; 104 | } 105 | 106 | static int handle_incoming_msg(struct recv_ctx *recv_ctx) 107 | { 108 | struct msg_header *hdr; 109 | 110 | ssize_t len = recv(recv_ctx->sd, recv_ctx->buf, MAX_LEN, MSG_TRUNC); 111 | if (len < 0) { 112 | warn("recv: recvfrom"); 113 | return -1; 114 | } 115 | 116 | recv_ctx->stats.curr_packet_len = len; 117 | if (recv_ctx->stats.curr_packet_len > MAX_LEN) { 118 | warn("recv: expected max len:%zd bytes, got:%zd bytes", MAX_LEN, 119 | recv_ctx->stats.curr_packet_len); 120 | return -1; 121 | } 122 | if (recv_ctx->stats.curr_packet_len < sizeof(VENDOR_TYPE_BENCH)) { 123 | warn("recv: short vendor prefix, got:%zd bytes", 124 | recv_ctx->stats.curr_packet_len); 125 | return -1; 126 | } 127 | 128 | hdr = (struct msg_header *)recv_ctx->buf; 129 | if (memcmp(hdr->vendor_prefix, VENDOR_TYPE_BENCH, 130 | sizeof(VENDOR_TYPE_BENCH)) != 0) { 131 | warnx("recv: unexpected vendor prefix %02x %02x %02x", 132 | hdr->vendor_prefix[0], hdr->vendor_prefix[1], 133 | hdr->vendor_prefix[2] 134 | ); 135 | return -1; 136 | } 137 | if (recv_ctx->stats.curr_packet_len < sizeof(*hdr)) { 138 | warn("recv: short message, got:%zd bytes", 139 | recv_ctx->stats.curr_packet_len); 140 | return -1; 141 | } 142 | 143 | if (hdr->magic != MAGIC_VAL) { 144 | warnx("recv: expected magic:\"%x\", got:\"%x\"\n", MAGIC_VAL, 145 | hdr->magic); 146 | return -1; 147 | } 148 | 149 | recv_ctx->stats.total_received_len += recv_ctx->stats.curr_packet_len; 150 | recv_ctx->stats.msg_count++; 151 | 152 | if (!valid_payload(recv_ctx->buf, recv_ctx->stats.curr_packet_len)) 153 | recv_ctx->stats.invalid_payloads++; 154 | 155 | if (!recv_ctx->started_recv_flag) { 156 | printf("recv: first msg received\n"); 157 | recv_ctx->started_recv_flag = true; 158 | clock_gettime(CLOCK_MONOTONIC, &recv_ctx->start_time); 159 | recv_ctx->stats.prev_seq_no = hdr->seq_no; 160 | recv_ctx->stats.msgs_dropped += 161 | get_packets_dropped(hdr->seq_no, SEQ_START); 162 | return -1; 163 | } 164 | 165 | recv_ctx->stats.msgs_dropped += 166 | get_packets_dropped(hdr->seq_no, recv_ctx->stats.prev_seq_no); 167 | 168 | recv_ctx->stats.prev_seq_no = hdr->seq_no; 169 | return 0; 170 | } 171 | 172 | static int mctp_bench_recv() 173 | { 174 | struct recv_ctx recv_ctx = {0}; 175 | struct sockaddr_mctp addr = {0}; 176 | int rc; 177 | 178 | recv_ctx.sd = socket(AF_MCTP, SOCK_DGRAM, 0); 179 | if (recv_ctx.sd < 0) 180 | err(EXIT_FAILURE, "recv: socket"); 181 | 182 | addr.smctp_family = AF_MCTP; 183 | addr.smctp_network = MCTP_NET_ANY; 184 | addr.smctp_addr.s_addr = MCTP_ADDR_ANY; 185 | addr.smctp_type = MCTP_TYPE_VENDOR_PCIE; 186 | addr.smctp_tag = MCTP_TAG_OWNER; 187 | 188 | recv_ctx.buf = malloc(MAX_LEN); 189 | if (!recv_ctx.buf) { 190 | err(EXIT_FAILURE, "recv: malloc failed"); 191 | } 192 | 193 | rc = bind(recv_ctx.sd, (struct sockaddr *)&addr, sizeof(addr)); 194 | if (rc) { 195 | close(recv_ctx.sd); 196 | err(EXIT_FAILURE, "recv: bind failed"); 197 | } 198 | 199 | recv_ctx.started_recv_flag = false; 200 | 201 | printf("recv: waiting for first msg\n"); 202 | while (1) { 203 | int timeout; 204 | struct pollfd pollfd; 205 | pollfd.fd = recv_ctx.sd; 206 | pollfd.events = POLLIN; 207 | 208 | if (recv_ctx.started_recv_flag) { 209 | clock_gettime(CLOCK_MONOTONIC, &recv_ctx.current_time); 210 | timeout = get_timeout(&recv_ctx); 211 | } else 212 | timeout = -1; 213 | 214 | rc = poll(&pollfd, 1, timeout); 215 | if (rc < 0) { 216 | warn("recv: poll failed"); 217 | break; 218 | } 219 | 220 | if (rc == 1 && pollfd.revents & POLLIN) { 221 | rc = handle_incoming_msg(&recv_ctx); 222 | if (rc) 223 | continue; 224 | } 225 | 226 | clock_gettime(CLOCK_MONOTONIC, &recv_ctx.current_time); 227 | 228 | recv_ctx.stats.elapsed_time = get_elapsed_time(&recv_ctx); 229 | if (recv_ctx.stats.elapsed_time >= DEFAULT_SECONDS_INTERVAL) { 230 | print_stats(&recv_ctx); 231 | recv_ctx.stats.total_received_len = 0; 232 | recv_ctx.stats.msg_count = 0l; 233 | recv_ctx.stats.msgs_dropped = 0l; 234 | recv_ctx.stats.invalid_payloads = 0l; 235 | clock_gettime(CLOCK_MONOTONIC, &recv_ctx.start_time); 236 | } 237 | } 238 | free(recv_ctx.buf); 239 | close(recv_ctx.sd); 240 | return 0; 241 | } 242 | 243 | static int allocate_tag(int sd, mctp_eid_t eid, int net, uint8_t *tag) 244 | { 245 | int rc = -1; 246 | 247 | #if !defined(SIOCMCTPALLOCTAG2) && !defined(SIOCMCTPALLOCTAG) 248 | #error No ALLOCTAG ioctl available 249 | #endif 250 | 251 | #if defined(SIOCMCTPALLOCTAG2) 252 | struct mctp_ioc_tag_ctl2 ctl2 = { 253 | .peer_addr = eid, 254 | .net = net, 255 | }; 256 | 257 | errno = 0; 258 | rc = ioctl(sd, SIOCMCTPALLOCTAG2, &ctl2); 259 | if (!rc) { 260 | *tag = ctl2.tag; 261 | return 0; 262 | } 263 | 264 | /* 265 | * If Alloctag V2 does not exist, we would get EINVAL. 266 | * In that case we want to fallback to Alloctag V1. 267 | * All other cases we return the error. 268 | */ 269 | if (errno != EINVAL) { 270 | return rc; 271 | } 272 | #endif 273 | 274 | #if defined(SIOCMCTPALLOCTAG) 275 | struct mctp_ioc_tag_ctl ctl = { 276 | .peer_addr = eid, 277 | }; 278 | 279 | /* Alloctag V1 only works with default net. */ 280 | if (net != DEFAULT_NET) { 281 | warnx("Can't use ALLOCTAG V1 for non-default net:%d", net); 282 | return -1; 283 | } 284 | 285 | rc = ioctl(sd, SIOCMCTPALLOCTAG, &ctl); 286 | if (!rc) { 287 | *tag = ctl.tag; 288 | return 0; 289 | } 290 | #endif 291 | return rc; 292 | } 293 | 294 | static int mctp_bench_send(struct mctp_bench_send_args send_args) 295 | { 296 | struct sockaddr_mctp addr = {0}; 297 | struct msg_header *hdr; 298 | unsigned char *buf; 299 | uint32_t sequence = SEQ_START; 300 | uint8_t tag; 301 | int rc, sd, last_rc; 302 | 303 | sd = socket(AF_MCTP, SOCK_DGRAM, 0); 304 | if (sd < 0) 305 | err(EXIT_FAILURE, "send: socket"); 306 | 307 | addr.smctp_family = AF_MCTP; 308 | addr.smctp_network = send_args.net; 309 | addr.smctp_addr.s_addr = send_args.eid; 310 | addr.smctp_type = MCTP_TYPE_VENDOR_PCIE; 311 | printf("send: eid = %d, net = %d, type = %d, msg_len = %zu bytes\n", 312 | send_args.eid, send_args.net, addr.smctp_type, send_args.len); 313 | 314 | buf = malloc(send_args.len); 315 | if (!buf) 316 | err(EXIT_FAILURE, "send: malloc"); 317 | 318 | rc = allocate_tag(sd, send_args.eid, send_args.net, &tag); 319 | if (rc) 320 | err(EXIT_FAILURE, "send: alloc tag failed"); 321 | 322 | for (size_t i = MSG_HEADER_LEN; i < send_args.len; i++) 323 | buf[i] = i & 0xff; 324 | 325 | hdr = (struct msg_header *)buf; 326 | memcpy(hdr->vendor_prefix, VENDOR_TYPE_BENCH, sizeof(VENDOR_TYPE_BENCH)); 327 | hdr->magic = MAGIC_VAL; 328 | 329 | /* will not match a sensible sendto() return value */ 330 | last_rc = 0; 331 | 332 | while (1) { 333 | addr.smctp_tag = tag; 334 | hdr->seq_no = sequence; 335 | 336 | rc = sendto(sd, buf, send_args.len, 0, (struct sockaddr *)&addr, 337 | sizeof(addr)); 338 | if (rc != (int)send_args.len && rc != last_rc) { 339 | last_rc = rc; 340 | warn("send: sendto(%zd bytes)", send_args.len); 341 | } 342 | 343 | sequence++; 344 | } 345 | free(buf); 346 | close(sd); 347 | return 0; 348 | } 349 | 350 | static void usage(void) 351 | { 352 | fprintf(stderr, "'mctp-bench send' [len ] eid [,]\n"); 353 | fprintf(stderr, "'mctp-bench recv'\n"); 354 | } 355 | 356 | static int parse_int(char *opt, unsigned int *out) 357 | { 358 | char *endptr; 359 | 360 | errno = 0; 361 | *out = strtoul(opt, &endptr, 0); 362 | if (endptr == opt || errno == ERANGE) { 363 | return -1; 364 | } 365 | return 0; 366 | } 367 | 368 | static int parse_net_and_eid(struct mctp_bench_send_args *send_args, char *opt) 369 | { 370 | char *comma; 371 | unsigned int tmp, rc; 372 | 373 | for (size_t i = 0; i < strlen(opt); i++) { 374 | if ((opt[i] < '0' || opt[i] > '9') && opt[i] != ',') { 375 | warnx("send: invalid eid or net value:\"%s\"", opt); 376 | return -1; 377 | } 378 | } 379 | comma = strchr(opt, ','); 380 | 381 | rc = parse_int(opt, &tmp); 382 | if (rc) { 383 | warn("send: invalid eid or net value:\"%s\"", opt); 384 | return -1; 385 | } 386 | 387 | if (comma) { 388 | if (!tmp) { 389 | warnx("send: eid cannot be set to 0\n"); 390 | return -1; 391 | } 392 | 393 | send_args->net = tmp; 394 | comma++; 395 | 396 | rc = parse_int(comma, &tmp); 397 | if (rc) { 398 | warn("send: invalid eid or net value:\"%s\"", opt); 399 | return -1; 400 | } 401 | } 402 | send_args->eid = tmp; 403 | return 0; 404 | } 405 | 406 | static int parse_len(struct mctp_bench_send_args *send_args, char *opt) 407 | { 408 | unsigned int tmp = 0; 409 | int rc = 0; 410 | 411 | rc = parse_int(opt, &tmp); 412 | if (rc || tmp > MAX_LEN) { 413 | warnx("send: invalid len value:\"%s\", max len:%zd bytes", opt, 414 | MAX_LEN); 415 | return -1; 416 | } 417 | 418 | if (tmp >= MSG_HEADER_LEN) { 419 | send_args->len = tmp; 420 | } else { 421 | printf("send: min len is %zd bytes, len set to %zd bytes\n", 422 | MSG_HEADER_LEN, MSG_HEADER_LEN); 423 | send_args->len = MSG_HEADER_LEN; 424 | } 425 | return 0; 426 | } 427 | 428 | int main(int argc, char **argv) 429 | { 430 | char *optname, *optval; 431 | int rc = 0; 432 | 433 | if (argc < 2 || argc > 6) { 434 | warnx("%s\n", (argc < 2) ? "error: missing command" 435 | : "error: too many arguments"); 436 | usage(); 437 | return EXIT_FAILURE; 438 | } 439 | 440 | if (!strcmp(argv[1], "send")) { 441 | struct mctp_bench_send_args send_args = { 442 | .eid = 0, 443 | .len = MSG_HEADER_LEN, 444 | .net = DEFAULT_NET, 445 | }; 446 | for (int i = 2; i < argc; i += 2) { 447 | optname = argv[i]; 448 | optval = argv[i + 1]; 449 | if (!strcmp(optname, "eid")) { 450 | rc = parse_net_and_eid(&send_args, optval); 451 | if (rc) { 452 | usage(); 453 | return EXIT_FAILURE; 454 | } 455 | } else if (!strcmp(optname, "len")) { 456 | rc = parse_len(&send_args, optval); 457 | if (rc) { 458 | usage(); 459 | return EXIT_FAILURE; 460 | } 461 | } else { 462 | warnx("send: unknown argument:\"%s\"\n", 463 | optname); 464 | usage(); 465 | return EXIT_FAILURE; 466 | } 467 | } 468 | 469 | if (!send_args.eid) { 470 | warnx("send: missing eid\n"); 471 | usage(); 472 | return EXIT_FAILURE; 473 | } 474 | return mctp_bench_send(send_args); 475 | } else if (!strcmp(argv[1], "recv")) { 476 | if (argc > 2) { 477 | warnx("recv: does not take extra arguments\n"); 478 | usage(); 479 | return EXIT_FAILURE; 480 | } 481 | return mctp_bench_recv(); 482 | } else { 483 | warnx("error: unknown command:\"%s\"\n", argv[1]); 484 | usage(); 485 | return EXIT_FAILURE; 486 | } 487 | return EXIT_FAILURE; 488 | } 489 | -------------------------------------------------------------------------------- /src/mctp-control-spec.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ 2 | 3 | /* Derived from libmctp's libmctp-cmds.h */ 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | /* 10 | * Helper structs and functions for MCTP control messages. 11 | * See DSP0236 v1.3.0 sec. 11 for reference. 12 | */ 13 | 14 | struct mctp_ctrl_msg_hdr { 15 | uint8_t rq_dgram_inst; 16 | uint8_t command_code; 17 | } __attribute__((__packed__)); 18 | 19 | struct mctp_ctrl_resp { 20 | struct mctp_ctrl_msg_hdr ctrl_hdr; 21 | uint8_t completion_code; 22 | } __attribute__((packed)); 23 | 24 | typedef enum { 25 | mctp_ctrl_cmd_set_eid_set_eid = 0, 26 | mctp_ctrl_cmd_set_eid_force_eid = 1, 27 | mctp_ctrl_cmd_set_eid_reset_eid = 2, 28 | mctp_ctrl_cmd_set_eid_set_discovered_flag = 3, 29 | } mctp_ctrl_cmd_set_eid_op; 30 | 31 | struct mctp_ctrl_cmd_set_eid { 32 | struct mctp_ctrl_msg_hdr ctrl_hdr; 33 | uint8_t operation; 34 | uint8_t eid; 35 | } __attribute__((__packed__)); 36 | 37 | struct mctp_ctrl_resp_set_eid { 38 | struct mctp_ctrl_msg_hdr ctrl_hdr; 39 | uint8_t completion_code; 40 | uint8_t status; 41 | mctp_eid_t eid_set; 42 | uint8_t eid_pool_size; 43 | } __attribute__((__packed__)); 44 | 45 | struct mctp_ctrl_cmd_get_eid { 46 | struct mctp_ctrl_msg_hdr ctrl_hdr; 47 | } __attribute__((__packed__)); 48 | 49 | struct mctp_ctrl_resp_get_eid { 50 | struct mctp_ctrl_msg_hdr ctrl_hdr; 51 | uint8_t completion_code; 52 | mctp_eid_t eid; 53 | uint8_t eid_type; 54 | uint8_t medium_data; 55 | } __attribute__((__packed__)); 56 | 57 | struct mctp_ctrl_cmd_get_uuid { 58 | struct mctp_ctrl_msg_hdr ctrl_hdr; 59 | } __attribute__((__packed__)); 60 | 61 | struct mctp_ctrl_resp_get_uuid { 62 | struct mctp_ctrl_msg_hdr ctrl_hdr; 63 | uint8_t completion_code; 64 | uint8_t uuid[16]; 65 | } __attribute__((__packed__)); 66 | 67 | struct mctp_ctrl_cmd_get_mctp_ver_support { 68 | struct mctp_ctrl_msg_hdr ctrl_hdr; 69 | uint8_t msg_type_number; 70 | } __attribute__((__packed__)); 71 | 72 | #define MCTP_GET_VERSION_SUPPORT_BASE_INFO 0xFF 73 | 74 | struct mctp_ctrl_resp_get_mctp_ver_support { 75 | struct mctp_ctrl_msg_hdr ctrl_hdr; 76 | uint8_t completion_code; 77 | uint8_t number_of_entries; 78 | } __attribute__((__packed__)); 79 | 80 | struct version_entry { 81 | uint8_t major; 82 | uint8_t minor; 83 | uint8_t update; 84 | uint8_t alpha; 85 | } __attribute__((__packed__)); 86 | 87 | struct mctp_ctrl_cmd_get_msg_type_support { 88 | struct mctp_ctrl_msg_hdr ctrl_hdr; 89 | } __attribute__((__packed__)); 90 | 91 | struct mctp_ctrl_resp_get_msg_type_support { 92 | struct mctp_ctrl_msg_hdr ctrl_hdr; 93 | uint8_t completion_code; 94 | uint8_t msg_type_count; 95 | } __attribute__((__packed__)); 96 | 97 | struct msg_type_entry { 98 | uint8_t msg_type_no; 99 | } __attribute__((__packed__)); 100 | 101 | struct mctp_ctrl_cmd_get_vdm_support { 102 | struct mctp_ctrl_msg_hdr ctrl_hdr; 103 | uint8_t vendor_id_set_selector; 104 | } __attribute__((__packed__)); 105 | 106 | struct mctp_ctrl_resp_get_vdm_support { 107 | struct mctp_ctrl_msg_hdr ctrl_hdr; 108 | uint8_t completion_code; 109 | uint8_t vendor_id_set_selector; 110 | uint8_t vendor_id_format; 111 | union { 112 | uint16_t vendor_id_data_pcie; 113 | uint32_t vendor_id_data_iana; 114 | }; 115 | /* following bytes are dependent on vendor id format 116 | * and shall be interpreted by appropriate binding handler */ 117 | } __attribute__((__packed__)); 118 | 119 | struct mctp_pci_ctrl_resp_get_vdm_support { 120 | struct mctp_ctrl_msg_hdr ctrl_hdr; 121 | uint8_t completion_code; 122 | uint8_t vendor_id_set_selector; 123 | uint8_t vendor_id_format; 124 | uint16_t vendor_id_data; 125 | uint16_t command_set_type; 126 | } __attribute__((__packed__)); 127 | 128 | struct mctp_ctrl_cmd_discovery_notify { 129 | struct mctp_ctrl_msg_hdr ctrl_hdr; 130 | } __attribute__((__packed__)); 131 | 132 | struct mctp_ctrl_resp_discovery_notify { 133 | struct mctp_ctrl_msg_hdr ctrl_hdr; 134 | uint8_t completion_code; 135 | } __attribute__((__packed__)); 136 | 137 | struct mctp_ctrl_cmd_get_routing_table { 138 | struct mctp_ctrl_msg_hdr ctrl_hdr; 139 | uint8_t entry_handle; 140 | } __attribute__((__packed__)); 141 | 142 | struct mctp_ctrl_resp_get_routing_table { 143 | struct mctp_ctrl_msg_hdr ctrl_hdr; 144 | uint8_t completion_code; 145 | uint8_t next_entry_handle; 146 | uint8_t number_of_entries; 147 | } __attribute__((__packed__)); 148 | 149 | struct get_routing_table_entry { 150 | uint8_t eid_range_size; 151 | uint8_t starting_eid; 152 | uint8_t entry_type; 153 | uint8_t phys_transport_binding_id; 154 | uint8_t phys_media_type_id; 155 | uint8_t phys_address_size; 156 | } __attribute__((__packed__)); 157 | 158 | struct mctp_ctrl_resp_prepare_discovery { 159 | struct mctp_ctrl_msg_hdr ctrl_hdr; 160 | uint8_t completion_code; 161 | } __attribute__((__packed__)); 162 | 163 | struct mctp_ctrl_resp_endpoint_discovery { 164 | struct mctp_ctrl_msg_hdr ctrl_hdr; 165 | uint8_t completion_code; 166 | } __attribute__((__packed__)); 167 | 168 | struct mctp_ctrl_cmd_resolve_endpoint_id { 169 | struct mctp_ctrl_msg_hdr ctrl_hdr; 170 | uint8_t eid; 171 | } __attribute__((__packed__)); 172 | 173 | struct mctp_ctrl_resp_resolve_endpoint_id { 174 | struct mctp_ctrl_msg_hdr ctrl_hdr; 175 | uint8_t completion_code; 176 | uint8_t eid; 177 | // ... uint8_t physical_address[N] 178 | } __attribute__((__packed__)); 179 | 180 | 181 | #define MCTP_CTRL_HDR_MSG_TYPE 0 182 | #define MCTP_CTRL_HDR_FLAG_REQUEST (1 << 7) 183 | #define MCTP_CTRL_HDR_FLAG_DGRAM (1 << 6) 184 | #define MCTP_CTRL_HDR_INSTANCE_ID_MASK 0x1F 185 | 186 | /* 187 | * MCTP Control Command IDs 188 | * See DSP0236 v1.3.0 Table 12. 189 | */ 190 | #define MCTP_CTRL_CMD_RESERVED 0x00 191 | #define MCTP_CTRL_CMD_SET_ENDPOINT_ID 0x01 192 | #define MCTP_CTRL_CMD_GET_ENDPOINT_ID 0x02 193 | #define MCTP_CTRL_CMD_GET_ENDPOINT_UUID 0x03 194 | #define MCTP_CTRL_CMD_GET_VERSION_SUPPORT 0x04 195 | #define MCTP_CTRL_CMD_GET_MESSAGE_TYPE_SUPPORT 0x05 196 | #define MCTP_CTRL_CMD_GET_VENDOR_MESSAGE_SUPPORT 0x06 197 | #define MCTP_CTRL_CMD_RESOLVE_ENDPOINT_ID 0x07 198 | #define MCTP_CTRL_CMD_ALLOCATE_ENDPOINT_IDS 0x08 199 | #define MCTP_CTRL_CMD_ROUTING_INFO_UPDATE 0x09 200 | #define MCTP_CTRL_CMD_GET_ROUTING_TABLE_ENTRIES 0x0A 201 | #define MCTP_CTRL_CMD_PREPARE_ENDPOINT_DISCOVERY 0x0B 202 | #define MCTP_CTRL_CMD_ENDPOINT_DISCOVERY 0x0C 203 | #define MCTP_CTRL_CMD_DISCOVERY_NOTIFY 0x0D 204 | #define MCTP_CTRL_CMD_GET_NETWORK_ID 0x0E 205 | #define MCTP_CTRL_CMD_QUERY_HOP 0x0F 206 | #define MCTP_CTRL_CMD_RESOLVE_UUID 0x10 207 | #define MCTP_CTRL_CMD_QUERY_RATE_LIMIT 0x11 208 | #define MCTP_CTRL_CMD_REQUEST_TX_RATE_LIMIT 0x12 209 | #define MCTP_CTRL_CMD_UPDATE_RATE_LIMIT 0x13 210 | #define MCTP_CTRL_CMD_QUERY_SUPPORTED_INTERFACES 0x14 211 | #define MCTP_CTRL_CMD_MAX 0x15 212 | /* 0xF0 - 0xFF are transport specific */ 213 | #define MCTP_CTRL_CMD_FIRST_TRANSPORT 0xF0 214 | #define MCTP_CTRL_CMD_LAST_TRANSPORT 0xFF 215 | 216 | /* 217 | * MCTP Control Completion Codes 218 | * See DSP0236 v1.3.0 Table 13. 219 | */ 220 | #define MCTP_CTRL_CC_SUCCESS 0x00 221 | #define MCTP_CTRL_CC_ERROR 0x01 222 | #define MCTP_CTRL_CC_ERROR_INVALID_DATA 0x02 223 | #define MCTP_CTRL_CC_ERROR_INVALID_LENGTH 0x03 224 | #define MCTP_CTRL_CC_ERROR_NOT_READY 0x04 225 | #define MCTP_CTRL_CC_ERROR_UNSUPPORTED_CMD 0x05 226 | /* 0x80 - 0xFF are command specific */ 227 | 228 | #define MCTP_CTRL_CC_GET_MCTP_VER_SUPPORT_UNSUPPORTED_TYPE 0x80 229 | 230 | /* MCTP Set Endpoint ID response fields 231 | * See DSP0236 v1.3.0 Table 14. 232 | */ 233 | 234 | #define MCTP_EID_ASSIGNMENT_STATUS_SHIFT 0x4 235 | #define MCTP_EID_ASSIGNMENT_STATUS_MASK 0x3 236 | #define SET_MCTP_EID_ASSIGNMENT_STATUS(field, status) \ 237 | ((field) |= (((status)&MCTP_EID_ASSIGNMENT_STATUS_MASK) \ 238 | << MCTP_EID_ASSIGNMENT_STATUS_SHIFT)) 239 | #define MCTP_SET_EID_ACCEPTED 0x0 240 | #define MCTP_SET_EID_REJECTED 0x1 241 | 242 | /* MCTP Physical Transport Binding identifiers 243 | * See DSP0239 v1.7.0 Table 3. 244 | */ 245 | #define MCTP_BINDING_RESERVED 0x00 246 | #define MCTP_BINDING_SMBUS 0x01 247 | #define MCTP_BINDING_PCIE 0x02 248 | #define MCTP_BINDING_USB 0x03 249 | #define MCTP_BINDING_KCS 0x04 250 | #define MCTP_BINDING_SERIAL 0x05 251 | #define MCTP_BINDING_VEDNOR 0x06 252 | 253 | #define MCTP_GET_VDM_SUPPORT_PCIE_FORMAT_ID 0x00 254 | #define MCTP_GET_VDM_SUPPORT_IANA_FORMAT_ID 0x01 255 | #define MCTP_GET_VDM_SUPPORT_NO_MORE_CAP_SET 0xFF 256 | 257 | #define MCTP_ENDPOINT_TYPE_SHIFT 4 258 | #define MCTP_ENDPOINT_TYPE_MASK 0x3 259 | #define MCTP_SIMPLE_ENDPOINT 0 260 | #define MCTP_BUS_OWNER_BRIDGE 1 261 | #define SET_ENDPOINT_TYPE(field, type) \ 262 | ((field) |= \ 263 | (((type)&MCTP_ENDPOINT_TYPE_MASK) << MCTP_ENDPOINT_TYPE_SHIFT)) 264 | 265 | #define MCTP_ENDPOINT_ID_TYPE_SHIFT 0 266 | #define MCTP_ENDPOINT_ID_TYPE_MASK 0x3 267 | #define MCTP_DYNAMIC_EID 0 268 | #define MCTP_STATIC_EID 1 269 | #define SET_ENDPOINT_ID_TYPE(field, type) \ 270 | ((field) |= \ 271 | (((type)&MCTP_ENDPOINT_ID_TYPE_MASK) << MCTP_ENDPOINT_ID_TYPE_SHIFT)) 272 | 273 | /* MCTP Routing Table entry types 274 | * See DSP0236 v1.3.0 Table 27. 275 | */ 276 | #define MCTP_ROUTING_ENTRY_PORT_SHIFT 0 277 | #define MCTP_ROUTING_ENTRY_PORT_MASK 0x1F 278 | #define SET_ROUTING_ENTRY_PORT(field, port) \ 279 | ((field) |= (((port)&MCTP_ROUTING_ENTRY_PORT_MASK) \ 280 | << MCTP_ROUTING_ENTRY_PORT_SHIFT)) 281 | #define GET_ROUTING_ENTRY_PORT(field) \ 282 | (((field) >> MCTP_ROUTING_ENTRY_PORT_SHIFT) & \ 283 | MCTP_ROUTING_ENTRY_PORT_MASK) 284 | 285 | #define MCTP_ROUTING_ENTRY_ASSIGNMENT_TYPE_SHIFT 5 286 | #define MCTP_ROUTING_ENTRY_ASSIGNMENT_TYPE_MASK 0x1 287 | #define MCTP_DYNAMIC_ASSIGNMENT 0 288 | #define MCTP_STATIC_ASSIGNMENT 1 289 | #define SET_ROUTING_ENTRY_ASSIGNMENT_TYPE(field, type) \ 290 | ((field) |= (((type)&MCTP_ROUTING_ENTRY_ASSIGNMENT_TYPE_MASK) \ 291 | << MCTP_ROUTING_ENTRY_ASSIGNMENT_TYPE_SHIFT)) 292 | #define GET_ROUTING_ENTRY_ASSIGNMENT_TYPE(field) \ 293 | (((field) >> MCTP_ROUTING_ENTRY_ASSIGNMENT_TYPE_SHIFT) & \ 294 | MCTP_ROUTING_ENTRY_ASSIGNMENT_TYPE_MASK) 295 | 296 | #define MCTP_ROUTING_ENTRY_TYPE_SHIFT 6 297 | #define MCTP_ROUTING_ENTRY_TYPE_MASK 0x3 298 | #define MCTP_ROUTING_ENTRY_ENDPOINT 0x00 299 | #define MCTP_ROUTING_ENTRY_BRIDGE_AND_ENDPOINTS 0x01 300 | #define MCTP_ROUTING_ENTRY_BRIDGE 0x02 301 | #define MCTP_ROUTING_ENTRY_ENDPOINTS 0x03 302 | #define SET_ROUTING_ENTRY_TYPE(field, type) \ 303 | ((field) |= (((type)&MCTP_ROUTING_ENTRY_TYPE_MASK) \ 304 | << MCTP_ROUTING_ENTRY_TYPE_SHIFT)) 305 | #define GET_ROUTING_ENTRY_TYPE(field) \ 306 | (((field) >> MCTP_ROUTING_ENTRY_TYPE_SHIFT) & \ 307 | MCTP_ROUTING_ENTRY_TYPE_MASK) 308 | -------------------------------------------------------------------------------- /src/mctp-echo.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 */ 2 | /* 3 | * mctp-echo: MCTP echo server, for testing. 4 | * 5 | * Copyright (c) 2021 Code Construct 6 | * Copyright (c) 2021 Google 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "mctp.h" 17 | 18 | // Code Construct allocation 19 | static const uint8_t VENDOR_TYPE_ECHO[3] = { 0xcc, 0xde, 0xf0 }; 20 | static const uint8_t MCTP_TYPE_VENDOR_PCIE = 0x7e; 21 | 22 | int main(void) 23 | { 24 | struct sockaddr_mctp addr; 25 | unsigned char *buf; 26 | socklen_t addrlen; 27 | size_t buflen; 28 | ssize_t len; 29 | int rc, sd; 30 | 31 | sd = socket(AF_MCTP, SOCK_DGRAM, 0); 32 | if (sd < 0) 33 | err(EXIT_FAILURE, "socket"); 34 | 35 | memset(&addr, 0, sizeof(addr)); 36 | addr.smctp_family = AF_MCTP; 37 | addr.smctp_network = MCTP_NET_ANY; 38 | addr.smctp_addr.s_addr = MCTP_ADDR_ANY; 39 | addr.smctp_type = MCTP_TYPE_VENDOR_PCIE; 40 | addr.smctp_tag = MCTP_TAG_OWNER; 41 | 42 | buflen = 0; 43 | buf = NULL; 44 | 45 | rc = bind(sd, (struct sockaddr *)&addr, sizeof(addr)); 46 | if (rc) 47 | err(EXIT_FAILURE, "bind"); 48 | 49 | for (;;) { 50 | len = recvfrom(sd, NULL, 0, MSG_PEEK | MSG_TRUNC, NULL, 0); 51 | if (len < 0) { 52 | warn("recvfrom(MSG_PEEK)"); 53 | continue; 54 | } 55 | 56 | if ((size_t)len > buflen) { 57 | buflen = len; 58 | buf = realloc(buf, buflen); 59 | if (!buf) 60 | err(EXIT_FAILURE, "realloc(%zd)", buflen); 61 | } 62 | 63 | addrlen = sizeof(addr); 64 | len = recvfrom(sd, buf, buflen, 0, 65 | (struct sockaddr *)&addr, &addrlen); 66 | if (len < 0) { 67 | warn("recvfrom"); 68 | continue; 69 | } 70 | 71 | if (addrlen != sizeof(addr)) { 72 | warnx("unknown address length %d, exp %zd", 73 | addrlen, sizeof(addr)); 74 | continue; 75 | } 76 | 77 | if (len < (ssize_t)sizeof(VENDOR_TYPE_ECHO)) { 78 | warnx("echo: short message from (net %d, eid %d), tag %d, type 0x%x: len %zd.\n", 79 | addr.smctp_network, addr.smctp_addr.s_addr, 80 | addr.smctp_tag, 81 | addr.smctp_type, 82 | len); 83 | continue; 84 | } 85 | 86 | if (memcmp(buf, VENDOR_TYPE_ECHO, sizeof(VENDOR_TYPE_ECHO)) != 0) { 87 | warnx("echo: unexpected vendor ID from (net %d, eid %d), tag %d, type 0x%x, len %zd.\n", 88 | addr.smctp_network, addr.smctp_addr.s_addr, 89 | addr.smctp_tag, 90 | addr.smctp_type, 91 | len); 92 | continue; 93 | } 94 | 95 | printf("echo: message from (net %d, eid %d), tag %d, type 0x%x: len %zd, responding\n", 96 | addr.smctp_network, addr.smctp_addr.s_addr, 97 | addr.smctp_tag, 98 | addr.smctp_type, 99 | len); 100 | 101 | addr.smctp_tag &= ~MCTP_TAG_OWNER; 102 | 103 | rc = sendto(sd, buf, len, 0, 104 | (struct sockaddr *)&addr, sizeof(addr)); 105 | 106 | if (rc != (int)len) { 107 | warn("sendto"); 108 | continue; 109 | } 110 | } 111 | 112 | return EXIT_SUCCESS; 113 | } 114 | -------------------------------------------------------------------------------- /src/mctp-netlink.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "mctp-netlink.h" 19 | #include "mctp.h" 20 | #include "mctp-util.h" 21 | #include "mctp-ops.h" 22 | 23 | struct linkmap_entry { 24 | int ifindex; 25 | char ifname[IFNAMSIZ+1]; 26 | uint32_t net; 27 | bool up; 28 | 29 | uint32_t min_mtu; 30 | uint32_t max_mtu; 31 | uint32_t hwaddr_len; 32 | 33 | mctp_eid_t *local_eids; 34 | size_t num_local; 35 | 36 | void *userdata; 37 | }; 38 | 39 | struct mctp_nl { 40 | // socket for queries 41 | int sd; 42 | // socket for monitor 43 | int sd_monitor; 44 | 45 | struct linkmap_entry *linkmap; 46 | size_t linkmap_count; 47 | size_t linkmap_alloc; 48 | bool verbose; 49 | 50 | // allows callers to silence printf of EEXIST returns. 51 | // TODO: this is a workaround, if more are required we should 52 | // rework how error messages are returned to callers. 53 | bool quiet_eexist; 54 | }; 55 | 56 | static int fill_local_addrs(mctp_nl *nl); 57 | static int fill_linkmap(mctp_nl *nl); 58 | static void sort_linkmap(mctp_nl *nl); 59 | static int linkmap_add_entry(mctp_nl *nl, struct ifinfomsg *info, 60 | const char *ifname, size_t ifname_len, uint32_t net, 61 | bool up, uint32_t min_mtu, uint32_t max_mtu, size_t hwaddr_len); 62 | static struct linkmap_entry *entry_byindex(const mctp_nl *nl, 63 | int index); 64 | 65 | static int open_nl_socket(void) 66 | { 67 | struct sockaddr_nl addr; 68 | int opt, rc, sd = -1; 69 | 70 | rc = mctp_ops.nl.socket(); 71 | if (rc < 0) 72 | goto err; 73 | sd = rc; 74 | memset(&addr, 0, sizeof(addr)); 75 | addr.nl_family = AF_NETLINK; 76 | rc = mctp_ops.nl.bind(sd, (struct sockaddr *)&addr, sizeof(addr)); 77 | if (rc) 78 | goto err; 79 | 80 | opt = 1; 81 | rc = mctp_ops.nl.setsockopt(sd, SOL_NETLINK, NETLINK_GET_STRICT_CHK, 82 | &opt, sizeof(opt)); 83 | if (rc) { 84 | rc = -errno; 85 | goto err; 86 | } 87 | 88 | opt = 1; 89 | rc = mctp_ops.nl.setsockopt(sd, SOL_NETLINK, NETLINK_EXT_ACK, &opt, 90 | sizeof(opt)); 91 | if (rc) 92 | { 93 | rc = -errno; 94 | goto err; 95 | } 96 | return sd; 97 | err: 98 | if (sd >= 0) { 99 | close(sd); 100 | } 101 | return rc; 102 | } 103 | 104 | mctp_nl * mctp_nl_new(bool verbose) 105 | { 106 | int rc; 107 | mctp_nl *nl; 108 | 109 | nl = calloc(1, sizeof(*nl)); 110 | if (!nl) { 111 | warn("calloc failed"); 112 | return NULL; 113 | } 114 | 115 | nl->sd = -1; 116 | nl->sd_monitor = -1; 117 | nl->verbose = verbose; 118 | nl->quiet_eexist = false; 119 | 120 | nl->sd = open_nl_socket(); 121 | if (nl->sd < 0) 122 | goto err; 123 | 124 | rc = fill_linkmap(nl); 125 | if (rc) 126 | goto err; 127 | 128 | return nl; 129 | err: 130 | mctp_nl_close(nl); 131 | return NULL; 132 | } 133 | 134 | /* Avoids printing warnings for EEXIST */ 135 | void mctp_nl_warn_eexist(mctp_nl *nl, bool warn) { 136 | nl->quiet_eexist = !warn; 137 | } 138 | 139 | static void free_linkmap(struct linkmap_entry *linkmap, size_t count) 140 | { 141 | for (size_t i = 0; i < count; i++) { 142 | free(linkmap[i].local_eids); 143 | } 144 | free(linkmap); 145 | } 146 | 147 | void mctp_nl_close(mctp_nl *nl) 148 | { 149 | free_linkmap(nl->linkmap, nl->linkmap_count); 150 | mctp_ops.nl.close(nl->sd); 151 | mctp_ops.nl.close(nl->sd_monitor); 152 | free(nl); 153 | } 154 | 155 | int mctp_nl_monitor(mctp_nl *nl, bool enable) 156 | { 157 | int rc; 158 | int opt; 159 | 160 | if (enable) { 161 | /* Already open */ 162 | if (nl->sd_monitor >= 0) 163 | return nl->sd_monitor; 164 | 165 | nl->sd_monitor = open_nl_socket(); 166 | if (nl->sd_monitor < 0) 167 | return nl->sd_monitor; 168 | 169 | opt = RTNLGRP_LINK; 170 | rc = mctp_ops.nl.setsockopt(nl->sd_monitor, SOL_NETLINK, 171 | NETLINK_ADD_MEMBERSHIP, 172 | &opt, sizeof(opt)); 173 | if (rc < 0) { 174 | rc = -errno; 175 | goto err; 176 | } 177 | 178 | opt = RTNLGRP_MCTP_IFADDR; 179 | rc = mctp_ops.nl.setsockopt(nl->sd_monitor, SOL_NETLINK, 180 | NETLINK_ADD_MEMBERSHIP, 181 | &opt, sizeof(opt)); 182 | if (rc < 0) { 183 | rc = -errno; 184 | if (errno == EINVAL) { 185 | warnx("Kernel doesn't support netlink monitor for MCTP addresses"); 186 | } 187 | goto err; 188 | } 189 | 190 | } else { 191 | close(nl->sd_monitor); 192 | nl->sd_monitor = -1; 193 | } 194 | 195 | return nl->sd_monitor; 196 | 197 | err: 198 | close(nl->sd_monitor); 199 | nl->sd_monitor = -1; 200 | return rc; 201 | } 202 | 203 | mctp_nl_change *push_change(mctp_nl_change **changes, size_t *psize) { 204 | size_t siz = *psize; 205 | siz++; 206 | *changes = realloc(*changes, siz * sizeof(**changes)); 207 | *psize = siz; 208 | return &(*changes)[siz-1]; 209 | } 210 | 211 | static void fill_eid_changes(const struct linkmap_entry *oe, 212 | const mctp_eid_t *old_eids, size_t num_old, 213 | const mctp_eid_t *new_eids, size_t num_new, 214 | mctp_nl_change **changes, size_t *psize) { 215 | 216 | // Iterate and match old/new eid lists 217 | for (size_t o = 0, n = 0; o < num_old || n < num_new; ) { 218 | mctp_nl_change *ch = NULL; 219 | 220 | // "beyond end of list" value 221 | int vo = 1000, vn = 1000; 222 | if (o < num_old) 223 | vo = old_eids[o]; 224 | if (n < num_new) 225 | vn = new_eids[n]; 226 | 227 | if (vo == vn) { 228 | // Same eid 229 | o++; 230 | n++; 231 | } else if (vn < vo) { 232 | // Added eid 233 | ch = push_change(changes, psize); 234 | ch->op = MCTP_NL_ADD_EID; 235 | ch->ifindex = oe->ifindex; 236 | ch->eid = vn; 237 | n++; 238 | } else if (vo < vn) { 239 | // Removed eid 240 | ch = push_change(changes, psize); 241 | ch->op = MCTP_NL_DEL_EID; 242 | ch->ifindex = oe->ifindex; 243 | ch->old_net = oe->net; 244 | ch->eid = vo; 245 | o++; 246 | } 247 | } 248 | } 249 | 250 | static void fill_link_changes(const struct linkmap_entry *old, size_t old_count, 251 | struct linkmap_entry *new, size_t new_count, 252 | mctp_nl_change **changes, size_t *num_changes) { 253 | 254 | size_t siz = 0; 255 | 256 | // iterate and match old/new interface lists 257 | for (size_t o = 0, n = 0; o < old_count || n < new_count; ) { 258 | const struct linkmap_entry *oe = &old[o]; 259 | struct linkmap_entry *ne = &new[n]; 260 | mctp_nl_change *ch = NULL; 261 | 262 | if (o >= old_count) 263 | oe = NULL; 264 | if (n >= new_count) 265 | ne = NULL; 266 | assert(oe || ne); 267 | 268 | if (oe && ne && oe->ifindex == ne->ifindex) { 269 | // Same link. 270 | ne->userdata = oe->userdata; 271 | if (oe->net == ne->net) { 272 | // Same net. Check for eid changes. 273 | fill_eid_changes(oe, 274 | oe->local_eids, oe->num_local, 275 | ne->local_eids, ne->num_local, 276 | changes, &siz); 277 | } else { 278 | // Net changed 279 | // First remove all old local EIDs. They can be re-added 280 | // in response to the later CHANGE_NET 281 | fill_eid_changes(oe, 282 | oe->local_eids, oe->num_local, 283 | NULL, 0, 284 | changes, &siz); 285 | 286 | ch = push_change(changes, &siz); 287 | ch->op = MCTP_NL_CHANGE_NET; 288 | ch->ifindex = ne->ifindex; 289 | ch->old_net = oe->net; 290 | ch->link_userdata = oe->userdata; 291 | } 292 | 293 | if (oe->up != ne->up) { 294 | ch = push_change(changes, &siz); 295 | ch->op = MCTP_NL_CHANGE_UP; 296 | ch->ifindex = ne->ifindex; 297 | ch->old_up = oe->up; 298 | ch->link_userdata = oe->userdata; 299 | } 300 | o++; 301 | n++; 302 | } else if (!oe || (ne && ne->ifindex < oe->ifindex)) { 303 | // Added link 304 | ch = push_change(changes, &siz); 305 | ch->op = MCTP_NL_ADD_LINK; 306 | ch->ifindex = ne->ifindex; 307 | n++; 308 | } else if (!ne || (oe && oe->ifindex < ne->ifindex)) { 309 | // Deleted link 310 | 311 | // Record each EID deletion as a change, since the old 312 | // EID list is deleted before this change list is returned 313 | fill_eid_changes(oe, oe->local_eids, oe->num_local, 314 | NULL, 0, 315 | changes, &siz); 316 | // Delete the link itself 317 | ch = push_change(changes, &siz); 318 | ch->op = MCTP_NL_DEL_LINK; 319 | ch->ifindex = oe->ifindex; 320 | ch->old_net = oe->net; 321 | ch->link_userdata = oe->userdata; 322 | o++; 323 | } 324 | } 325 | *num_changes = siz; 326 | } 327 | 328 | void mctp_nl_changes_dump(mctp_nl *nl, mctp_nl_change *changes, size_t num_changes) { 329 | const char* ops[MCTP_NL_OP_COUNT] = { 330 | "ADD_LINK", "DEL_LINK", "CHANGE_NET", "CHANGE_UP", 331 | "ADD_EID", "DEL_EID", 332 | }; 333 | 334 | fprintf(stderr, "%zu changes:\n", num_changes); 335 | for (size_t i = 0; i < num_changes; i++) { 336 | mctp_nl_change *ch = &changes[i]; 337 | const char* ifname = mctp_nl_if_byindex(nl, ch->ifindex); 338 | if (!ifname) 339 | ifname = "deleted"; 340 | fprintf(stderr, "%3zd %-12s ifindex %3d (%-20s) eid %3d old_net %4d old_up %d\n", 341 | i, ops[ch->op], ch->ifindex, ifname, ch->eid, 342 | ch->old_net, ch->old_up); 343 | } 344 | 345 | } 346 | 347 | int mctp_nl_handle_monitor(mctp_nl *nl, mctp_nl_change **changes, size_t *num_changes) 348 | { 349 | int rc; 350 | struct linkmap_entry *old_linkmap; 351 | size_t old_count; 352 | size_t old_alloc; 353 | 354 | *changes = NULL; 355 | *num_changes = 0; 356 | 357 | if (nl->sd_monitor < 0) { 358 | warnx("%s without mctp_nl_monitor", __func__); 359 | return -EBADF; 360 | } 361 | 362 | // Drain the socket 363 | while (recv(nl->sd_monitor, NULL, 0, MSG_TRUNC|MSG_DONTWAIT) > 0) {} 364 | 365 | old_linkmap = nl->linkmap; 366 | old_count = nl->linkmap_count; 367 | old_alloc = nl->linkmap_alloc; 368 | 369 | nl->linkmap = NULL; 370 | nl->linkmap_count = nl->linkmap_alloc = 0; 371 | 372 | rc = fill_linkmap(nl); 373 | if (rc) 374 | goto err; 375 | 376 | fill_link_changes(old_linkmap, old_count, 377 | nl->linkmap, nl->linkmap_count, 378 | changes, num_changes); 379 | 380 | free_linkmap(old_linkmap, old_count); 381 | return 0; 382 | 383 | err: 384 | // restore original 385 | free_linkmap(nl->linkmap, nl->linkmap_count); 386 | nl->linkmap = old_linkmap; 387 | nl->linkmap_count = old_count; 388 | nl->linkmap_alloc = old_alloc; 389 | 390 | return rc; 391 | } 392 | 393 | /* Pointer returned on match, optionally returns ret_len */ 394 | void* mctp_get_rtnlmsg_attr(int rta_type, struct rtattr *rta, size_t len, 395 | size_t *ret_len) 396 | { 397 | for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { 398 | if (rta->rta_type == rta_type) { 399 | if (ret_len) { 400 | *ret_len = RTA_PAYLOAD(rta); 401 | } 402 | return RTA_DATA(rta); 403 | } 404 | } 405 | if (ret_len) { 406 | *ret_len = 0; 407 | } 408 | return NULL; 409 | } 410 | 411 | bool mctp_get_rtnlmsg_attr_u32(int rta_type, struct rtattr *rta, size_t len, 412 | uint32_t *ret_value) { 413 | size_t plen; 414 | uint32_t *p = mctp_get_rtnlmsg_attr(rta_type, rta, len, &plen); 415 | if (p) { 416 | if (plen == sizeof(*ret_value)) { 417 | *ret_value = *p; 418 | return true; 419 | } else { 420 | warnx("Unexpected attribute length %zu for type %d", 421 | plen, rta_type); 422 | } 423 | } 424 | return false; 425 | } 426 | 427 | bool mctp_get_rtnlmsg_attr_u8(int rta_type, struct rtattr *rta, size_t len, 428 | uint8_t *ret_value) { 429 | size_t plen; 430 | uint8_t *p = mctp_get_rtnlmsg_attr(rta_type, rta, len, &plen); 431 | if (p) { 432 | if (plen == sizeof(*ret_value)) { 433 | *ret_value = *p; 434 | return true; 435 | } else { 436 | warnx("Unexpected attribute length %zu for type %d", 437 | plen, rta_type); 438 | } 439 | } 440 | return false; 441 | } 442 | 443 | /* Returns the space used */ 444 | size_t mctp_put_rtnlmsg_attr(struct rtattr **prta, size_t *rta_len, 445 | unsigned short type, const void* value, size_t val_len) 446 | { 447 | struct rtattr *rta = *prta; 448 | rta->rta_type = type; 449 | rta->rta_len = RTA_LENGTH(val_len); 450 | memcpy(RTA_DATA(rta), value, val_len); 451 | *prta = RTA_NEXT(*prta, *rta_len); 452 | return RTA_SPACE(val_len); 453 | } 454 | 455 | 456 | static void dump_nlmsg_hdr(struct nlmsghdr *hdr, const char *indent) 457 | { 458 | printf("%slen: %d\n", indent, hdr->nlmsg_len); 459 | printf("%stype: %d\n", indent, hdr->nlmsg_type); 460 | printf("%sflags: %d\n", indent, hdr->nlmsg_flags); 461 | printf("%sseq: %d\n", indent, hdr->nlmsg_seq); 462 | printf("%spid: %d\n", indent, hdr->nlmsg_pid); 463 | } 464 | 465 | void mctp_display_nlmsg_error(const mctp_nl *nl, struct nlmsgerr *errmsg, size_t errlen) 466 | { 467 | size_t rta_len, errstrlen; 468 | struct rtattr *rta; 469 | char* errstr; 470 | 471 | if (errlen < sizeof(*errmsg)) { 472 | printf("short error message (%zu bytes)\n", errlen); 473 | return; 474 | } 475 | // skip the whole errmsg->msg and following payload 476 | rta = (void *)errmsg + offsetof(struct nlmsgerr, msg) + errmsg->msg.nlmsg_len; 477 | rta_len = (void*)errmsg + errlen - (void*)rta; 478 | 479 | if (!(nl->quiet_eexist && errmsg->error == -EEXIST)) 480 | printf("Error from kernel: %s (%d)\n", strerror(-errmsg->error), errmsg->error); 481 | errstr = mctp_get_rtnlmsg_attr(NLMSGERR_ATTR_MSG, rta, rta_len, &errstrlen); 482 | if (errstr) { 483 | errstrlen = strnlen(errstr, errstrlen); 484 | printf(" %*s\n", (int)errstrlen, errstr); 485 | } 486 | } 487 | 488 | void mctp_dump_nlmsg_error(const mctp_nl *nl, struct nlmsgerr *errmsg, size_t errlen) 489 | { 490 | printf("error:\n"); 491 | mctp_display_nlmsg_error(nl, errmsg, errlen); 492 | printf(" error packet dump:\n"); 493 | mctp_hexdump(errmsg, errlen, " "); 494 | printf(" error in reply to message:\n"); 495 | dump_nlmsg_hdr(&errmsg->msg, " "); 496 | } 497 | 498 | /* Receive and handle a NLMSG_ERROR and return the error code */ 499 | static int handle_nlmsg_ack(mctp_nl *nl) 500 | { 501 | char resp[4096]; 502 | struct nlmsghdr *msg; 503 | int rc; 504 | size_t len; 505 | 506 | rc = mctp_ops.nl.recvfrom(nl->sd, resp, sizeof(resp), 0, NULL, NULL); 507 | if (rc < 0) 508 | return rc; 509 | len = rc; 510 | msg = (void*)resp; 511 | 512 | rc = 0; 513 | for (; NLMSG_OK(msg, len); msg = NLMSG_NEXT(msg, len)) { 514 | if (msg->nlmsg_type == NLMSG_ERROR) { 515 | struct nlmsgerr *errmsg = NLMSG_DATA(msg); 516 | size_t errlen = NLMSG_PAYLOAD(msg, 0); 517 | if (errmsg->error) { 518 | if (nl->verbose) 519 | mctp_dump_nlmsg_error(nl, errmsg, errlen); 520 | else 521 | mctp_display_nlmsg_error(nl, errmsg, errlen); 522 | rc = errmsg->error; 523 | } 524 | } else { 525 | warnx("Received unexpected message type %d instead of status", 526 | msg->nlmsg_type); 527 | if (nl->verbose) { 528 | mctp_hexdump(msg, msg->nlmsg_len, " "); 529 | } 530 | } 531 | } 532 | return rc; 533 | } 534 | 535 | /* 536 | * Note that only rtnl_doit_func() handlers like RTM_NEWADDR 537 | * will automatically return a response to NLM_F_ACK, other requests 538 | * shouldn't have it set. 539 | */ 540 | int mctp_nl_send(mctp_nl *nl, struct nlmsghdr *msg) 541 | { 542 | struct sockaddr_nl addr; 543 | int rc; 544 | 545 | memset(&addr, 0, sizeof(addr)); 546 | addr.nl_family = AF_NETLINK; 547 | addr.nl_pid = 0; 548 | 549 | rc = mctp_ops.nl.sendto(nl->sd, msg, msg->nlmsg_len, 0, 550 | (struct sockaddr *)&addr, sizeof(addr)); 551 | if (rc < 0) 552 | return rc; 553 | 554 | if (rc != (int)msg->nlmsg_len) 555 | warnx("sendto: short send (%d, expected %d)", 556 | rc, msg->nlmsg_len); 557 | 558 | if (msg->nlmsg_flags & NLM_F_ACK) { 559 | return handle_nlmsg_ack(nl); 560 | } 561 | return 0; 562 | } 563 | 564 | /* Returns if the last message is NLMSG_DONE, or isn't multipart */ 565 | static bool nlmsgs_are_done(struct nlmsghdr *msg, size_t len) 566 | { 567 | bool done = false; 568 | for (; NLMSG_OK(msg, len); msg = NLMSG_NEXT(msg, len)) { 569 | if (done) 570 | warnx("received message after NLMSG_DONE"); 571 | done = (msg->nlmsg_type == NLMSG_DONE) 572 | || !(msg->nlmsg_flags & NLM_F_MULTI); 573 | } 574 | return done; 575 | } 576 | 577 | /* respp is optional for returned buffer, length is set in resp+lenp */ 578 | int mctp_nl_recv_all(mctp_nl *nl, int sd, 579 | struct nlmsghdr **respp, size_t *resp_lenp) 580 | { 581 | void *respbuf = NULL; 582 | struct nlmsghdr *resp = NULL; 583 | struct sockaddr_nl addr; 584 | socklen_t addrlen; 585 | size_t newlen, readlen, pos; 586 | bool done; 587 | int rc; 588 | 589 | if (respp) { 590 | *respp = NULL; 591 | *resp_lenp = 0; 592 | } 593 | 594 | pos = 0; 595 | done = false; 596 | 597 | // read all the responses into a single buffer 598 | while (!done) { 599 | rc = mctp_ops.nl.recvfrom(sd, NULL, 0, MSG_PEEK|MSG_TRUNC, 600 | NULL, 0); 601 | if (rc < 0) { 602 | warnx("recvfrom(MSG_PEEK)"); 603 | rc = -errno; 604 | goto out; 605 | } 606 | 607 | if (rc == 0) { 608 | if (pos == 0) { 609 | warnx("No response to message"); 610 | return -1; 611 | } else { 612 | // No more datagrams 613 | break; 614 | } 615 | } 616 | 617 | readlen = rc; 618 | newlen = pos + readlen; 619 | respbuf = realloc(respbuf, newlen); 620 | if (!respbuf) 621 | { 622 | warnx("allocation of %zu failed", newlen); 623 | rc = -ENOMEM; 624 | goto out; 625 | } 626 | resp = respbuf + pos; 627 | 628 | addrlen = sizeof(addr); 629 | rc = mctp_ops.nl.recvfrom(sd, resp, readlen, MSG_TRUNC, 630 | (struct sockaddr *)&addr, &addrlen); 631 | if (rc < 0) { 632 | warnx("recvfrom(MSG_PEEK)"); 633 | rc = -errno; 634 | goto out; 635 | } 636 | 637 | if ((size_t)rc > readlen) 638 | warnx("recvfrom: extra message data? (got %d, exp %zd)", 639 | rc, readlen); 640 | 641 | if (addrlen != sizeof(addr)) { 642 | warn("recvfrom: weird addrlen? (%d, expecting %zd)", addrlen, 643 | sizeof(addr)); 644 | } 645 | 646 | done = nlmsgs_are_done(resp, rc); 647 | pos = min(newlen, pos+rc); 648 | } 649 | 650 | rc = 0; 651 | out: 652 | if (rc == 0 && respp) { 653 | *respp = respbuf; 654 | *resp_lenp = pos; 655 | } else { 656 | free(respbuf); 657 | } 658 | 659 | return rc; 660 | } 661 | 662 | /* respp is optional for returned buffer, length is set in resp+lenp */ 663 | int mctp_nl_query(mctp_nl *nl, struct nlmsghdr *msg, 664 | struct nlmsghdr **respp, size_t *resp_lenp) 665 | { 666 | int rc; 667 | 668 | if (respp) { 669 | *respp = NULL; 670 | *resp_lenp = 0; 671 | } 672 | 673 | rc = mctp_nl_send(nl, msg); 674 | if (rc) 675 | return rc; 676 | 677 | return mctp_nl_recv_all(nl, nl->sd, respp, resp_lenp); 678 | } 679 | 680 | static int parse_getlink_dump(mctp_nl *nl, struct nlmsghdr *nlh, uint32_t len) 681 | { 682 | struct ifinfomsg *info; 683 | 684 | for (; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) { 685 | struct rtattr *rta = NULL, *rt_nest = NULL, *rt_mctp = NULL; 686 | char *ifname = NULL; 687 | size_t ifname_len, rlen, nlen, mlen, hwaddr_len; 688 | uint32_t net, min_mtu = 0, max_mtu = 0; 689 | bool up; 690 | 691 | if (nlh->nlmsg_type == NLMSG_DONE) 692 | return 0; 693 | 694 | if (nlh->nlmsg_type == NLMSG_ERROR) 695 | return -1; 696 | 697 | if (NLMSG_PAYLOAD(nlh, 0) < sizeof(*info)) 698 | return -1; 699 | 700 | info = NLMSG_DATA(nlh); 701 | if (!info->ifi_index) 702 | continue; 703 | 704 | rta = (void *)(info + 1); 705 | rlen = NLMSG_PAYLOAD(nlh, sizeof(*info)); 706 | 707 | rt_nest = mctp_get_rtnlmsg_attr(IFLA_AF_SPEC, rta, rlen, &nlen); 708 | if (rt_nest) { 709 | rt_mctp = mctp_get_rtnlmsg_attr(AF_MCTP, rt_nest, nlen, &mlen); 710 | } 711 | if (!rt_mctp) { 712 | /* Skip non-MCTP interfaces */ 713 | continue; 714 | } 715 | 716 | ifname = mctp_get_rtnlmsg_attr(IFLA_IFNAME, rta, rlen, &ifname_len); 717 | if (!ifname) { 718 | warnx("no ifname?"); 719 | continue; 720 | } 721 | 722 | ifname_len = strnlen(ifname, ifname_len); 723 | if (!mctp_get_rtnlmsg_attr_u32(IFLA_MCTP_NET, rt_mctp, mlen, &net)) { 724 | warnx("Missing IFLA_MCTP_NET for %s", ifname); 725 | continue; 726 | } 727 | 728 | if (!mctp_get_rtnlmsg_attr_u32(IFLA_MIN_MTU, rta, rlen, &min_mtu)) { 729 | warnx("Missing IFLA_MIN_MTU for %s", ifname); 730 | continue; 731 | } 732 | 733 | if (!mctp_get_rtnlmsg_attr_u32(IFLA_MAX_MTU, rta, rlen, &max_mtu)) { 734 | warnx("Missing IFLA_MAX_MTU for %s", ifname); 735 | continue; 736 | } 737 | 738 | /* Treat missing address as 0 length */ 739 | hwaddr_len = 0; 740 | mctp_get_rtnlmsg_attr(IFLA_ADDRESS, rta, rlen, &hwaddr_len); 741 | 742 | /* TODO: media type */ 743 | 744 | up = info->ifi_flags & IFF_UP; 745 | linkmap_add_entry(nl, info, ifname, ifname_len, net, up, 746 | min_mtu, max_mtu, hwaddr_len); 747 | } 748 | // Not done. 749 | return 1; 750 | } 751 | 752 | static int fill_linkmap(mctp_nl *nl) 753 | { 754 | struct { 755 | struct nlmsghdr nh; 756 | struct ifinfomsg ifmsg; 757 | } msg = { 0 }; 758 | struct sockaddr_nl addr; 759 | socklen_t addrlen; 760 | size_t buflen; 761 | void *buf; 762 | int rc; 763 | 764 | msg.nh.nlmsg_len = NLMSG_LENGTH(sizeof(msg.ifmsg)); 765 | msg.nh.nlmsg_type = RTM_GETLINK; 766 | msg.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; 767 | msg.ifmsg.ifi_family = AF_MCTP; 768 | 769 | rc = mctp_nl_send(nl, &msg.nh); 770 | if (rc) 771 | return rc; 772 | 773 | buf = NULL; 774 | buflen = 0; 775 | addrlen = sizeof(addr); 776 | 777 | for (;;) { 778 | rc = mctp_ops.nl.recvfrom(nl->sd, NULL, 0, MSG_TRUNC | MSG_PEEK, 779 | NULL, NULL); 780 | if (rc < 0) { 781 | warn("recvfrom(MSG_PEEK)"); 782 | break; 783 | } 784 | 785 | if (!rc) 786 | break; 787 | 788 | if ((size_t)rc > buflen) { 789 | char *tmp; 790 | buflen = rc; 791 | tmp = realloc(buf, buflen); 792 | if (!tmp) { 793 | rc = -1; 794 | break; 795 | } 796 | buf = tmp; 797 | } 798 | 799 | rc = mctp_ops.nl.recvfrom(nl->sd, buf, buflen, 0, 800 | (struct sockaddr *)&addr, &addrlen); 801 | if (rc < 0) { 802 | warn("recvfrom()"); 803 | break; 804 | } 805 | 806 | rc = parse_getlink_dump(nl, buf, rc); 807 | if (rc <= 0) 808 | break; 809 | } 810 | 811 | if (rc == 0) 812 | rc = fill_local_addrs(nl); 813 | 814 | sort_linkmap(nl); 815 | 816 | free(buf); 817 | return rc; 818 | } 819 | 820 | static int fill_local_addrs(mctp_nl *nl) 821 | { 822 | int rc; 823 | struct nlmsghdr *resp = NULL, *rp = NULL; 824 | size_t len; 825 | struct { 826 | struct nlmsghdr nh; 827 | struct ifaddrmsg ifmsg; 828 | struct rtattr rta; 829 | char ifname[16]; 830 | } msg = {0}; 831 | 832 | msg.nh.nlmsg_len = NLMSG_LENGTH(sizeof(msg.ifmsg)); 833 | 834 | msg.nh.nlmsg_type = RTM_GETADDR; 835 | msg.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; 836 | msg.ifmsg.ifa_family = AF_MCTP; 837 | 838 | rc = mctp_nl_query(nl, &msg.nh, &resp, &len); 839 | if (rc) 840 | return rc; 841 | 842 | rp = resp; 843 | for (; NLMSG_OK(rp, len); rp = NLMSG_NEXT(rp, len)) { 844 | struct ifaddrmsg *ifa = NULL; 845 | size_t rta_len, ifalen; 846 | struct rtattr *rta = NULL; 847 | void* tmp; 848 | struct linkmap_entry* entry = NULL; 849 | mctp_eid_t eid; 850 | 851 | if (rp->nlmsg_type != RTM_NEWADDR) 852 | continue; 853 | ifa = NLMSG_DATA(rp); 854 | ifalen = NLMSG_PAYLOAD(rp, 0); 855 | if (ifalen < sizeof(*ifa)) { 856 | warnx("kernel returned short ifaddrmsg"); 857 | continue; 858 | } 859 | if (ifa->ifa_family != AF_MCTP) 860 | continue; 861 | rta = (void *)(ifa + 1); 862 | rta_len = ifalen - sizeof(*ifa); 863 | if (!mctp_get_rtnlmsg_attr_u8(IFA_LOCAL, rta, rta_len, &eid)) 864 | continue; 865 | 866 | entry = entry_byindex(nl, ifa->ifa_index); 867 | if (!entry) { 868 | warnx("kernel returned address for unknown if %d", ifa->ifa_index); 869 | continue; 870 | } 871 | tmp = realloc(entry->local_eids, 872 | (entry->num_local+1) * sizeof(*entry->local_eids)); 873 | if (!tmp) 874 | continue; 875 | entry->local_eids = tmp; 876 | entry->local_eids[entry->num_local] = eid; 877 | entry->num_local++; 878 | } 879 | 880 | free(resp); 881 | return rc; 882 | } 883 | 884 | static int cmp_eid(const void* a, const void* b) 885 | { 886 | const mctp_eid_t *ea = a, *eb = b; 887 | return (int)(*ea) - (int)(*eb); 888 | } 889 | 890 | static int cmp_ifindex(const void* a, const void* b) 891 | { 892 | const struct linkmap_entry *ea = a, *eb = b; 893 | return ea->ifindex - eb->ifindex; 894 | } 895 | 896 | static void sort_linkmap(mctp_nl *nl) 897 | { 898 | size_t i; 899 | 900 | qsort(nl->linkmap, nl->linkmap_count, sizeof(*nl->linkmap), cmp_ifindex); 901 | 902 | for (i = 0; i < nl->linkmap_count; i++) { 903 | struct linkmap_entry *entry = &nl->linkmap[i]; 904 | qsort(entry->local_eids, entry->num_local, 905 | sizeof(mctp_eid_t), cmp_eid); 906 | } 907 | } 908 | 909 | void mctp_nl_linkmap_dump(const mctp_nl *nl) 910 | { 911 | size_t i, j; 912 | 913 | fprintf(stderr, "linkmap\n"); 914 | for (i = 0; i < nl->linkmap_count; i++) { 915 | struct linkmap_entry *entry = &nl->linkmap[i]; 916 | const char* updown = entry->up ? "up" : "DOWN"; 917 | fprintf(stderr, " %2d: %s, net %d %s local addrs [", 918 | entry->ifindex, entry->ifname, 919 | entry->net, updown); 920 | for (j = 0; j < entry->num_local; j++) { 921 | if (j != 0) 922 | fprintf(stderr, ", "); 923 | fprintf(stderr, "%d", entry->local_eids[j]); 924 | } 925 | fprintf(stderr, "]\n"); 926 | } 927 | } 928 | 929 | int mctp_nl_ifindex_byname(const mctp_nl *nl, const char *ifname) 930 | { 931 | size_t i; 932 | 933 | for (i = 0; i < nl->linkmap_count; i++) { 934 | struct linkmap_entry *entry = &nl->linkmap[i]; 935 | if (!strcmp(entry->ifname, ifname)) 936 | return entry->ifindex; 937 | } 938 | 939 | return 0; 940 | } 941 | 942 | const char* mctp_nl_if_byindex(const mctp_nl *nl, int index) 943 | { 944 | struct linkmap_entry *entry = entry_byindex(nl, index); 945 | if (entry) 946 | return entry->ifname; 947 | return NULL; 948 | } 949 | 950 | uint32_t mctp_nl_net_byindex(const mctp_nl *nl, int index) 951 | { 952 | struct linkmap_entry *entry = entry_byindex(nl, index); 953 | if (entry) 954 | return entry->net; 955 | return 0; 956 | } 957 | 958 | int mctp_nl_set_link_userdata(mctp_nl *nl, int ifindex, void *userdata) 959 | { 960 | struct linkmap_entry *entry = entry_byindex(nl, ifindex); 961 | if (!entry) 962 | return -1; 963 | 964 | entry->userdata = userdata; 965 | return 0; 966 | } 967 | 968 | void *mctp_nl_get_link_userdata(const mctp_nl *nl, int ifindex) 969 | { 970 | struct linkmap_entry *entry = entry_byindex(nl, ifindex); 971 | 972 | return entry ? entry->userdata : NULL; 973 | } 974 | 975 | void *mctp_nl_get_link_userdata_byname(const mctp_nl *nl, const char *ifname) 976 | { 977 | size_t i; 978 | 979 | for (i = 0; i < nl->linkmap_count; i++) { 980 | struct linkmap_entry *entry = &nl->linkmap[i]; 981 | if (!strcmp(entry->ifname, ifname)) 982 | return entry->userdata; 983 | } 984 | 985 | return NULL; 986 | } 987 | 988 | bool mctp_nl_up_byindex(const mctp_nl *nl, int index) 989 | { 990 | struct linkmap_entry *entry = entry_byindex(nl, index); 991 | if (entry) 992 | return entry->up; 993 | return false; 994 | } 995 | 996 | uint32_t mctp_nl_min_mtu_byindex(const mctp_nl *nl, int index) 997 | { 998 | struct linkmap_entry *entry = entry_byindex(nl, index); 999 | if (entry) 1000 | return entry->min_mtu; 1001 | return 0; 1002 | } 1003 | 1004 | uint32_t mctp_nl_max_mtu_byindex(const mctp_nl *nl, int index) 1005 | { 1006 | struct linkmap_entry *entry = entry_byindex(nl, index); 1007 | if (entry) 1008 | return entry->max_mtu; 1009 | return 0; 1010 | } 1011 | 1012 | int mctp_nl_hwaddr_len_byindex(const mctp_nl *nl, int index, size_t *ret_hwaddr_len) 1013 | { 1014 | struct linkmap_entry *entry = entry_byindex(nl, index); 1015 | if (!entry) { 1016 | return -ENOENT; 1017 | } 1018 | *ret_hwaddr_len = entry->hwaddr_len; 1019 | return 0; 1020 | } 1021 | 1022 | mctp_eid_t *mctp_nl_addrs_byindex(const mctp_nl *nl, int index, 1023 | size_t *ret_num) 1024 | { 1025 | struct linkmap_entry *entry = entry_byindex(nl, index); 1026 | mctp_eid_t *ret; 1027 | 1028 | *ret_num = 0; 1029 | if (!entry) 1030 | return NULL; 1031 | ret = malloc(entry->num_local); 1032 | if (!ret) 1033 | return NULL; 1034 | memcpy(ret, entry->local_eids, entry->num_local); 1035 | *ret_num = entry->num_local; 1036 | return ret; 1037 | } 1038 | 1039 | static struct linkmap_entry *entry_byindex(const mctp_nl *nl, 1040 | int index) 1041 | { 1042 | size_t i; 1043 | 1044 | for (i = 0; i < nl->linkmap_count; i++) { 1045 | struct linkmap_entry *entry = &nl->linkmap[i]; 1046 | if (entry->ifindex == index) { 1047 | return entry; 1048 | } 1049 | } 1050 | return NULL; 1051 | } 1052 | 1053 | uint32_t *mctp_nl_net_list(const mctp_nl *nl, size_t *ret_num_nets) 1054 | { 1055 | uint32_t *nets = NULL; 1056 | size_t i, j; 1057 | 1058 | *ret_num_nets = 0; 1059 | // allocation may be oversized, that's OK 1060 | nets = calloc(nl->linkmap_count, sizeof(uint32_t)); 1061 | if (!nets) { 1062 | warnx("Allocation failed"); 1063 | return NULL; 1064 | } 1065 | 1066 | for (i = 0; i < nl->linkmap_count; i++) { 1067 | for (j = 0; j < nl->linkmap_count; j++) { 1068 | if (nets[j] == nl->linkmap[i].net) { 1069 | // Already added 1070 | break; 1071 | } 1072 | if (nets[j] == 0) { 1073 | // End of the list, add it 1074 | nets[j] = nl->linkmap[i].net; 1075 | (*ret_num_nets)++; 1076 | break; 1077 | } 1078 | } 1079 | } 1080 | return nets; 1081 | } 1082 | 1083 | int *mctp_nl_if_list(const mctp_nl *nl, size_t *ret_num_ifs) 1084 | { 1085 | size_t i; 1086 | int *ifs; 1087 | 1088 | *ret_num_ifs = 0; 1089 | ifs = malloc(sizeof(int) * nl->linkmap_count); 1090 | if (!ifs) 1091 | return NULL; 1092 | for (i = 0; i < nl->linkmap_count; i++) { 1093 | ifs[i] = nl->linkmap[i].ifindex; 1094 | } 1095 | *ret_num_ifs = nl->linkmap_count; 1096 | return ifs; 1097 | } 1098 | 1099 | static int linkmap_add_entry(mctp_nl *nl, struct ifinfomsg *info, 1100 | const char *ifname, size_t ifname_len, uint32_t net, 1101 | bool up, uint32_t min_mtu, uint32_t max_mtu, size_t hwaddr_len) 1102 | { 1103 | struct linkmap_entry *entry; 1104 | size_t newsz; 1105 | void *tmp; 1106 | int idx; 1107 | 1108 | if (ifname_len > IFNAMSIZ) { 1109 | warnx("linkmap, too long ifname '%*s'", (int)ifname_len, ifname); 1110 | return -1; 1111 | } 1112 | 1113 | if (net <= 0) { 1114 | warnx("Bad network ID %d for %*s", net, (int)ifname_len, ifname); 1115 | return -1; 1116 | } 1117 | 1118 | idx = nl->linkmap_count++; 1119 | 1120 | if (nl->linkmap_count > nl->linkmap_alloc) { 1121 | newsz = max(nl->linkmap_alloc * 2, 1); 1122 | tmp = realloc(nl->linkmap, newsz * sizeof(*nl->linkmap)); 1123 | if (!tmp) { 1124 | warnx("Error allocating linkmap memory"); 1125 | return -1; 1126 | } 1127 | nl->linkmap_alloc = newsz; 1128 | nl->linkmap = tmp; 1129 | } 1130 | 1131 | entry = &nl->linkmap[idx]; 1132 | memset(entry, 0, sizeof(*entry)); 1133 | snprintf(entry->ifname, IFNAMSIZ, "%*s", (int)ifname_len, ifname); 1134 | entry->ifindex = info->ifi_index; 1135 | entry->net = net; 1136 | entry->up = up; 1137 | entry->max_mtu = max_mtu; 1138 | entry->min_mtu = min_mtu; 1139 | entry->hwaddr_len = hwaddr_len; 1140 | return 0; 1141 | } 1142 | 1143 | /* Common parts of RTM_NEWROUTE and RTM_DELROUTE */ 1144 | struct mctp_rtalter_msg { 1145 | struct nlmsghdr nh; 1146 | struct rtmsg rtmsg; 1147 | uint8_t rta_buff[ 1148 | RTA_SPACE(sizeof(mctp_eid_t)) + // eid 1149 | RTA_SPACE(sizeof(int)) + // ifindex 1150 | 100 // space for MTU, nexthop etc 1151 | ]; 1152 | }; 1153 | static int fill_rtalter_args(struct mctp_nl *nl, struct mctp_rtalter_msg *msg, 1154 | struct rtattr **prta, size_t *prta_len, 1155 | mctp_eid_t eid, const char* linkstr) 1156 | { 1157 | int ifindex; 1158 | struct rtattr *rta; 1159 | size_t rta_len; 1160 | 1161 | ifindex = mctp_nl_ifindex_byname(nl, linkstr); 1162 | if (!ifindex) { 1163 | warnx("invalid device %s", linkstr); 1164 | return -1; 1165 | } 1166 | 1167 | memset(msg, 0x0, sizeof(*msg)); 1168 | msg->nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; 1169 | 1170 | msg->rtmsg.rtm_family = AF_MCTP; 1171 | msg->rtmsg.rtm_type = RTN_UNICAST; 1172 | // TODO add eid range handling 1173 | msg->rtmsg.rtm_dst_len = 0; 1174 | msg->rtmsg.rtm_type = RTN_UNICAST; 1175 | 1176 | msg->nh.nlmsg_len = NLMSG_LENGTH(sizeof(msg->rtmsg)); 1177 | rta_len = sizeof(msg->rta_buff); 1178 | rta = (void*)msg->rta_buff; 1179 | 1180 | msg->nh.nlmsg_len += mctp_put_rtnlmsg_attr(&rta, &rta_len, 1181 | RTA_DST, &eid, sizeof(eid)); 1182 | msg->nh.nlmsg_len += mctp_put_rtnlmsg_attr(&rta, &rta_len, 1183 | RTA_OIF, &ifindex, sizeof(ifindex)); 1184 | 1185 | if (prta) 1186 | *prta = rta; 1187 | if (prta_len) 1188 | *prta_len = rta_len; 1189 | 1190 | return 0; 1191 | } 1192 | 1193 | int mctp_nl_route_add(struct mctp_nl *nl, uint8_t eid, const char* ifname, 1194 | uint32_t mtu) { 1195 | struct mctp_rtalter_msg msg; 1196 | struct rtattr *rta; 1197 | size_t rta_len; 1198 | int rc; 1199 | 1200 | rc = fill_rtalter_args(nl, &msg, &rta, &rta_len, eid, ifname); 1201 | if (rc) { 1202 | return -1; 1203 | } 1204 | msg.nh.nlmsg_type = RTM_NEWROUTE; 1205 | 1206 | if (mtu != 0) { 1207 | /* Nested 1208 | RTA_METRICS 1209 | RTAX_MTU 1210 | */ 1211 | struct rtattr *rta1; 1212 | size_t rta_len1, space1; 1213 | uint8_t buff1[100]; 1214 | 1215 | rta1 = (void*)buff1; 1216 | rta_len1 = sizeof(buff1); 1217 | space1 = 0; 1218 | space1 += mctp_put_rtnlmsg_attr(&rta1, &rta_len1, 1219 | RTAX_MTU, &mtu, sizeof(mtu)); 1220 | // TODO add metric 1221 | msg.nh.nlmsg_len += mctp_put_rtnlmsg_attr(&rta, &rta_len, 1222 | RTA_METRICS|NLA_F_NESTED, buff1, space1); 1223 | } 1224 | 1225 | return mctp_nl_send(nl, &msg.nh); 1226 | 1227 | } 1228 | 1229 | int mctp_nl_route_del(struct mctp_nl *nl, uint8_t eid, const char* ifname) 1230 | { 1231 | struct mctp_rtalter_msg msg; 1232 | int rc; 1233 | 1234 | rc = fill_rtalter_args(nl, &msg, NULL, NULL, eid, ifname); 1235 | if (rc) { 1236 | return rc; 1237 | } 1238 | msg.nh.nlmsg_type = RTM_DELROUTE; 1239 | 1240 | return mctp_nl_send(nl, &msg.nh); 1241 | } 1242 | 1243 | -------------------------------------------------------------------------------- /src/mctp-netlink.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "mctp.h" 9 | 10 | struct mctp_nl; 11 | typedef struct mctp_nl mctp_nl; 12 | 13 | struct mctp_nl_change { 14 | #define MCTP_NL_OP_COUNT 6 15 | enum { 16 | MCTP_NL_ADD_LINK, 17 | MCTP_NL_DEL_LINK, 18 | MCTP_NL_CHANGE_NET, 19 | MCTP_NL_CHANGE_UP, 20 | MCTP_NL_ADD_EID, 21 | MCTP_NL_DEL_EID, 22 | } op; 23 | 24 | int ifindex; 25 | 26 | // Filled for ADD_EID, DEL_EID 27 | mctp_eid_t eid; 28 | 29 | // Filled for DEL_EID, DEL_LINK, CHANGE_NET 30 | int old_net; 31 | 32 | // Filled for CHANGE_UP 33 | bool old_up; 34 | 35 | // If userdata is present on the link, it is passed here. Populated for 36 | // link change events (DEL_LINK, CHANGE_NET, CHANGE_UP). 37 | void *link_userdata; 38 | }; 39 | typedef struct mctp_nl_change mctp_nl_change; 40 | 41 | /* Allocates the structure, connects to netlink, and populates 42 | the list of interfaces */ 43 | // verbose flag controls dumping error response packets 44 | mctp_nl * mctp_nl_new(bool verbose); 45 | /* Cleans and deallocates nl */ 46 | void mctp_nl_close(mctp_nl *nl); 47 | 48 | /* Avoids printing warnings for EEXIST */ 49 | void mctp_nl_warn_eexist(mctp_nl *nl, bool warn); 50 | 51 | /* Sends a message. If NLM_F_ACK flag is set it will wait for a 52 | response then print and return any error */ 53 | int mctp_nl_send(mctp_nl *nl, struct nlmsghdr *msg); 54 | /* Sends a message and returns the responses. 55 | respp is optional, should be freed by the caller */ 56 | int mctp_nl_query(mctp_nl *nl, struct nlmsghdr *msg, 57 | struct nlmsghdr **respp, size_t *resp_lenp); 58 | 59 | int mctp_nl_recv_all(mctp_nl *nl, int sd, 60 | struct nlmsghdr **respp, size_t *resp_lenp); 61 | 62 | /* Lookup MCTP interfaces */ 63 | int mctp_nl_ifindex_byname(const mctp_nl *nl, const char *ifname); 64 | const char* mctp_nl_if_byindex(const mctp_nl *nl, int index); 65 | uint32_t mctp_nl_net_byindex(const mctp_nl *nl, int index); 66 | bool mctp_nl_up_byindex(const mctp_nl *nl, int index); 67 | /* Returns interface min_mtu, or 0 if bad index */ 68 | uint32_t mctp_nl_min_mtu_byindex(const mctp_nl *nl, int index); 69 | /* Returns interface max_mtu, or 0 if bad index */ 70 | uint32_t mctp_nl_max_mtu_byindex(const mctp_nl *nl, int index); 71 | /* Returns negative errno on failure */ 72 | int mctp_nl_hwaddr_len_byindex(const mctp_nl *nl, int index, size_t *ret_hwaddr_len); 73 | /* Caller to free */ 74 | mctp_eid_t *mctp_nl_addrs_byindex(const mctp_nl *nl, int index, 75 | size_t *ret_num); 76 | void mctp_nl_linkmap_dump(const mctp_nl *nl); 77 | /* Returns an allocated list of nets, caller to free */ 78 | uint32_t *mctp_nl_net_list(const mctp_nl *nl, size_t *ret_num_nets); 79 | /* Returns an allocated list of ifindex, caller to free */ 80 | int *mctp_nl_if_list(const mctp_nl *nl, size_t *ret_num_if); 81 | 82 | /* Get/set userdata for a link. The userdata is attached to a link 83 | * with index @ifindex. Userdata will also be populated into 84 | * struct mctp_nl_change->userdata, and would typically be freed on 85 | * MCTP_NL_DEL_LINK events 86 | * 87 | * Returns non-zero if the link does not exist. 88 | */ 89 | int mctp_nl_set_link_userdata(mctp_nl *nl, int ifindex, void *userdata); 90 | 91 | /* Returns NULL if the link does not exist */ 92 | void *mctp_nl_get_link_userdata(const mctp_nl *nl, int ifindex); 93 | /* Returns NULL if the link does not exist */ 94 | void *mctp_nl_get_link_userdata_byname(const mctp_nl *nl, const char *ifname); 95 | 96 | /* MCTP route helper */ 97 | int mctp_nl_route_add(struct mctp_nl *nl, uint8_t eid, const char* ifname, 98 | uint32_t mtu); 99 | int mctp_nl_route_del(struct mctp_nl *nl, uint8_t eid, const char* ifname); 100 | 101 | /* Helpers */ 102 | 103 | void* mctp_get_rtnlmsg_attr(int rta_type, struct rtattr *rta, size_t len, 104 | size_t *ret_len); 105 | bool mctp_get_rtnlmsg_attr_u32(int rta_type, struct rtattr *rta, size_t len, 106 | uint32_t *ret_value); 107 | bool mctp_get_rtnlmsg_attr_u8(int rta_type, struct rtattr *rta, size_t len, 108 | uint8_t *ret_value); 109 | /* Returns the space used */ 110 | size_t mctp_put_rtnlmsg_attr(struct rtattr **prta, size_t *rta_len, 111 | unsigned short type, const void* value, size_t val_len); 112 | 113 | void mctp_dump_nlmsg_error(const mctp_nl *nl, struct nlmsgerr *errmsg, size_t errlen); 114 | void mctp_display_nlmsg_error(const mctp_nl *nl, struct nlmsgerr *errmsg, size_t errlen); 115 | 116 | /* enable=true will return the socket listening for netlink messages. 117 | enable=false stops receiving 118 | */ 119 | int mctp_nl_monitor(mctp_nl *nl, bool enable); 120 | 121 | /* Drains the monitor socket and refreshes link/address state from netlink. 122 | Updates are returned in 'changes', with the new state reflected in the nl 123 | struct */ 124 | int mctp_nl_handle_monitor(mctp_nl *nl, mctp_nl_change **changes, 125 | size_t *num_changes); 126 | 127 | void mctp_nl_changes_dump(mctp_nl *nl, mctp_nl_change *changes, 128 | size_t num_changes); 129 | 130 | -------------------------------------------------------------------------------- /src/mctp-ops.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 */ 2 | /* 3 | * mctp-ops: Abstraction for socket operations for mctp & mctpd. 4 | * 5 | * Copyright (c) 2023 Code Construct 6 | */ 7 | 8 | #define _GNU_SOURCE 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "mctp.h" 15 | #include "mctp-ops.h" 16 | 17 | static int mctp_op_mctp_socket(void) 18 | { 19 | return socket(AF_MCTP, SOCK_DGRAM, 0); 20 | } 21 | 22 | static int mctp_op_netlink_socket(void) 23 | { 24 | return socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); 25 | } 26 | 27 | static int mctp_op_bind(int sd, struct sockaddr *addr, socklen_t addrlen) 28 | { 29 | return bind(sd, addr, addrlen); 30 | } 31 | 32 | static int mctp_op_setsockopt(int sd, int level, int optname, void *optval, 33 | socklen_t optlen) 34 | { 35 | return setsockopt(sd, level, optname, optval, optlen); 36 | } 37 | 38 | static ssize_t mctp_op_sendto(int sd, const void *buf, size_t len, int flags, 39 | const struct sockaddr *dest, socklen_t addrlen) 40 | { 41 | return sendto(sd, buf, len, flags, dest, addrlen); 42 | } 43 | 44 | static ssize_t mctp_op_recvfrom(int sd, void *buf, size_t len, int flags, 45 | struct sockaddr *src, socklen_t *addrlen) 46 | { 47 | return recvfrom(sd, buf, len, flags, src, addrlen); 48 | } 49 | 50 | static int mctp_op_close(int sd) 51 | { 52 | return close(sd); 53 | } 54 | 55 | static void mctp_bug_warn(const char* fmt, va_list args) 56 | { 57 | vwarnx(fmt, args); 58 | } 59 | 60 | const struct mctp_ops mctp_ops = { 61 | .mctp = { 62 | .socket = mctp_op_mctp_socket, 63 | .setsockopt = mctp_op_setsockopt, 64 | .bind = mctp_op_bind, 65 | .sendto = mctp_op_sendto, 66 | .recvfrom = mctp_op_recvfrom, 67 | .close = mctp_op_close, 68 | }, 69 | .nl = { 70 | .socket = mctp_op_netlink_socket, 71 | .setsockopt = mctp_op_setsockopt, 72 | .bind = mctp_op_bind, 73 | .sendto = mctp_op_sendto, 74 | .recvfrom = mctp_op_recvfrom, 75 | .close = mctp_op_close, 76 | }, 77 | .bug_warn = mctp_bug_warn, 78 | }; 79 | 80 | void mctp_ops_init(void) { } 81 | -------------------------------------------------------------------------------- /src/mctp-ops.h: -------------------------------------------------------------------------------- 1 | 2 | /* SPDX-License-Identifier: GPL-2.0 */ 3 | /* 4 | * mctpd: bus owner for MCTP using Linux kernel 5 | * 6 | * Copyright (c) 2023 Code Construct 7 | */ 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | #define _GNU_SOURCE 14 | 15 | struct socket_ops { 16 | int (*socket)(void); 17 | int (*setsockopt)(int sd, int level, int optname, void *optval, 18 | socklen_t optlen); 19 | int (*bind)(int sd, struct sockaddr *addr, socklen_t addrlen); 20 | ssize_t (*sendto)(int sd, const void *buf, size_t len, int flags, 21 | const struct sockaddr *dest, socklen_t addrlen); 22 | ssize_t (*recvfrom)(int sd, void *buf, size_t len, int flags, 23 | struct sockaddr *src, socklen_t *addrlen); 24 | int (*close)(int sd); 25 | }; 26 | 27 | struct mctp_ops { 28 | struct socket_ops mctp; 29 | struct socket_ops nl; 30 | void (*bug_warn)(const char* fmt, va_list args); 31 | }; 32 | 33 | extern const struct mctp_ops mctp_ops; 34 | 35 | void mctp_ops_init(void); 36 | -------------------------------------------------------------------------------- /src/mctp-req.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 */ 2 | /* 3 | * mctp-req: MCTP echo requester 4 | * 5 | * Copyright (c) 2021 Code Construct 6 | * Copyright (c) 2021 Google 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "mctp.h" 19 | #include "mctp-util.h" 20 | 21 | static const int DEFAULT_NET = 1; 22 | static const mctp_eid_t DEFAULT_EID = 8; 23 | static const size_t DEFAULT_LEN = 1; 24 | 25 | // Code Construct allocation 26 | static const uint8_t VENDOR_TYPE_ECHO[3] = { 0xcc, 0xde, 0xf0 }; 27 | static const uint8_t MCTP_TYPE_VENDOR_PCIE = 0x7e; 28 | 29 | /* lladdrlen != -1 to ignore ifindex/lladdr */ 30 | static int mctp_req(unsigned int net, mctp_eid_t eid, 31 | unsigned int ifindex, uint8_t *lladdr, int lladdrlen, 32 | uint8_t *data, size_t len) 33 | { 34 | struct sockaddr_mctp_ext addr; 35 | unsigned char *payload, *buf; 36 | socklen_t addrlen; 37 | int rc, sd, val; 38 | size_t i, buf_len; 39 | 40 | sd = socket(AF_MCTP, SOCK_DGRAM, 0); 41 | if (sd < 0) 42 | err(EXIT_FAILURE, "socket"); 43 | 44 | memset(&addr, 0x0, sizeof(addr)); 45 | addrlen = sizeof(struct sockaddr_mctp); 46 | addr.smctp_base.smctp_family = AF_MCTP; 47 | addr.smctp_base.smctp_network = net; 48 | addr.smctp_base.smctp_addr.s_addr = eid; 49 | addr.smctp_base.smctp_type = MCTP_TYPE_VENDOR_PCIE; 50 | addr.smctp_base.smctp_tag = MCTP_TAG_OWNER; 51 | printf("req: sending to (net %d, eid %d), type 0x%x\n", 52 | net, eid, addr.smctp_base.smctp_type); 53 | 54 | buf_len = len + sizeof(VENDOR_TYPE_ECHO); 55 | buf = malloc(buf_len); 56 | if (!buf) 57 | err(EXIT_FAILURE, "malloc"); 58 | memcpy(buf, VENDOR_TYPE_ECHO, sizeof(VENDOR_TYPE_ECHO)); 59 | payload = &buf[sizeof(VENDOR_TYPE_ECHO)]; 60 | 61 | if (data) { 62 | memcpy(payload, data, len); 63 | } else { 64 | for (i = 0; i < len; i++) 65 | payload[i] = i & 0xff; 66 | } 67 | 68 | /* extended addressing */ 69 | if (lladdrlen != -1) { 70 | addrlen = sizeof(struct sockaddr_mctp_ext); 71 | addr.smctp_halen = lladdrlen; 72 | memcpy(addr.smctp_haddr, lladdr, lladdrlen); 73 | addr.smctp_ifindex = ifindex; 74 | printf(" ext ifindex %d ha[0]=0x%02x len %hhu\n", 75 | addr.smctp_ifindex, 76 | addr.smctp_haddr[0], addr.smctp_halen); 77 | val = 1; 78 | rc = setsockopt(sd, SOL_MCTP, MCTP_OPT_ADDR_EXT, &val, sizeof(val)); 79 | if (rc < 0) 80 | errx(EXIT_FAILURE, 81 | "Kernel does not support MCTP extended addressing"); 82 | } 83 | 84 | 85 | /* send data */ 86 | rc = sendto(sd, buf, buf_len, 0, 87 | (struct sockaddr *)&addr, addrlen); 88 | if (rc != (int)buf_len) 89 | err(EXIT_FAILURE, "sendto(%zd)", buf_len); 90 | 91 | /* receive response */ 92 | addrlen = sizeof(addr); 93 | rc = recvfrom(sd, buf, buf_len, MSG_TRUNC, 94 | (struct sockaddr *)&addr, &addrlen); 95 | if (rc < 0) 96 | err(EXIT_FAILURE, "recvfrom"); 97 | else if ((size_t)rc != buf_len) 98 | errx(EXIT_FAILURE, "unexpected length: got %d, exp %zd", 99 | rc, buf_len); 100 | 101 | if (!(addrlen == sizeof(struct sockaddr_mctp_ext) || 102 | addrlen == sizeof(struct sockaddr_mctp))) 103 | errx(EXIT_FAILURE, "unknown recv address length %d, exp %zu or %zu)", 104 | addrlen, sizeof(struct sockaddr_mctp_ext), 105 | sizeof(struct sockaddr_mctp)); 106 | 107 | 108 | printf("req: message from (net %d, eid %d) type 0x%x len %zd\n", 109 | addr.smctp_base.smctp_network, addr.smctp_base.smctp_addr.s_addr, 110 | addr.smctp_base.smctp_type, 111 | len); 112 | if (addrlen == sizeof(struct sockaddr_mctp_ext)) { 113 | printf(" ext ifindex %d ha[0]=0x%02x len %hhu\n", 114 | addr.smctp_ifindex, 115 | addr.smctp_haddr[0], addr.smctp_halen); 116 | } 117 | 118 | if (memcmp(buf, VENDOR_TYPE_ECHO, sizeof(VENDOR_TYPE_ECHO)) != 0) { 119 | errx(EXIT_FAILURE, "unexpected vendor ID"); 120 | } 121 | 122 | for (i = 0; i < len; i++) { 123 | uint8_t exp = data ? data[i] : i & 0xff; 124 | if (payload[i] != exp) 125 | errx(EXIT_FAILURE, 126 | "payload mismatch at byte 0x%zx; " 127 | "sent 0x%02x, received 0x%02x", 128 | i, exp, buf[i]); 129 | } 130 | 131 | return 0; 132 | } 133 | 134 | static void usage(void) 135 | { 136 | fprintf(stderr, "mctp-req [eid ] [net ] [ifindex lladdr ] [len ]\n"); 137 | fprintf(stderr, "default eid %d net %d len %zd\n", 138 | DEFAULT_EID, DEFAULT_NET, DEFAULT_LEN); 139 | } 140 | 141 | int main(int argc, char ** argv) 142 | { 143 | uint8_t *data, lladdr[MAX_ADDR_LEN]; 144 | int lladdrlen = -1, datalen = -1; 145 | unsigned int net = DEFAULT_NET; 146 | mctp_eid_t eid = DEFAULT_EID; 147 | size_t len = DEFAULT_LEN, sz; 148 | char *endp, *optname, *optval; 149 | unsigned int tmp, ifindex; 150 | bool valid_parse; 151 | int i; 152 | 153 | if (!(argc % 2)) { 154 | warnx("extra argument %s", argv[argc-1]); 155 | usage(); 156 | return 255; 157 | } 158 | 159 | data = NULL; 160 | ifindex = 0; 161 | 162 | for (i = 1; i < argc; i += 2) { 163 | optname = argv[i]; 164 | optval = argv[i+1]; 165 | 166 | tmp = strtoul(optval, &endp, 0); 167 | valid_parse = (endp != optval); 168 | 169 | if (!strcmp(optname, "eid")) { 170 | if (tmp > 0xff) 171 | errx(EXIT_FAILURE, "Bad eid"); 172 | eid = tmp; 173 | } else if (!strcmp(optname, "net")) { 174 | if (tmp > 0xff) 175 | errx(EXIT_FAILURE, "Bad net"); 176 | net = tmp; 177 | } else if (!strcmp(optname, "ifindex")) { 178 | ifindex = tmp; 179 | } else if (!strcmp(optname, "len")) { 180 | if (tmp > 64 * 1024) 181 | errx(EXIT_FAILURE, "Bad len"); 182 | len = tmp; 183 | } else if (!strcmp(optname, "data")) { 184 | sz = (strlen(optval) + 2) / 3; 185 | data = malloc(sz); 186 | if (!data) 187 | err(EXIT_FAILURE, "malloc"); 188 | if (parse_hex_addr(optval, data, &sz)) { 189 | errx(EXIT_FAILURE, "Bad data"); 190 | } 191 | datalen = sz; 192 | valid_parse = true; 193 | } else if (!strcmp(optname, "lladdr")) { 194 | sz = sizeof(lladdr); 195 | if (parse_hex_addr(optval, lladdr, &sz)) { 196 | errx(EXIT_FAILURE, "Bad lladdr"); 197 | } 198 | lladdrlen = sz; 199 | valid_parse = true; 200 | } else 201 | errx(EXIT_FAILURE, "Unknown argument %s", optname); 202 | 203 | // Handle bad integer etc. 204 | if (!valid_parse) { 205 | errx(EXIT_FAILURE, "invalid %s value %s", 206 | optname, optval); 207 | } 208 | } 209 | 210 | if (data) 211 | len = datalen; 212 | 213 | return mctp_req(net, eid, ifindex, lladdr, lladdrlen, data, len); 214 | } 215 | -------------------------------------------------------------------------------- /src/mctp-util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "mctp-util.h" 7 | 8 | void mctp_hexdump(const void *b, int len, const char *indent) { 9 | const char* buf = b; 10 | const int row_len = 16; 11 | int i, j; 12 | 13 | for (i = 0; i < len; i += row_len) { 14 | char hbuf[row_len * strlen("00 ") + 1]; 15 | char cbuf[row_len + strlen("|") + 1]; 16 | 17 | for (j = 0; (j < row_len) && ((i+j) < len); j++) { 18 | unsigned char c = buf[i + j]; 19 | 20 | sprintf(hbuf + j * 3, "%02x ", c); 21 | 22 | if (!isprint(c)) 23 | c = '.'; 24 | 25 | sprintf(cbuf + j, "%c", c); 26 | } 27 | 28 | strcat(cbuf, "|"); 29 | 30 | printf("%s%08x %*s |%s\n", indent, i, 31 | (int)(0 - sizeof(hbuf) + 1), hbuf, cbuf); 32 | } 33 | } 34 | 35 | void print_hex_addr(const uint8_t *data, size_t len) 36 | { 37 | for (size_t i = 0; i < len; i++) { 38 | if (i > 0) { 39 | putchar(':'); 40 | } 41 | printf("%02x", data[i]); 42 | } 43 | } 44 | 45 | int write_hex_addr(const uint8_t *data, size_t len, char* dest, size_t dest_len) 46 | { 47 | size_t l; 48 | if (dest_len < len*3) { 49 | snprintf(dest, dest_len, "XXXX"); 50 | return -EINVAL; 51 | } 52 | 53 | dest[0] = '\0'; 54 | for (size_t i = 0; i < len; i++) { 55 | if (i > 0) { 56 | l = snprintf(dest, dest_len, ":"); 57 | if (l >= dest_len) 58 | return -EPROTO; 59 | dest_len -= l; 60 | dest += l; 61 | } 62 | l = snprintf(dest, dest_len, "%02x", data[i]); 63 | if (l >= dest_len) 64 | return -EPROTO; 65 | dest_len -= l; 66 | dest += l; 67 | } 68 | return 0; 69 | } 70 | 71 | // Accepts colon separated hex bytes 72 | int parse_hex_addr(const char* in, uint8_t *out, size_t *out_len) 73 | { 74 | int rc = -1; 75 | size_t out_pos = 0; 76 | while (1) { 77 | if (*in == '\0') { 78 | rc = 0; 79 | break; 80 | } 81 | else if (*in == ':') { 82 | in++; 83 | if (*in == ':' || *in == '\0' || out_pos == 0) { 84 | // can't have repeated ':' or ':' at start or end. 85 | break; 86 | } 87 | } else { 88 | char* endp; 89 | int tmp; 90 | tmp = strtoul(in, &endp, 16); 91 | if (endp == in || tmp > 0xff) { 92 | break; 93 | } 94 | if (out_pos >= *out_len) { 95 | break; 96 | } 97 | *out = tmp & 0xff; 98 | out++; 99 | out_pos++; 100 | in = endp; 101 | } 102 | } 103 | 104 | if (rc) { 105 | *out_len = 0; 106 | } else { 107 | *out_len = out_pos; 108 | } 109 | return rc; 110 | } 111 | 112 | int parse_uint32(const char *str, uint32_t *out) 113 | { 114 | unsigned long v; 115 | char *endp; 116 | v = strtoul(str, &endp, 0); 117 | if (endp == str || *endp != '\0') 118 | return -EINVAL; 119 | if (v > UINT32_MAX) 120 | return -EOVERFLOW; 121 | *out = v; 122 | return 0; 123 | } 124 | 125 | int parse_int32(const char *str, int32_t *out) 126 | { 127 | long v; 128 | char *endp; 129 | v = strtol(str, &endp, 0); 130 | if (endp == str || *endp != '\0') 131 | return -EINVAL; 132 | if (v > INT32_MAX || v < INT32_MIN) 133 | return -EOVERFLOW; 134 | *out = v; 135 | return 0; 136 | } 137 | 138 | /* Returns a malloced pointer */ 139 | char* bytes_to_uuid(const uint8_t u[16]) 140 | { 141 | char *buf = malloc(37); 142 | if (!buf) { 143 | return NULL; 144 | } 145 | snprintf(buf, 37, 146 | "%02x%02x%02x%02x" 147 | "-" 148 | "%02x%02x" 149 | "-" 150 | "%02x%02x" 151 | "-" 152 | "%02x%02x" 153 | "-" 154 | "%02x%02x%02x%02x%02x%02x", 155 | u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7], 156 | u[8], u[9], u[10], u[11], u[12], u[13], u[14], u[15]); 157 | return buf; 158 | } 159 | -------------------------------------------------------------------------------- /src/mctp-util.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define max(a, b) ((a) > (b) ? (a) : (b)) 4 | #define min(a, b) ((a) < (b) ? (a) : (b)) 5 | 6 | #define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) 7 | 8 | void mctp_hexdump(const void *b, int len, const char *indent); 9 | void print_hex_addr(const uint8_t *data, size_t len); 10 | int write_hex_addr(const uint8_t *data, size_t len, char* dest, size_t dest_len); 11 | int parse_hex_addr(const char* in, uint8_t *out, size_t *out_len); 12 | int parse_uint32(const char *str, uint32_t *out); 13 | int parse_int32(const char *str, int32_t *out); 14 | /* Returns a malloced pointer */ 15 | char* bytes_to_uuid(const uint8_t u[16]); 16 | -------------------------------------------------------------------------------- /src/mctp.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 */ 2 | /* 3 | * mctp: userspace utility for managing the kernel MCTP stack. 4 | * 5 | * Interim definitions for the MCTP kernel interface. While the MCTP kernel 6 | * API is going through upstream, system toolchains may not yet have the 7 | * required definitions for mctp tools. This header provides fallback 8 | * implementations (protected by #ifdef guards) of those new interfaces. 9 | * 10 | * Copyright (c) 2021 Code Construct 11 | * Copyright (c) 2021 Google 12 | */ 13 | 14 | #ifndef _MCTP_H 15 | #define _MCTP_H 16 | 17 | #include "config.h" 18 | 19 | #if HAVE_LINUX_MCTP_H 20 | #include 21 | #endif 22 | #include 23 | #include 24 | 25 | #ifndef AF_MCTP 26 | #define AF_MCTP 45 27 | #endif 28 | 29 | /* MCTP serial line discipline */ 30 | #ifndef N_MCTP 31 | #define N_MCTP 28 32 | #endif 33 | 34 | #include 35 | 36 | #ifndef MCTP_NET_ANY 37 | 38 | typedef uint8_t mctp_eid_t; 39 | 40 | struct mctp_addr { 41 | mctp_eid_t s_addr; 42 | }; 43 | 44 | struct sockaddr_mctp { 45 | unsigned short int smctp_family; 46 | unsigned short __smctp_pad0; 47 | int smctp_network; 48 | struct mctp_addr smctp_addr; 49 | uint8_t smctp_type; 50 | uint8_t smctp_tag; 51 | uint8_t __smctp_pad1; 52 | }; 53 | 54 | #define MCTP_NET_ANY 0 55 | #define MCTP_ADDR_ANY 0xff 56 | #define MCTP_TAG_OWNER 0x08 57 | 58 | #endif /* MCTP_NET_ANY */ 59 | 60 | #ifndef MAX_ADDR_LEN 61 | #define MAX_ADDR_LEN 32 62 | #endif 63 | 64 | /* Added in v5.16 */ 65 | #ifndef MCTP_OPT_ADDR_EXT 66 | 67 | #define MCTP_OPT_ADDR_EXT 1 68 | 69 | struct sockaddr_mctp_ext { 70 | struct sockaddr_mctp smctp_base; 71 | int smctp_ifindex; 72 | uint8_t smctp_halen; 73 | uint8_t __smctp_pad0[3]; 74 | unsigned char smctp_haddr[MAX_ADDR_LEN]; 75 | }; 76 | 77 | #endif /* MCTP_OPT_ADDR_EXT */ 78 | 79 | #ifndef IFLA_MCTP_MAX 80 | 81 | /* From if_link.h */ 82 | enum { 83 | IFLA_MCTP_UNSPEC, 84 | IFLA_MCTP_NET, 85 | __IFLA_MCTP_MAX, 86 | }; 87 | 88 | #define IFLA_MCTP_MAX (__IFLA_MCTP_MAX - 1) 89 | 90 | #endif /* IFLA_MCTP_MAX */ 91 | 92 | /* setsockopt(2) options */ 93 | #ifndef SOL_MCTP 94 | #define SOL_MCTP 285 95 | #endif 96 | 97 | #ifndef RTNLGRP_MCTP_IFADDR 98 | #define RTNLGRP_MCTP_IFADDR 34 99 | #endif 100 | 101 | #endif /* _MCTP_H */ 102 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | 4 | import pytest 5 | import asyncdbus 6 | 7 | import mctpd as fake_mctpd 8 | 9 | """Simple system & network. 10 | 11 | Contains one interface (lladdr 0x10, local EID 8), and one endpoint (lladdr 12 | 0x1d), that reports support for MCTP control and PLDM. 13 | """ 14 | @pytest.fixture 15 | async def sysnet(): 16 | return await fake_mctpd.default_sysnet() 17 | 18 | @pytest.fixture 19 | async def dbus(): 20 | async with asyncdbus.MessageBus().connect() as bus: 21 | yield bus 22 | 23 | @pytest.fixture 24 | def config(): 25 | return None 26 | 27 | @pytest.fixture 28 | async def mctpd(nursery, dbus, sysnet, config): 29 | m = fake_mctpd.MctpdWrapper(dbus, sysnet, config = config) 30 | await m.start_mctpd(nursery) 31 | yield m 32 | res = await m.stop_mctpd() 33 | assert res == 0 34 | -------------------------------------------------------------------------------- /tests/mctp-ops-test.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 */ 2 | /* 3 | * mctp-ops-test: Test implementations for mctp socket ops 4 | * 5 | * Copyright (c) 2023 Code Construct 6 | */ 7 | 8 | #define _GNU_SOURCE 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | #include "mctp-ops.h" 24 | #include "test-proto.h" 25 | 26 | static int control_sd; 27 | 28 | static int mctp_op_socket(int type) 29 | { 30 | union { 31 | struct cmsghdr hdr; 32 | unsigned char buf[CMSG_SPACE(sizeof(int))]; 33 | } msg; 34 | struct cmsghdr *cmsg; 35 | struct control_msg_req req; 36 | struct control_msg_rsp rsp; 37 | struct msghdr hdr = { 0 }; 38 | struct iovec iov; 39 | int rc, var, sd; 40 | 41 | if (type == AF_MCTP) 42 | req.type = CONTROL_OP_SOCKET_MCTP; 43 | else if (type == AF_NETLINK) 44 | req.type = CONTROL_OP_SOCKET_NL; 45 | else 46 | errx(EXIT_FAILURE, "invalid socket type?"); 47 | 48 | rc = send(control_sd, &req, sizeof(req), 0); 49 | if (rc < 0) 50 | err(EXIT_FAILURE, "control send error"); 51 | 52 | iov.iov_base = &rsp; 53 | iov.iov_len = sizeof(rsp); 54 | hdr.msg_iov = &iov; 55 | hdr.msg_iovlen = 1; 56 | hdr.msg_control = &msg; 57 | hdr.msg_controllen = sizeof(msg); 58 | rc = recvmsg(control_sd, &hdr, 0); 59 | 60 | cmsg = CMSG_FIRSTHDR(&hdr); 61 | if (!cmsg || cmsg->cmsg_len != CMSG_LEN(sizeof(int)) 62 | || cmsg->cmsg_level != SOL_SOCKET 63 | || cmsg->cmsg_type != SCM_RIGHTS) 64 | { 65 | errx(EXIT_FAILURE, "invalid control response"); 66 | } 67 | 68 | memcpy(&sd, CMSG_DATA(cmsg), sizeof(int)); 69 | var = 0; 70 | ioctl(sd, FIONBIO, &var); 71 | 72 | return sd; 73 | } 74 | 75 | static int mctp_op_mctp_socket(void) 76 | { 77 | return mctp_op_socket(AF_MCTP); 78 | } 79 | 80 | static int mctp_op_netlink_socket(void) 81 | { 82 | return mctp_op_socket(AF_NETLINK); 83 | } 84 | 85 | static int mctp_op_bind(int sd, struct sockaddr *addr, socklen_t addrlen) 86 | { 87 | struct msghdr msg = { 0 }; 88 | struct sock_msg sock_msg = { 0 }; 89 | struct iovec iov; 90 | ssize_t rc; 91 | 92 | sock_msg.type = SOCK_BIND; 93 | sock_msg.bind.addrlen = addrlen; 94 | memcpy(&sock_msg.bind.addr.buf, addr, addrlen); 95 | 96 | iov.iov_base = &sock_msg; 97 | iov.iov_len = sizeof(sock_msg); 98 | 99 | msg.msg_iov = &iov; 100 | msg.msg_iovlen = 1; 101 | 102 | rc = sendmsg(sd, &msg, 0); 103 | if (rc < 0) 104 | return rc; 105 | 106 | if (rc < (int)sizeof(sock_msg)) { 107 | errno = EPROTO; 108 | return -1; 109 | } 110 | 111 | return 0; 112 | } 113 | 114 | static int mctp_op_setsockopt(int sd, int level, int optname, void *optval, 115 | socklen_t optlen) 116 | { 117 | struct msghdr msg = { 0 }; 118 | struct sock_msg sock_msg = { 0 }; 119 | struct iovec iov[2]; 120 | ssize_t rc; 121 | 122 | sock_msg.type = SOCK_SETSOCKOPT; 123 | sock_msg.setsockopt.level = level; 124 | sock_msg.setsockopt.optname = optname; 125 | 126 | iov[0].iov_base = &sock_msg; 127 | iov[0].iov_len = sizeof(sock_msg); 128 | iov[1].iov_base = optval; 129 | iov[1].iov_len = optlen; 130 | 131 | msg.msg_iov = iov; 132 | msg.msg_iovlen = 2; 133 | 134 | rc = sendmsg(sd, &msg, 0); 135 | if (rc < 0) 136 | return rc; 137 | 138 | if (rc < (int)sizeof(sock_msg)) { 139 | errno = EPROTO; 140 | return -1; 141 | } 142 | 143 | /* todo: return code */ 144 | return 0; 145 | } 146 | 147 | static ssize_t mctp_op_sendto(int sd, const void *buf, size_t len, int flags, 148 | const struct sockaddr *dest, socklen_t addrlen) 149 | { 150 | struct msghdr msg = { 0 }; 151 | struct sock_msg sock_msg = { 0 }; 152 | struct iovec iov[2]; 153 | ssize_t rc; 154 | 155 | sock_msg.type = SOCK_SEND; 156 | sock_msg.send.addrlen = addrlen; 157 | memcpy(&sock_msg.send.addr.buf, dest, addrlen); 158 | 159 | iov[0].iov_base = &sock_msg; 160 | iov[0].iov_len = sizeof(sock_msg); 161 | iov[1].iov_base = (void *)buf; 162 | iov[1].iov_len = len; 163 | 164 | msg.msg_iov = iov; 165 | msg.msg_iovlen = 2; 166 | 167 | rc = sendmsg(sd, &msg, 0); 168 | if (rc < 0) 169 | return rc; 170 | 171 | if (rc < (int)sizeof(sock_msg)) { 172 | errno = EPROTO; 173 | return -1; 174 | } 175 | 176 | return rc - sizeof(sock_msg); 177 | } 178 | 179 | static ssize_t mctp_op_recvfrom(int sd, void *buf, size_t len, int flags, 180 | struct sockaddr *src, socklen_t *addrlenp) 181 | { 182 | struct msghdr msg = { 0 }; 183 | struct sock_msg sock_msg = { 0 }; 184 | struct iovec iov[2]; 185 | ssize_t rc; 186 | 187 | iov[0].iov_base = &sock_msg; 188 | iov[0].iov_len = sizeof(sock_msg); 189 | iov[1].iov_base = (void *)buf; 190 | iov[1].iov_len = len; 191 | 192 | msg.msg_iov = iov; 193 | msg.msg_iovlen = 2; 194 | 195 | rc = recvmsg(sd, &msg, flags); 196 | if (rc <= 0) 197 | return rc; 198 | 199 | if (rc < (ssize_t)sizeof(sock_msg)) 200 | errx(EXIT_FAILURE, "ops protocol error"); 201 | 202 | if (sock_msg.type != SOCK_RECV) 203 | errx(EXIT_FAILURE, "Unexpected message type %d?", 204 | sock_msg.type); 205 | 206 | if (src) 207 | memcpy(src, &sock_msg.recv.addr.buf, sock_msg.recv.addrlen); 208 | if (addrlenp) 209 | *addrlenp = sock_msg.recv.addrlen; 210 | 211 | return rc - sizeof(sock_msg); 212 | } 213 | 214 | static int mctp_op_close(int sd) 215 | { 216 | return close(sd); 217 | } 218 | 219 | static void mctp_bug_warn(const char* fmt, va_list args) 220 | { 221 | vwarnx(fmt, args); 222 | warnx("Aborting on bug in tests"); 223 | abort(); 224 | } 225 | 226 | const struct mctp_ops mctp_ops = { 227 | .mctp = { 228 | .socket = mctp_op_mctp_socket, 229 | .setsockopt = mctp_op_setsockopt, 230 | .bind = mctp_op_bind, 231 | .sendto = mctp_op_sendto, 232 | .recvfrom = mctp_op_recvfrom, 233 | .close = mctp_op_close, 234 | }, 235 | .nl = { 236 | .socket = mctp_op_netlink_socket, 237 | .setsockopt = mctp_op_setsockopt, 238 | .bind = mctp_op_bind, 239 | .sendto = mctp_op_sendto, 240 | .recvfrom = mctp_op_recvfrom, 241 | .close = mctp_op_close, 242 | }, 243 | .bug_warn = mctp_bug_warn, 244 | }; 245 | 246 | void mctp_ops_init(void) 247 | { 248 | struct control_msg_req req; 249 | struct control_msg_rsp rsp; 250 | const char *sockstr; 251 | ssize_t len; 252 | int var, sd; 253 | 254 | sockstr = getenv("MCTP_TEST_SOCK"); 255 | if (!sockstr || !strlen(sockstr)) 256 | errx(EXIT_FAILURE, "No MCTP_TEST_SOCK fd provided"); 257 | 258 | sd = atoi(sockstr); 259 | var = 0; 260 | ioctl(sd, FIONBIO, &var); 261 | 262 | req.type = CONTROL_OP_INIT; 263 | len = send(sd, &req, sizeof(req), 0); 264 | if (len != sizeof(req)) 265 | err(EXIT_FAILURE, "control init failed"); 266 | 267 | len = recv(sd, &rsp, sizeof(rsp), 0); 268 | control_sd = sd; 269 | } 270 | 271 | -------------------------------------------------------------------------------- /tests/mctp_test_utils.py: -------------------------------------------------------------------------------- 1 | 2 | async def mctpd_mctp_iface_obj(dbus, iface): 3 | obj = await dbus.get_proxy_object( 4 | 'au.com.codeconstruct.MCTP1', 5 | '/au/com/codeconstruct/mctp1/interfaces/' + iface.name 6 | ) 7 | return await obj.get_interface('au.com.codeconstruct.MCTP.BusOwner1') 8 | 9 | async def mctpd_mctp_iface_control_obj(dbus, iface): 10 | obj = await dbus.get_proxy_object( 11 | "au.com.codeconstruct.MCTP1", 12 | "/au/com/codeconstruct/mctp1/interfaces/" + iface.name, 13 | ) 14 | return await obj.get_interface("au.com.codeconstruct.MCTP.Interface1") 15 | 16 | async def mctpd_mctp_endpoint_obj(dbus, path, iface): 17 | obj = await dbus.get_proxy_object( 18 | 'au.com.codeconstruct.MCTP1', 19 | path, 20 | ) 21 | return await obj.get_interface(iface) 22 | 23 | async def mctpd_mctp_network_obj(dbus, net): 24 | obj = await dbus.get_proxy_object( 25 | 'au.com.codeconstruct.MCTP1', 26 | f'/au/com/codeconstruct/mctp1/networks/{net:d}' 27 | ) 28 | iface = await obj.get_interface('au.com.codeconstruct.MCTP.Network1') 29 | # fixup autogenerated snake-case names 30 | iface.get_local_eids = iface.get_local_ei_ds 31 | iface.set_local_eids = iface.set_local_ei_ds 32 | return iface 33 | 34 | async def mctpd_mctp_endpoint_control_obj(dbus, path): 35 | return await mctpd_mctp_endpoint_obj( 36 | dbus, 37 | path, 38 | 'au.com.codeconstruct.MCTP.Endpoint1' 39 | ) 40 | 41 | async def mctpd_mctp_endpoint_common_obj(dbus, path): 42 | return await mctpd_mctp_endpoint_obj( 43 | dbus, 44 | path, 45 | 'xyz.openbmc_project.MCTP.Endpoint' 46 | ) 47 | -------------------------------------------------------------------------------- /tests/pytest.ini.in: -------------------------------------------------------------------------------- 1 | [pytest] 2 | trio_mode = true 3 | testpaths = @testpaths@ 4 | filterwarnings = 5 | ignore:.*wait_readable:DeprecationWarning:asyncdbus 6 | ignore:.*wait_writable:DeprecationWarning:asyncdbus 7 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | asyncdbus==0.6.1 2 | pyroute2==0.7.10 3 | pytest==8.3.2 4 | pytest-trio==0.8.0 5 | trio==0.26.2 6 | pytest-tap==3.5 7 | -------------------------------------------------------------------------------- /tests/test-proto.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | 6 | #include "mctp.h" 7 | 8 | enum { 9 | CONTROL_OP_INIT, 10 | CONTROL_OP_SOCKET_MCTP, 11 | CONTROL_OP_SOCKET_NL, 12 | }; 13 | 14 | struct control_msg_req { 15 | uint8_t type; 16 | }; 17 | 18 | struct control_msg_rsp { 19 | uint8_t val; 20 | }; 21 | 22 | union sock_msg_sockaddr { 23 | struct sockaddr_mctp mctp; 24 | struct sockaddr_mctp_ext mctp_ext; 25 | struct sockaddr_nl nl; 26 | unsigned char buf[56]; 27 | }; 28 | 29 | struct sock_msg { 30 | enum { 31 | SOCK_RECV, 32 | SOCK_SEND, 33 | SOCK_SETSOCKOPT, 34 | SOCK_BIND, 35 | } type; 36 | union { 37 | struct sock_msg_recv { 38 | union sock_msg_sockaddr addr; 39 | socklen_t addrlen; 40 | uint8_t data[]; 41 | } recv; 42 | struct sock_msg_send { 43 | union sock_msg_sockaddr addr; 44 | socklen_t addrlen; 45 | uint8_t data[]; 46 | } send; 47 | struct sock_msg_setsockopt { 48 | int level; 49 | int optname; 50 | uint8_t optdata[]; 51 | } setsockopt; 52 | struct sock_msg_bind { 53 | union sock_msg_sockaddr addr; 54 | socklen_t addrlen; 55 | } bind; 56 | }; 57 | }; 58 | 59 | 60 | -------------------------------------------------------------------------------- /tests/test_mctpd.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import trio 3 | import uuid 4 | import asyncdbus 5 | 6 | from mctp_test_utils import ( 7 | mctpd_mctp_iface_obj, 8 | mctpd_mctp_network_obj, 9 | mctpd_mctp_endpoint_common_obj, 10 | mctpd_mctp_endpoint_control_obj 11 | ) 12 | from mctpd import Endpoint, MCTPSockAddr 13 | 14 | # DBus constant symbol suffixes: 15 | # 16 | # - C: Connection 17 | # - P: Path 18 | # - I: Interface 19 | MCTPD_C = 'au.com.codeconstruct.MCTP1' 20 | MCTPD_MCTP_P = '/au/com/codeconstruct/mctp1' 21 | MCTPD_MCTP_I = 'au.com.codeconstruct.MCTP.BusOwner1' 22 | MCTPD_ENDPOINT_I = 'au.com.codeconstruct.MCTP.Endpoint1' 23 | DBUS_OBJECT_MANAGER_I = 'org.freedesktop.DBus.ObjectManager' 24 | DBUS_PROPERTIES_I = 'org.freedesktop.DBus.Properties' 25 | 26 | MCTPD_TRECLAIM = 5 27 | 28 | async def _introspect_path_recursive(dbus, path, node_set): 29 | node_set.add(path) 30 | dups = set() 31 | 32 | obj = await dbus.get_proxy_object('au.com.codeconstruct.MCTP1', path) 33 | iface = await obj.get_interface('org.freedesktop.DBus.Introspectable') 34 | data = await iface.call_introspect() 35 | node = asyncdbus.introspection.Node.parse(data) 36 | 37 | for subnode in node.nodes: 38 | if path == '/': 39 | subnode_path = '/' + subnode.name 40 | else: 41 | subnode_path = path + '/' + subnode.name 42 | 43 | if subnode_path in node_set: 44 | dups.add(subnode_path) 45 | 46 | d = await _introspect_path_recursive(dbus, subnode_path, node_set) 47 | dups.update(d) 48 | 49 | return dups 50 | 51 | """ Test that the dbus object tree is sensible: we can introspect all 52 | objects, and that there are no duplicates 53 | """ 54 | async def test_enumerate(dbus, mctpd): 55 | dups = await _introspect_path_recursive(dbus, '/', set()) 56 | assert not dups 57 | 58 | 59 | """ Test the SetupEndpoint dbus call 60 | 61 | Using the default system & network ojects, call SetupEndpoint on our mock 62 | endpoint. We expect the dbus call to return the endpoint details, and 63 | the new kernel neighbour and route entries. 64 | 65 | We have a few things provided by the test infrastructure: 66 | 67 | - dbus is the dbus connection, call the mctpd_mctp_iface_obj helper to 68 | get the MCTP dbus interface object 69 | 70 | - mctpd is our wrapper for the mctpd process and mock MCTP environment. This 71 | has two properties that represent external state: 72 | 73 | mctp.system: the local system info - containing MCTP interfaces 74 | (mctp.system.interfaces), addresses (.addresses), neighbours (.neighbours) 75 | and routes (.routes). These may be updated by the running mctpd process 76 | during tests, over the simlated netlink socket. 77 | 78 | mctp.network: the set of remote MCTP endpoints connected to the system. Each 79 | endpoint has a physical address (.lladdr) and an EID (.eid), and a tiny 80 | MCTP control protocol implementation, which the mctpd process will 81 | interact with over simulated AF_MCTP sockets. 82 | 83 | By default, we get some minimal defaults for .system and .network: 84 | 85 | - The system has one interface ('mctp0'), assigned local EID 8. This is 86 | similar to a MCTP-over-i2c interface, in that physical addresses are 87 | a single byte. 88 | 89 | - The network has one endpoint (lladdr 0x1d) connected to mctp0, with no EID 90 | assigned. It also has a random UUID, and advertises support for MCTP 91 | Control Protocol and PLDM (but note that it doesn't actually implement 92 | any PLDM!). 93 | 94 | But these are only defaults; .system and .network can be altered as required 95 | for each test. 96 | """ 97 | async def test_setup_endpoint(dbus, mctpd): 98 | # shortcuts to the default system/network configuration 99 | iface = mctpd.system.interfaces[0] 100 | ep = mctpd.network.endpoints[0] 101 | 102 | # our proxy dbus object for mctpd 103 | mctp = await mctpd_mctp_iface_obj(dbus, iface) 104 | 105 | # call SetupEndpoint. This will raise an exception on any dbus error. 106 | (eid, net, path, new) = await mctp.call_setup_endpoint(ep.lladdr) 107 | 108 | # ep.eid will be updated (through the Set Endpoint ID message); this 109 | # should match the returned EID 110 | assert eid == ep.eid 111 | 112 | # we should have a neighbour for the new endpoint 113 | assert len(mctpd.system.neighbours) == 1 114 | neigh = mctpd.system.neighbours[0] 115 | assert neigh.lladdr == ep.lladdr 116 | assert neigh.eid == ep.eid 117 | 118 | # we should have a route for the new endpoint too 119 | assert len(mctpd.system.routes) == 2 120 | 121 | """ Test that we correctly handle address conflicts on EID assignment. 122 | 123 | We have the following scenario: 124 | 125 | 1. A configured peer at physaddr 1, EID A, allocated by mctpd 126 | 2. A non-configured peer at physaddr 2, somehow carrying a default EID also A 127 | 3. Attempt to enumerate physaddr 2 128 | 129 | At (3), we should reconfigure the EID to B. 130 | """ 131 | async def test_setup_endpoint_conflict(dbus, mctpd): 132 | iface = mctpd.system.interfaces[0] 133 | 134 | mctp = await mctpd_mctp_iface_obj(dbus, iface) 135 | 136 | ep1 = mctpd.network.endpoints[0] 137 | (eid1, _, _, _) = await mctp.call_setup_endpoint(ep1.lladdr) 138 | 139 | # endpoint configured with eid1 already 140 | ep2 = Endpoint(iface, bytes([0x1e]), eid=eid1) 141 | mctpd.network.add_endpoint(ep2) 142 | 143 | (eid2, _, _, _) = await mctp.call_setup_endpoint(ep2.lladdr) 144 | assert eid1 != eid2 145 | 146 | """ Test neighbour removal """ 147 | async def test_remove_endpoint(dbus, mctpd): 148 | iface = mctpd.system.interfaces[0] 149 | ep1 = mctpd.network.endpoints[0] 150 | 151 | mctp = await mctpd_mctp_iface_obj(dbus, iface) 152 | (_, _, path, _) = await mctp.call_setup_endpoint(ep1.lladdr) 153 | 154 | assert(len(mctpd.system.neighbours) == 1) 155 | 156 | ep = await mctpd_mctp_endpoint_control_obj(dbus, path) 157 | 158 | await ep.call_remove() 159 | assert(len(mctpd.system.neighbours) == 0) 160 | 161 | async def test_recover_endpoint_present(dbus, mctpd): 162 | iface = mctpd.system.interfaces[0] 163 | dev = mctpd.network.endpoints[0] 164 | mctp = await mctpd_mctp_iface_obj(dbus, iface) 165 | (eid, net, path, new) = await mctp.call_setup_endpoint(dev.lladdr) 166 | 167 | ep = await dbus.get_proxy_object(MCTPD_C, path) 168 | ep_props = await ep.get_interface(DBUS_PROPERTIES_I) 169 | 170 | recovered = trio.Semaphore(initial_value = 0) 171 | def ep_connectivity_changed(iface, changed, invalidated): 172 | if iface == MCTPD_ENDPOINT_I and 'Connectivity' in changed: 173 | if 'Available' == changed['Connectivity'].value: 174 | recovered.release() 175 | 176 | await ep_props.on_properties_changed(ep_connectivity_changed) 177 | 178 | ep_ep = await ep.get_interface(MCTPD_ENDPOINT_I) 179 | await ep_ep.call_recover() 180 | 181 | with trio.move_on_after(2 * MCTPD_TRECLAIM) as expected: 182 | await recovered.acquire() 183 | 184 | # Cancellation implies failure to acquire recovered, which implies failure 185 | # to transition 'Connectivity' to 'Available', which is a test failure. 186 | assert not expected.cancelled_caught 187 | 188 | async def test_recover_endpoint_removed(dbus, mctpd): 189 | iface = mctpd.system.interfaces[0] 190 | dev = mctpd.network.endpoints[0] 191 | mctp = await dbus.get_proxy_object(MCTPD_C, MCTPD_MCTP_P) 192 | mctp_iface = await mctpd_mctp_iface_obj(dbus, iface) 193 | (eid, net, path, new) = await mctp_iface.call_setup_endpoint(dev.lladdr) 194 | 195 | ep = await dbus.get_proxy_object(MCTPD_C, path) 196 | ep_props = await ep.get_interface(DBUS_PROPERTIES_I) 197 | 198 | degraded = trio.Semaphore(initial_value = 0) 199 | def ep_connectivity_changed(iface, changed, invalidated): 200 | if iface == MCTPD_ENDPOINT_I and 'Connectivity' in changed: 201 | if 'Degraded' == changed['Connectivity'].value: 202 | degraded.release() 203 | 204 | await ep_props.on_properties_changed(ep_connectivity_changed) 205 | 206 | mctp_objmgr = await mctp.get_interface(DBUS_OBJECT_MANAGER_I) 207 | 208 | removed = trio.Semaphore(initial_value = 0) 209 | def ep_removed(ep_path, interfaces): 210 | if ep_path == path and MCTPD_ENDPOINT_I in interfaces: 211 | removed.release() 212 | 213 | await mctp_objmgr.on_interfaces_removed(ep_removed) 214 | 215 | del mctpd.network.endpoints[0] 216 | ep_ep = await ep.get_interface(MCTPD_ENDPOINT_I) 217 | await ep_ep.call_recover() 218 | 219 | with trio.move_on_after(2 * MCTPD_TRECLAIM) as expected: 220 | await removed.acquire() 221 | await degraded.acquire() 222 | 223 | assert not expected.cancelled_caught 224 | 225 | async def test_recover_endpoint_reset(dbus, mctpd): 226 | iface = mctpd.system.interfaces[0] 227 | dev = mctpd.network.endpoints[0] 228 | mctp = await dbus.get_proxy_object(MCTPD_C, MCTPD_MCTP_P) 229 | mctp_iface = await mctpd_mctp_iface_obj(dbus, iface) 230 | (eid, net, path, new) = await mctp_iface.call_setup_endpoint(dev.lladdr) 231 | 232 | ep = await dbus.get_proxy_object(MCTPD_C, path) 233 | ep_props = await ep.get_interface(DBUS_PROPERTIES_I) 234 | 235 | recovered = trio.Semaphore(initial_value = 0) 236 | def ep_connectivity_changed(iface, changed, invalidated): 237 | if iface == MCTPD_ENDPOINT_I and 'Connectivity' in changed: 238 | if 'Available' == changed['Connectivity'].value: 239 | recovered.release() 240 | 241 | await ep_props.on_properties_changed(ep_connectivity_changed) 242 | 243 | # Disable the endpoint device 244 | del mctpd.network.endpoints[0] 245 | 246 | ep_ep = await ep.get_interface(MCTPD_ENDPOINT_I) 247 | await ep_ep.call_recover() 248 | 249 | # Force the first poll to fail 250 | await trio.sleep(1) 251 | 252 | # Reset the endpoint device and re-enable it 253 | dev.reset() 254 | mctpd.network.add_endpoint(dev) 255 | 256 | with trio.move_on_after(2 * MCTPD_TRECLAIM) as expected: 257 | await recovered.acquire() 258 | 259 | assert not expected.cancelled_caught 260 | 261 | async def test_recover_endpoint_exchange(dbus, mctpd): 262 | iface = mctpd.system.interfaces[0] 263 | dev = mctpd.network.endpoints[0] 264 | mctp = await dbus.get_proxy_object(MCTPD_C, MCTPD_MCTP_P) 265 | mctp_iface = await mctpd_mctp_iface_obj(dbus, iface) 266 | (eid, net, path, new) = await mctp_iface.call_setup_endpoint(dev.lladdr) 267 | 268 | ep = await dbus.get_proxy_object(MCTPD_C, path) 269 | ep_props = await ep.get_interface(DBUS_PROPERTIES_I) 270 | 271 | degraded = trio.Semaphore(initial_value = 0) 272 | def ep_connectivity_changed(iface, changed, invalidated): 273 | if iface == MCTPD_ENDPOINT_I and 'Connectivity' in changed: 274 | if 'Degraded' == changed['Connectivity'].value: 275 | degraded.release() 276 | 277 | await ep_props.on_properties_changed(ep_connectivity_changed) 278 | 279 | mctp_objmgr = await mctp.get_interface(DBUS_OBJECT_MANAGER_I) 280 | 281 | removed = trio.Semaphore(initial_value = 0) 282 | def ep_removed(ep_path, interfaces): 283 | if ep_path == path and MCTPD_ENDPOINT_I in interfaces: 284 | removed.release() 285 | 286 | await mctp_objmgr.on_interfaces_removed(ep_removed) 287 | 288 | added = trio.Semaphore(initial_value = 0) 289 | def ep_added(ep_path, content): 290 | if MCTPD_ENDPOINT_I in content: 291 | added.release() 292 | 293 | await mctp_objmgr.on_interfaces_added(ep_added) 294 | 295 | # Remove the current device 296 | del mctpd.network.endpoints[0] 297 | 298 | ep_ep = await ep.get_interface(MCTPD_ENDPOINT_I) 299 | await ep_ep.call_recover() 300 | 301 | # Force the first poll to fail 302 | await trio.sleep(1) 303 | 304 | # Add a new the endpoint device at the same physical address (different UUID) 305 | mctpd.network.add_endpoint(Endpoint(dev.iface, dev.lladdr, types = dev.types)) 306 | 307 | with trio.move_on_after(2 * MCTPD_TRECLAIM) as expected: 308 | await added.acquire() 309 | await removed.acquire() 310 | await degraded.acquire() 311 | 312 | assert not expected.cancelled_caught 313 | 314 | """ Test that we get the correct EID allocated (and the usual route/neigh setup) 315 | on an AssignEndpointStatic call """ 316 | async def test_assign_endpoint_static(dbus, mctpd): 317 | iface = mctpd.system.interfaces[0] 318 | dev = mctpd.network.endpoints[0] 319 | mctp = await mctpd_mctp_iface_obj(dbus, iface) 320 | static_eid = 12 321 | 322 | (eid, _, _, new) = await mctp.call_assign_endpoint_static( 323 | dev.lladdr, 324 | static_eid 325 | ) 326 | 327 | assert eid == static_eid 328 | assert new 329 | 330 | assert len(mctpd.system.neighbours) == 1 331 | neigh = mctpd.system.neighbours[0] 332 | assert neigh.lladdr == dev.lladdr 333 | assert neigh.eid == static_eid 334 | assert len(mctpd.system.routes) == 2 335 | 336 | """ Test that we can repeat an AssignEndpointStatic call with the same static 337 | EID""" 338 | async def test_assign_endpoint_static_allocated(dbus, mctpd): 339 | iface = mctpd.system.interfaces[0] 340 | mctp = await mctpd_mctp_iface_obj(dbus, iface) 341 | dev = mctpd.network.endpoints[0] 342 | static_eid = 12 343 | 344 | (eid, _, _, new) = await mctp.call_assign_endpoint_static( 345 | dev.lladdr, 346 | static_eid, 347 | ) 348 | 349 | assert eid == static_eid 350 | assert new 351 | 352 | # repeat, same EID 353 | (eid, _, _, new) = await mctp.call_assign_endpoint_static( 354 | dev.lladdr, 355 | static_eid, 356 | ) 357 | 358 | assert eid == static_eid 359 | assert not new 360 | 361 | """ Test that we cannot assign a conflicting static EID """ 362 | async def test_assign_endpoint_static_conflict(dbus, mctpd): 363 | iface = mctpd.system.interfaces[0] 364 | mctp = await mctpd_mctp_iface_obj(dbus, iface) 365 | dev1 = mctpd.network.endpoints[0] 366 | 367 | dev2 = Endpoint(iface, bytes([0x1e])) 368 | mctpd.network.add_endpoint(dev2) 369 | 370 | # dynamic EID assigment for dev1 371 | (eid, _, _, new) = await mctp.call_assign_endpoint( 372 | dev1.lladdr, 373 | ) 374 | 375 | assert new 376 | 377 | # try to assign dev2 with the dev1's existing EID 378 | with pytest.raises(asyncdbus.errors.DBusError) as ex: 379 | await mctp.call_assign_endpoint_static(dev2.lladdr, eid) 380 | 381 | assert str(ex.value) == "Address in use" 382 | 383 | """ Test that we cannot re-assign a static EID to an endpoint that already has 384 | a different EID allocated""" 385 | async def test_assign_endpoint_static_varies(dbus, mctpd): 386 | iface = mctpd.system.interfaces[0] 387 | dev = mctpd.network.endpoints[0] 388 | mctp = await mctpd_mctp_iface_obj(dbus, iface) 389 | static_eid = 12 390 | 391 | (eid, _, _, new) = await mctp.call_assign_endpoint_static( 392 | dev.lladdr, 393 | static_eid 394 | ) 395 | 396 | assert eid == static_eid 397 | assert new 398 | 399 | with pytest.raises(asyncdbus.errors.DBusError) as ex: 400 | await mctp.call_assign_endpoint_static(dev.lladdr, 13) 401 | 402 | assert str(ex.value) == "Already assigned a different EID" 403 | 404 | """ Test that the mctpd control protocol responder support has support 405 | for a basic Get Endpoint ID command""" 406 | async def test_get_endpoint_id(dbus, mctpd): 407 | iface = mctpd.system.interfaces[0] 408 | dev = mctpd.network.endpoints[0] 409 | mctp = await mctpd_mctp_iface_obj(dbus, iface) 410 | dev.eid = 12 411 | 412 | await mctpd.system.add_route(mctpd.system.Route(iface, dev.eid, 0)) 413 | await mctpd.system.add_neighbour( 414 | mctpd.system.Neighbour(iface, dev.lladdr, dev.eid) 415 | ) 416 | 417 | rsp = await dev.send_control(mctpd.network.mctp_socket, 0x02) 418 | 419 | # command code 420 | assert rsp[1] == 0x02 421 | # completion code indicates success 422 | assert rsp[2] == 0x00 423 | # EID matches the system 424 | assert rsp[3] == mctpd.system.addresses[0].eid 425 | 426 | """ During a LearnEndpoint's Get Endpoint ID exchange, return a response 427 | from a different command; in this case Get Message Type Support, which happens 428 | to be the same length as a the expected Get Endpoint ID response.""" 429 | async def test_learn_endpoint_invalid_response_command(dbus, mctpd): 430 | class BusyEndpoint(Endpoint): 431 | async def handle_mctp_control(self, sock, src_addr, msg): 432 | flags, opcode = msg[0:2] 433 | if opcode != 2: 434 | return await super().handle_mctp_control(sock, src_addr, msg) 435 | dst_addr = MCTPSockAddr.for_ep_resp(self, src_addr, sock.addr_ext) 436 | msg = bytes([flags & 0x1f, 0x05, 0x00, 0x02, 0x00, 0x01]) 437 | await sock.send(dst_addr, msg) 438 | 439 | iface = mctpd.system.interfaces[0] 440 | ep = BusyEndpoint(iface, bytes([0x1e]), eid = 15) 441 | mctpd.network.add_endpoint(ep) 442 | mctp = await mctpd_mctp_iface_obj(dbus, iface) 443 | 444 | with pytest.raises(asyncdbus.errors.DBusError) as ex: 445 | rc = await mctp.call_learn_endpoint(ep.lladdr) 446 | 447 | assert str(ex.value) == "Request failed" 448 | 449 | """ Ensure a response with an invalid IID is discarded """ 450 | async def test_learn_endpoint_invalid_response_iid(dbus, mctpd): 451 | class InvalidIIDEndpoint(Endpoint): 452 | async def handle_mctp_control(self, sock, src_addr, msg): 453 | # bump IID 454 | flags = msg[0] 455 | iid_mask = 0x1d 456 | flags = (flags & ~iid_mask) | ((flags + 1) & iid_mask) 457 | msg = bytes([flags]) + msg[1:] 458 | return await super().handle_mctp_control(sock, src_addr, msg) 459 | 460 | iface = mctpd.system.interfaces[0] 461 | ep = InvalidIIDEndpoint(iface, bytes([0x1e]), eid = 15) 462 | mctpd.network.add_endpoint(ep) 463 | mctp = await mctpd_mctp_iface_obj(dbus, iface) 464 | 465 | with pytest.raises(asyncdbus.errors.DBusError) as ex: 466 | await mctp.call_learn_endpoint(ep.lladdr) 467 | 468 | assert str(ex.value) == "Request failed" 469 | 470 | """ Ensure we're parsing Get Message Type Support responses""" 471 | async def test_query_message_types(dbus, mctpd): 472 | iface = mctpd.system.interfaces[0] 473 | ep = mctpd.network.endpoints[0] 474 | ep_types = [0, 1, 5] 475 | ep.types = ep_types 476 | 477 | mctp = await mctpd_mctp_iface_obj(dbus, iface) 478 | 479 | (eid, net, path, new) = await mctp.call_setup_endpoint(ep.lladdr) 480 | 481 | assert eid == ep.eid 482 | 483 | ep = await mctpd_mctp_endpoint_common_obj(dbus, path) 484 | 485 | query_types = list(await ep.get_supported_message_types()) 486 | ep_types.sort() 487 | query_types.sort() 488 | 489 | assert ep_types == query_types 490 | 491 | """ Network1.LocalEIDs should reflect locally-assigned EID state """ 492 | async def test_network_local_eids_single(dbus, mctpd): 493 | iface = mctpd.system.interfaces[0] 494 | 495 | net = await mctpd_mctp_network_obj(dbus, iface.net) 496 | eids = list(await net.get_local_eids()) 497 | 498 | assert eids == [8] 499 | 500 | async def test_network_local_eids_multiple(dbus, mctpd): 501 | iface = mctpd.system.interfaces[0] 502 | await mctpd.system.add_address(mctpd.system.Address(iface, 9)) 503 | 504 | net = await mctpd_mctp_network_obj(dbus, iface.net) 505 | eids = list(await net.get_local_eids()) 506 | 507 | assert eids == [8, 9] 508 | 509 | async def test_network_local_eids_none(dbus, mctpd): 510 | iface = mctpd.system.interfaces[0] 511 | await mctpd.system.del_address(mctpd.system.Address(iface, 8)) 512 | 513 | net = await mctpd_mctp_network_obj(dbus, iface.net) 514 | eids = list(await net.get_local_eids()) 515 | 516 | assert eids == [] 517 | 518 | async def test_concurrent_recovery_setup(dbus, mctpd): 519 | iface = mctpd.system.interfaces[0] 520 | mctp_i = await mctpd_mctp_iface_obj(dbus, iface) 521 | 522 | # mctpd context tracks 20 peer objects by default, add and set up 19 so we 523 | # reach the allocation boundary. 524 | split = 19 525 | for i in range(split): 526 | pep = Endpoint(iface, bytes([0x1e + i])) 527 | mctpd.network.add_endpoint(pep) 528 | (_, _, path, _) = await mctp_i.call_setup_endpoint(pep.lladdr) 529 | 530 | # Grab the DBus path for an endpoint that we will cause to be removed from 531 | # the network through the recovery path. Arbitrarily use the most recent 532 | # one added 533 | ep = await dbus.get_proxy_object(MCTPD_C, path) 534 | ep_props = await ep.get_interface(DBUS_PROPERTIES_I) 535 | 536 | # Set up a match for Connectivity transitioning to Degraded on the endpoint 537 | # for which we request recovery 538 | degraded = trio.Semaphore(initial_value = 0) 539 | def ep_connectivity_changed(iface, changed, invalidated): 540 | if iface == MCTPD_ENDPOINT_I and 'Connectivity' in changed: 541 | if 'Degraded' == changed['Connectivity'].value: 542 | degraded.release() 543 | await ep_props.on_properties_changed(ep_connectivity_changed) 544 | 545 | # Set up a match for the recovery endpoint object being removed from DBus 546 | mctp_p = await dbus.get_proxy_object(MCTPD_C, MCTPD_MCTP_P) 547 | mctp_objmgr = await mctp_p.get_interface(DBUS_OBJECT_MANAGER_I) 548 | removed = trio.Semaphore(initial_value = 0) 549 | def ep_removed(ep_path, interfaces): 550 | if ep_path == path and MCTPD_ENDPOINT_I in interfaces: 551 | removed.release() 552 | 553 | await mctp_objmgr.on_interfaces_removed(ep_removed) 554 | 555 | # Delete the endpoint from the network so its recovery will fail after 556 | # timeout. Note we delete at the split index as the network was already 557 | # populated with the default endpoint 558 | del mctpd.network.endpoints[split] 559 | 560 | # Begin recovery for the endpoint ... 561 | ep_ep = await ep.get_interface(MCTPD_ENDPOINT_I) 562 | await ep_ep.call_recover() 563 | 564 | # ... and wait until we're notified the recovery process has begun 565 | with trio.move_on_after(1) as expected: 566 | await degraded.acquire() 567 | assert not expected.cancelled_caught 568 | 569 | # Now that we're asynchronously waiting for the endpoint recovery process 570 | # to complete, force a realloc() of the peer object array by adding a new 571 | # peer, which will invalidate the recovering peer's pointer 572 | pep = Endpoint(iface, bytes([0x1e + split])) 573 | mctpd.network.add_endpoint(pep) 574 | (_, _, _, new) = await mctp_i.call_setup_endpoint(pep.lladdr) 575 | assert new 576 | 577 | # Verify the recovery process completed gracefully with removal of the 578 | # endpoint's DBus object 579 | with trio.move_on_after(2 * MCTPD_TRECLAIM) as expected: 580 | await removed.acquire() 581 | assert not expected.cancelled_caught 582 | 583 | """ Bridged EP can be discovered via Network1.LearnEndpoint """ 584 | async def test_bridged_learn_endpoint(dbus, mctpd): 585 | iface = mctpd.system.interfaces[0] 586 | ep = mctpd.network.endpoints[0] 587 | br_ep = Endpoint(iface, bytes(), eid = 10, types = [0, 2]) 588 | ep.add_bridged_ep(br_ep) 589 | mctpd.network.add_endpoint(br_ep) 590 | 591 | await mctpd.system.add_route(mctpd.system.Route(iface, br_ep.eid, 1)) 592 | # static neighbour; no gateway route support at present 593 | await mctpd.system.add_neighbour(mctpd.system.Neighbour(iface, ep.lladdr, br_ep.eid)) 594 | 595 | net = await mctpd_mctp_network_obj(dbus, iface.net) 596 | (path, new) = await net.call_learn_endpoint(br_ep.eid) 597 | 598 | assert path == f'/au/com/codeconstruct/mctp1/networks/1/endpoints/{br_ep.eid}' 599 | assert new 600 | 601 | """ Change a network id, while we have an active endpoint on that net """ 602 | async def test_change_network(dbus, mctpd): 603 | iface = mctpd.system.interfaces[0]; 604 | ep = mctpd.network.endpoints[0] 605 | 606 | net = await mctpd_mctp_network_obj(dbus, 1) 607 | assert net is not None 608 | 609 | iface.net = 2 610 | await mctpd.system.notify_interface(iface) 611 | 612 | # we should now have a new net at 2 613 | net = await mctpd_mctp_network_obj(dbus, 2) 614 | assert net is not None 615 | 616 | # and nothing at 1 617 | with pytest.raises(asyncdbus.errors.DBusError) as ex: 618 | await mctpd_mctp_network_obj(dbus, 1) 619 | assert str(ex.value) == "Unknown object '/au/com/codeconstruct/mctp1/networks/1'." 620 | 621 | # endpoint should be present under 2/ 622 | ep = await mctpd_mctp_endpoint_common_obj(dbus, 623 | '/au/com/codeconstruct/mctp1/networks/2/endpoints/8' 624 | ) 625 | assert ep is not None 626 | 627 | """ Delete our only interface """ 628 | async def test_del_interface_last(dbus, mctpd): 629 | iface = mctpd.system.interfaces[0] 630 | await mctpd.system.del_interface(iface) 631 | 632 | # interface should be gone 633 | with pytest.raises(asyncdbus.errors.DBusError): 634 | await mctpd_mctp_iface_obj(dbus, iface) 635 | 636 | # network should be gone 637 | with pytest.raises(asyncdbus.errors.DBusError): 638 | await mctpd_mctp_network_obj(dbus, iface.net) 639 | 640 | """ Remove and re-add an interface """ 641 | async def test_add_interface(dbus, mctpd): 642 | net = 1 643 | # Create a new netdevice 644 | iface = mctpd.system.Interface('mctpnew', 10, net, bytes([]), 68, 254, True) 645 | await mctpd.system.add_interface(iface) 646 | await mctpd.system.add_address(mctpd.system.Address(iface, 88)) 647 | mctp = await mctpd_mctp_iface_obj(dbus, iface) 648 | 649 | # Add an endpoint on the interface 650 | mctpd.network.add_endpoint(Endpoint(iface, bytes([]), types = [0, 1])) 651 | 652 | static_eid = 30 653 | (eid, _, _, new) = await mctp.call_assign_endpoint_static( 654 | bytes([]), 655 | static_eid 656 | ) 657 | assert eid == static_eid 658 | assert new 659 | assert mctpd.system.lookup_route(net, static_eid).iface == iface 660 | 661 | # Remove the netdevice 662 | await mctpd.system.del_interface(iface) 663 | 664 | # Interface should be gone 665 | with pytest.raises(asyncdbus.errors.DBusError): 666 | await mctpd_mctp_iface_obj(dbus, iface) 667 | assert mctpd.system.lookup_route(net, static_eid) is None 668 | 669 | # Re-add the same interface name again, with a new ifindex 11 670 | iface = mctpd.system.Interface('mctpnew', 11, net, bytes([]), 68, 254, True) 671 | await mctpd.system.add_interface(iface) 672 | await mctpd.system.add_address(mctpd.system.Address(iface, 89)) 673 | mctp = await mctpd_mctp_iface_obj(dbus, iface) 674 | 675 | # Add an endpoint on the interface 676 | mctpd.network.add_endpoint(Endpoint(iface, bytes([]), types = [0, 1])) 677 | 678 | # Old route should still be gone 679 | assert mctpd.system.lookup_route(net, static_eid) is None 680 | 681 | static_eid = 40 682 | (eid, _, _, new) = await mctp.call_assign_endpoint_static( 683 | bytes([]), 684 | static_eid 685 | ) 686 | assert eid == static_eid 687 | assert new 688 | assert mctpd.system.lookup_route(net, static_eid).iface == iface 689 | -------------------------------------------------------------------------------- /tests/test_mctpd_endpoint.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from mctp_test_utils import * 3 | from mctpd import * 4 | 5 | @pytest.fixture(name="config") 6 | def endpoint_config(): 7 | return """ 8 | mode = "endpoint" 9 | """ 10 | 11 | """ Test if mctpd is running as an endpoint """ 12 | async def test_endpoint_role(dbus, mctpd): 13 | obj = await mctpd_mctp_iface_control_obj(dbus, mctpd.system.interfaces[0]) 14 | role = await obj.get_role() 15 | assert str(role) == "Endpoint" 16 | --------------------------------------------------------------------------------