pax_global_header00006660000000000000000000000064150001214270014502gustar00rootroot0000000000000052 comment=0db45d53db094754631c58fb701732725f86276e batctl-2025.1/000077500000000000000000000000001500012142700130445ustar00rootroot00000000000000batctl-2025.1/.mailmap000066400000000000000000000037151500012142700144730ustar00rootroot00000000000000# # This list is used by git-shortlog to fix a few botched name translations # in the git archive, either because the author's full name was messed up # and/or not always written the same way, making contributions from the # same person appearing not to be so or badly displayed. Also allows for # old email addresses to map to new email addresses. # # For format details, see "man gitmailmap" or "MAPPING AUTHORS" in # "man git-shortlog" on older systems. # # Please keep this list dictionary sorted. # Andreas Langer Andreas Langer Andrew Lunn Antonio Quartulli Antonio Quartulli Antonio Quartulli Antonio Quartulli Antonio Quartulli Antonio Quartulli Antonio Quartulli Linus Lüssing Linus Lüssing Linus Lüssing Marek Lindner Marek Lindner Simon Wunderlich Simon Wunderlich Simon Wunderlich Simon Wunderlich Sven Eckelmann Sven Eckelmann Sven Eckelmann Sven Eckelmann batctl-2025.1/CHANGELOG.rst000066400000000000000000000142071500012142700150710ustar00rootroot00000000000000.. SPDX-License-Identifier: GPL-2.0 2025.1 (2025-04-17) =================== * coding style cleanups and refactoring 2025.0 (2025-02-07) =================== * subsecond precision support for ping intervals * coding style cleanups and refactoring 2024.4 (2024-12-10) =================== * coding style cleanups and refactoring * drop explicit requirement to run as root 2024.3 (2024-10-15) =================== * bugs squashed: - fix header for neighbor table with B.A.T.M.A.N. V algorithm 2024.2 (2024-06-20) =================== * (no changes) 2024.1 (2024-04-05) =================== * (no changes) 2024.0 (2024-02-01) =================== * Add stateless multicast packet format support * bugs squashed: - Fix various length checks in tcpdump-like subcommand 2023.3 (2023-11-15) =================== * (no changes) 2023.2 (2023-08-16) =================== * (no changes) 2023.1 (2023-05-25) =================== * Synchronize with kernel headers 2023.0 (2023-01-26) =================== * (no changes) 2022.3 (2022-11-10) =================== * (no changes) 2022.2 (2022-07-26) =================== * (no changes) 2022.1 (2022-05-06) =================== * bugs squashed: - drop additional delay after the ping packet 2022.0 (2022-02-03) =================== * (no changes) 2021.4 (2021-11-19) =================== * (no changes) 2021.3 (2021-09-14) =================== * (no changes) 2021.2 (2021-08-20) =================== * manpage cleanups * coding style cleanups and refactoring 2021.1 (2021-05-18) =================== * add various commands to print generic netlink replies as JSON * coding style cleanups and refactoring 2021.0 (2021-01-28) =================== * Drop support for batman-adv's sysfs+debugfs * allow to select routing algorithm during creation of interface * bugs squashed: - fix query of meshif's ap_isolation status - ignore "interface already exists" error during "interface add" 2020.4 (2020-10-27) =================== * bugs squashed: - Fix endianness in ICMPv6 Echo Request/Reply parsing 2020.3 (2020-08-24) =================== * add per interface hop penalty command 2020.2 (2020-07-06) =================== * coding style cleanups and refactoring * drop support for automatic destruction of empty meshifs * bugs squashed: - Fix parsing of radiotap headers on big endian systems 2020.1 (2020-04-24) =================== * bugs squashed: - Fix error code on throughputmeter errors 2020.0 (2020-03-04) =================== * (no changes) 2019.5 (2019-12-12) =================== * (no changes) 2019.4 (2019-10-25) =================== * fix deprecation warning for option '-m' 2019.3 (2019-08-01) =================== * add tcpdump support for MCAST TVLV, unicast fragments and coded packets * implement support for multicast RTR flags * avoid some kernel deprecation warning by using more generic netlink over sysfs * use type specific prefixes to select mesh interface or vlan instead of '-m' * add support for hardif specific settings 2019.2 (2019-05-23) =================== * coding style cleanups and refactoring * add multicast_fanout setting subcommand * implement netlink based support for remaining sysfs-only features * drop support for deprecated log command support * remove non-netlink support for translating MAC addresses to originators 2019.1 (2019-03-28) =================== * coding style cleanups and refactoring * introduce support for batadv meshif, hardif and vlan configuration via netlink * replace multicast_mode with multicast_forceflood settings subcommand * add hop_penalty setting subcommand 2019.0 (2019-02-01) =================== * coding style cleanups and refactoring * add gateway selection manpage section for B.A.T.M.A.N. V * bugs squashed: - re-integrate support for translation table unicast/multicast filter - avoid incorrect warning about disabled mesh interface when debugfs support is not enabled in batman-adv 2018.4 (2018-11-14) =================== * coding style cleanups and refactoring * correction of manpage spelling errors * new subcommand "event" to receive netlink notifications * infrastructure to disable commands during build time * drop of the legacy vis subcommands 2018.3 (2018-09-14) =================== * (no changes) 2018.2 (2018-07-10) =================== * (no changes) 2018.1 (2018-04-25) =================== * synchronization of batman-adv netlink and packet headers * add DAT cache and multicast flags netlink support * disable translation support for non-unicast mac addresses 2018.0 (2018-02-26) =================== * synchronization of batman-adv netlink and packet headers * mark licenses clearer, change batman-adv UAPI header from ISC to MIT * coding style cleanups and refactoring 2017.4 (2017-12-05) =================== * synchronization of batman-adv netlink header * coding style cleanups and refactoring * documentation cleanup * bugs squashed: - improve error handling for libnl related errors - add checks for various allocation errors 2017.3 (2017-09-28) =================== * bugs squashed: - Fix error messages on traceroute send failures 2017.2 (2017-06-28) =================== * coding style cleanups and refactoring 2017.1 (2017-05-23) ==================== * (no changes) 2017.0 (2017-02-28) =================== * remove root check for read-only sysfs and rtnl functionality * coding style cleanups * bugs squashed: - fix check for root priviliges when started under modified effective uid 2016.5 (2016-12-15) =================== * reimplement traceroute/ping commands in userspace without debugfs * switch interface manipulation from (legacy) sysfs to rtnetlink * coding style cleanups 2016.4 (2016-10-27) =================== * integrate support for batman-adv netlink * coding style cleanups * documentation updates * bugs squashed: - fix endless loop in TP meter on some platforms - fix build errors caused by name conflicts 2016.3 (2016-09-01) =================== * synchronize common headers with batman-adv * support multicast logging and debug table * split tcpdump OGM packet filter in OGM and OGMv2 filter * add infrastructure to communicate with batadv netlink family * integrate command to control new kernel throughput meter batctl-2025.1/LICENSES/000077500000000000000000000000001500012142700142515ustar00rootroot00000000000000batctl-2025.1/LICENSES/deprecated/000077500000000000000000000000001500012142700163515ustar00rootroot00000000000000batctl-2025.1/LICENSES/deprecated/ISC000066400000000000000000000020261500012142700167120ustar00rootroot00000000000000Valid-License-Identifier: ISC SPDX-URL: https://spdx.org/licenses/ISC.html Usage-Guide: To use the ISC License put the following SPDX tag/value pair into a comment according to the placement guidelines in the licensing rules documentation: SPDX-License-Identifier: ISC License-Text: ISC License Copyright (c) Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. batctl-2025.1/LICENSES/preferred/000077500000000000000000000000001500012142700162275ustar00rootroot00000000000000batctl-2025.1/LICENSES/preferred/GPL-2.0000066400000000000000000000443061500012142700171000ustar00rootroot00000000000000Valid-License-Identifier: GPL-2.0 Valid-License-Identifier: GPL-2.0+ SPDX-URL: https://spdx.org/licenses/GPL-2.0.html Usage-Guide: To use this license in source code, put one of the following SPDX tag/value pairs into a comment according to the placement guidelines in the licensing rules documentation. For 'GNU General Public License (GPL) version 2 only' use: SPDX-License-Identifier: GPL-2.0 For 'GNU General Public License (GPL) version 2 or any later version' use: SPDX-License-Identifier: GPL-2.0+ License-Text: GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. batctl-2025.1/LICENSES/preferred/MIT000066400000000000000000000025341500012142700166070ustar00rootroot00000000000000Valid-License-Identifier: MIT SPDX-URL: https://spdx.org/licenses/MIT.html Usage-Guide: To use the MIT License put the following SPDX tag/value pair into a comment according to the placement guidelines in the licensing rules documentation: SPDX-License-Identifier: MIT License-Text: MIT License Copyright (c) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. batctl-2025.1/Makefile000077500000000000000000000125631500012142700145160ustar00rootroot00000000000000#!/usr/bin/make -f # SPDX-License-Identifier: GPL-2.0 # -*- makefile -*- # # Copyright (C) B.A.T.M.A.N. contributors # # License-Filename: LICENSES/preferred/GPL-2.0 # just for backward compatibility - please use CONFIG_bisect_iv instead export CONFIG_BATCTL_BISECT=n # batctl build BINARY_NAME = batctl obj-y += bat-hosts.o obj-y += debug.o obj-y += functions.o obj-y += genl.o obj-y += genl_json.o obj-y += hash.o obj-y += icmp_helper.o obj-y += main.o obj-y += netlink.o obj-y += sys.o define add_command CONFIG_$(1):=$(2) ifneq ($$(CONFIG_$(1)),y) ifneq ($$(CONFIG_$(1)),n) $$(warning invalid value for parameter CONFIG_$(1): $$(CONFIG_$(1))) endif endif obj-$$(CONFIG_$(1)) += $(1).o endef # add_command # using the make parameter CONFIG_* (e.g. CONFIG_bisect_iv) with the value 'y' # enables the related feature and 'n' disables it $(eval $(call add_command,aggregation,y)) $(eval $(call add_command,ap_isolation,y)) $(eval $(call add_command,backbonetable,y)) $(eval $(call add_command,bisect_iv,$(CONFIG_BATCTL_BISECT))) $(eval $(call add_command,bla_backbone_json,y)) $(eval $(call add_command,bla_claim_json,y)) $(eval $(call add_command,bonding,y)) $(eval $(call add_command,bridge_loop_avoidance,y)) $(eval $(call add_command,claimtable,y)) $(eval $(call add_command,dat_cache,y)) $(eval $(call add_command,dat_cache_json,y)) $(eval $(call add_command,distributed_arp_table,y)) $(eval $(call add_command,elp_interval,y)) $(eval $(call add_command,event,y)) $(eval $(call add_command,fragmentation,y)) $(eval $(call add_command,gateways,y)) $(eval $(call add_command,gateways_json,y)) $(eval $(call add_command,gw_mode,y)) $(eval $(call add_command,hardif_json,y)) $(eval $(call add_command,hardifs_json,y)) $(eval $(call add_command,hop_penalty,y)) $(eval $(call add_command,interface,y)) $(eval $(call add_command,isolation_mark,y)) $(eval $(call add_command,loglevel,y)) $(eval $(call add_command,mcast_flags,y)) $(eval $(call add_command,mcast_flags_json,y)) $(eval $(call add_command,mesh_json,y)) $(eval $(call add_command,multicast_fanout,y)) $(eval $(call add_command,multicast_forceflood,y)) $(eval $(call add_command,multicast_mode,y)) $(eval $(call add_command,neighbors,y)) $(eval $(call add_command,neighbors_json,y)) $(eval $(call add_command,network_coding,y)) $(eval $(call add_command,orig_interval,y)) $(eval $(call add_command,originators,y)) $(eval $(call add_command,originators_json,y)) $(eval $(call add_command,ping,y)) $(eval $(call add_command,routing_algo,y)) $(eval $(call add_command,statistics,y)) $(eval $(call add_command,tcpdump,y)) $(eval $(call add_command,throughput_override,y)) $(eval $(call add_command,throughputmeter,y)) $(eval $(call add_command,traceroute,y)) $(eval $(call add_command,transglobal,y)) $(eval $(call add_command,translate,y)) $(eval $(call add_command,translocal,y)) $(eval $(call add_command,transtable_global_json,y)) $(eval $(call add_command,transtable_local_json,y)) $(eval $(call add_command,vlan_json,y)) MANPAGE = man/batctl.8 # batctl flags and options CFLAGS += -Wall -W -std=gnu99 -fno-strict-aliasing -MD -MP CPPFLAGS += -D_GNU_SOURCE LDLIBS += -lm -lrt # disable verbose output ifneq ($(findstring $(MAKEFLAGS),s),s) ifndef V Q_CC = @echo ' ' CC $@; Q_LD = @echo ' ' LD $@; export Q_CC export Q_LD endif endif ifeq ($(origin PKG_CONFIG), undefined) PKG_CONFIG = pkg-config ifeq ($(shell which $(PKG_CONFIG) 2>/dev/null),) $(error $(PKG_CONFIG) not found) endif endif ifeq ($(origin LIBNL_CFLAGS) $(origin LIBNL_LDLIBS), undefined undefined) LIBNL_NAME ?= libnl-3.0 ifeq ($(shell $(PKG_CONFIG) --modversion $(LIBNL_NAME) 2>/dev/null),) $(error No $(LIBNL_NAME) development libraries found!) endif LIBNL_CFLAGS += $(shell $(PKG_CONFIG) --cflags $(LIBNL_NAME)) LIBNL_LDLIBS += $(shell $(PKG_CONFIG) --libs $(LIBNL_NAME)) endif CFLAGS += $(LIBNL_CFLAGS) LDLIBS += $(LIBNL_LDLIBS) ifeq ($(origin LIBNL_GENL_CFLAGS) $(origin LIBNL_GENL_LDLIBS), undefined undefined) LIBNL_GENL_NAME ?= libnl-genl-3.0 ifeq ($(shell $(PKG_CONFIG) --modversion $(LIBNL_GENL_NAME) 2>/dev/null),) $(error No $(LIBNL_GENL_NAME) development libraries found!) endif LIBNL_GENL_CFLAGS += $(shell $(PKG_CONFIG) --cflags $(LIBNL_GENL_NAME)) LIBNL_GENL_LDLIBS += $(shell $(PKG_CONFIG) --libs $(LIBNL_GENL_NAME)) endif CFLAGS += $(LIBNL_GENL_CFLAGS) LDLIBS += $(LIBNL_GENL_LDLIBS) # standard build tools CC ?= gcc RM ?= rm -f INSTALL ?= install MKDIR ?= mkdir -p COMPILE.c = $(Q_CC)$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c LINK.o = $(Q_LD)$(CC) $(CFLAGS) $(LDFLAGS) $(TARGET_ARCH) # standard install paths PREFIX = /usr/local SBINDIR = $(PREFIX)/sbin MANDIR = $(PREFIX)/share/man # try to generate revision REVISION= $(shell if [ -d .git ]; then \ echo $$(git describe --always --dirty --match "v*" |sed 's/^v//' 2> /dev/null || echo "[unknown]"); \ fi) ifneq ($(REVISION),) CPPFLAGS += -DSOURCE_VERSION=\"$(REVISION)\" endif # default target all: $(BINARY_NAME) # standard build rules .SUFFIXES: .o .c .c.o: $(COMPILE.c) -o $@ $< $(BINARY_NAME): $(obj-y) $(LINK.o) $^ $(LDLIBS) -o $@ clean: $(RM) $(BINARY_NAME) $(obj-y) $(obj-n) $(DEP) install: $(BINARY_NAME) $(MKDIR) $(DESTDIR)$(SBINDIR) $(MKDIR) $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -m 0755 $(BINARY_NAME) $(DESTDIR)$(SBINDIR) $(INSTALL) -m 0644 $(MANPAGE) $(DESTDIR)$(MANDIR)/man8 # load dependencies DEP = $(obj-y:.o=.d) $(obj-n:.o=.d) -include $(DEP) .PHONY: all clean install batctl-2025.1/README.rst000066400000000000000000001222151500012142700145360ustar00rootroot00000000000000.. SPDX-License-Identifier: GPL-2.0 ========================================================== batctl - B.A.T.M.A.N. advanced control and management tool ========================================================== Introduction ============ Why do I need batctl ? B.A.T.M.A.N. advanced operates on layer 2 and thus all hosts participating in the virtual switch are completely transparent for all protocols above layer 2. Therefore the common diagnosis tools do not work as expected. To overcome these problems batctl was created. At the moment batctl contains ping, traceroute, tcpdump and interfaces to the kernel module settings. How does it work ? ------------------ batctl uses the raw packet sockets to inject custom icmp packets into the data flow. That's why ping and traceroute work almost like their IP based counterparts. Tcpdump was designed because B.A.T.M.A.N. advanced encapsulates all traffic within batman packets, so that the normal tcpdump would not recognize the packets. The bat-hosts file ------------------ This file is similar to the /etc/hosts file. You can write one MAC address and one host name per line. batctl will analyze the file to find the matching MAC address to your provided host name. Host names are much easier to remember than MAC addresses. ;) Commands ======== batctl interface ---------------- display or modify the interface settings Usage:: batctl interface|if [add|del iface(s)] Example:: $ batctl interface eth0: active batctl ping ----------- Sends a Layer 2 batman-adv ping to check round trip time and connectivity Usage:: batctl ping [parameters] mac|bat-host|host-name|IP-address parameters: -c ping packet count -h print this help -i interval in seconds -t timeout in seconds -T don't try to translate mac to originator address -R record route Example:: $ batctl ping fe:fe:00:00:09:01 PING fe:fe:00:00:09:01 (fe:fe:00:00:09:01) 19(47) bytes of data 19 bytes from fe:fe:00:00:09:01 icmp_seq=1 ttl=43 time=8.74 ms 19 bytes from fe:fe:00:00:09:01 icmp_seq=2 ttl=43 time=7.48 ms 19 bytes from fe:fe:00:00:09:01 icmp_seq=3 ttl=43 time=8.23 ms ^C--- fe:fe:00:00:09:01 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss rtt min/avg/max/mdev = 7.476/8.151/8.743/1.267 ms batctl statistics ----------------- The batman-adv kernel module maintains a number of traffic counters which are exported to user space. With batctl these counters can be easily retrieved. The output may vary depending on which features have been compiled into the kernel module. For example, if the distributed arp table (short: dat) wasn't selected as an option at compile time its counters won't be shown. Each module subsystem has its own counters which are indicated by their prefixes: mgmt: mesh protocol counters tt: translation table counters dat: distributed arp table counters All counters without a prefix concern payload (pure user data) traffic. Usage:: batctl statistics Example:: $ batctl statistics tx: 14 tx_bytes: 1316 tx_errors: 0 rx: 14 rx_bytes: 1316 forward: 0 forward_bytes: 0 mgmt_tx: 18 mgmt_tx_bytes: 762 mgmt_rx: 17 mgmt_rx_bytes: 1020 tt_request_tx: 0 tt_request_rx: 0 tt_response_tx: 0 tt_response_rx: 0 tt_roam_adv_tx: 0 tt_roam_adv_rx: 0 dat_request_tx: 0 dat_request_rx: 0 dat_reply_tx: 1 dat_reply_rx: 0 batctl tcpdump -------------- tcpdump layer 2 and/or layer 3 traffic on the given interface Usage:: batctl tcpdump [parameters] interface [interface] parameters: -c compat filter - only display packets matching own compat version (14) -h print this help -n don't convert addresses to bat-host names -p dump specific packet type -x dump all packet types except specified packet types: 1 - batman ogm packets 2 - batman icmp packets 4 - batman unicast packets 8 - batman broadcast packets 16 - batman unicast tvlv packets 32 - batman fragmented packets 64 - batman tt / roaming packets 128 - non batman packets 129 - batman ogm & non batman packets tcpdump supports standard interfaces as well as raw wifi interfaces running in monitor mode. Example output for tcpdump:: $ batctl tcpdump mesh0 01:51:42.401188 BAT kansas: OGM via neigh kansas, seqno 6718, tq 255, ttl 50, v 9, flags [..I], length 28 01:51:42.489735 BAT kansas: OGM via neigh wyoming, seqno 6718, tq 245, ttl 49, v 9, flags [.D.], length 28 01:51:42.510330 BAT wyoming: OGM via neigh wyoming, seqno 6721, tq 255, ttl 50, v 9, flags [..I], length 28 01:51:42.601092 BAT wyoming: OGM via neigh kansas, seqno 6721, tq 245, ttl 49, v 9, flags [.D.], length 28 01:51:43.361076 BAT kansas > wyoming: ICMP echo request, id 0, seq 1, ttl 1, v 9, length 19 01:51:43.365347 BAT wyoming > kansas: ICMP echo reply, id 0, seq 1, ttl 50, v 9, length 19 01:51:43.372224 BAT kansas > wyoming: ICMP echo request, id 0, seq 2, ttl 1, v 9, length 19 01:51:43.376506 BAT wyoming > kansas: ICMP echo reply, id 0, seq 2, ttl 50, v 9, length 19 01:51:43.381250 BAT kansas: OGM via neigh kansas, seqno 6719, tq 255, ttl 50, v 9, flags [..I], length 28 01:51:43.386281 BAT kansas > wyoming: ICMP echo request, id 0, seq 3, ttl 1, v 9, length 19 01:51:43.387910 BAT wyoming > kansas: ICMP echo reply, id 0, seq 3, ttl 50, v 9, length 19 01:51:43.479503 BAT kansas: OGM via neigh wyoming, seqno 6719, tq 245, ttl 49, v 9, flags [.D.], length 28 01:51:43.509899 BAT wyoming: OGM via neigh wyoming, seqno 6722, tq 255, ttl 50, v 9, flags [..I], length 28 01:51:43.600999 BAT wyoming: OGM via neigh kansas, seqno 6722, tq 245, ttl 49, v 9, flags [.D.], length 28 01:51:44.381064 BAT kansas: OGM via neigh kansas, seqno 6720, tq 255, ttl 50, v 9, flags [..I], length 28 batctl traceroute ----------------- Traceroute sends 3 packets to each hop, awaits the answers and prints out the response times. Usage:: batctl traceroute [parameters] mac|bat-host|host-name|IP-address Example:: $ batctl traceroute fe:fe:00:00:09:01 traceroute to fe:fe:00:00:09:01 (fe:fe:00:00:09:01), 50 hops max, 19 byte packets 1: fe:fe:00:00:02:01 4.932 ms 2.338 ms 1.333 ms 2: fe:fe:00:00:03:01 6.860 ms 1.579 ms 1.260 ms 3: fe:fe:00:00:04:01 2.342 ms 1.547 ms 1.655 ms 4: fe:fe:00:00:05:01 2.906 ms 2.211 ms 2.253 ms 5: fe:fe:00:00:06:01 3.577 ms 2.687 ms 3.088 ms 6: fe:fe:00:00:07:01 4.217 ms 5.741 ms 3.551 ms 7: fe:fe:00:00:08:01 5.017 ms 5.547 ms 4.294 ms 8: fe:fe:00:00:09:01 5.730 ms 4.970 ms 6.437 ms batctl translate ---------------- Translates a destination (hostname, IPv4, IPv6, MAC, bat_host-name) to the originator mac address responsible for it. Usage:: batctl translate mac|bat-host|host-name|IP-address Example:: $ batctl translate www.google.de 02:ca:fe:af:fe:01 $ batctl translate 02:ca:fe:af:fe:01 02:ca:fe:af:fe:01 $ batctl translate 192.168.1.2 02:ca:fe:af:fe:05 $ batctl translate fe:fe:00:00:09:01 02:ca:fe:af:fe:05 $ batctl translate 2001::1 02:ca:fe:af:fe:05 Debug information tables ======================== batctl backbonetable -------------------- Check the bridge loop avoidance backbone table Usage:: batctl backbonetable|bbt Example:: Originator VID last seen (CRC ) 4a:97:a4:b8:fc:17 on -1 1.376s (0x847a) batctl claimtable ----------------- Check the bridge loop avoidance claim table table Usage:: batctl claimtable|cl Example:: Client VID Originator [o] (CRC ) e4:95:6e:4f:06:28 on -1 by 02:ba:de:af:fe:01 [*] (0xbb73) 08:ee:8b:84:82:8b on -1 by 02:ba:de:af:fe:01 [*] (0xbb73) ac:86:74:9f:4d:80 on -1 by 02:ba:de:af:fe:01 [*] (0x3b7e) 60:14:66:6f:ec:52 on -1 by 02:ba:de:af:fe:01 [*] (0xbb73) 3a:ef:e8:e0:10:02 on -1 by 02:ba:de:af:fe:01 [*] (0xbb73) 56:bd:b4:a7:0b:aa on -1 by 02:ba:de:af:fe:01 [*] (0xbb73) 42:3a:6e:68:01:7d on -1 by 02:ba:de:af:fe:01 [*] (0xbb73) 0c:d7:46:2c:41:39 on -1 by 02:ba:de:af:fe:01 [*] (0xbb73) batctl dat_cache ---------------- display the local D.A.T. cache Usage:: batctl dat_cache|dc Example:: Distributed ARP Table (bat0): IPv4 MAC last-seen * 172.100.0.1 b6:9b:d0:ea:b1:13 0:00 where IPv4: is the IP address of a client in the mesh network MAC: is the MAC address associated to that IP last-seen: is the amount of time since last refresh of this entry batctl gateways --------------- Check the detected (and maybe selected) gateways Usage:: batctl gateways|gwl Example:: Router ( TQ) Next Hop [outgoingIf] Bandwidth 02:62:e7:ab:01:01 (180) ae:1b:bf:52:25:58 [ enp0s1]: 10.0/2.0 MBit 02:62:e7:ab:05:01 (180) ae:1b:bf:52:25:58 [ enp0s1]: 10.0/2.0 MBit 02:62:e7:ab:06:01 (235) ae:1b:bf:52:25:58 [ enp0s1]: 10.0/2.0 MBit 02:62:e7:ab:02:01 (176) ae:1b:bf:52:25:58 [ enp0s1]: 10.0/2.0 MBit 02:62:e7:ab:03:01 (180) ae:1b:bf:52:25:58 [ enp0s1]: 10.0/2.0 MBit 02:62:e7:ab:04:01 (180) ae:1b:bf:52:25:58 [ enp0s1]: 10.0/2.0 MBit batctl mcast_flags ------------------ display local and remote multicast flags Usage:: batctl mcast_flags|mf Example:: Multicast flags (own flags: [U46R4R6.]) * Bridged [U] U * No IGMP/MLD Querier [4/6]: ./. * Shadowing IGMP/MLD Querier [4/6]: 4/6 ------------------------------------------- Originator Flags 02:04:64:a4:39:c1 [U... . .] 02:04:64:a4:39:c2 [...R4R6.] 02:04:64:a4:39:c3 [.... . P] where Originator: the MAC address of the originating (primary interface) batman-adv node Flags: multicast flags of the according node U: wants all unsnoopable multicast traffic, meaning other nodes need to always forward any multicast traffic destined to ff02::1 or 224.0.0.0/24 to it 4: wants all IPv4 multicast traffic, meaning other nodes need to always forward any IPv4 multicast traffic to it 6: wants all IPv6 multicast traffic, meaning other nodes need to always forward any IPv6 multicast traffic to it R4: wants all routable IPv4 multicast traffic, meaning other nodes need to always forward multicast traffic destined to 224.0.0.0/4 excluding 224.0.0.0/24 to it R6: wants all routable IPv6 multicast traffic, meaning other nodes need to always forward multicast traffic destined to ffXY::/16 with Y > 2 (scope greater than link-local) to it P: the node either cannot handle batman-adv multicast packets with a multicast tracker TVLV or one of its hard interfaces has an MTU smaller than 1280 bytes If a node does not have multicast optimizations available (e.g. old batman-adv version or optimizations not compiled in), therefore not announcing any multicast tvlv/flags, a '-' will be displayed instead of '[...]'. batctl neighbors ---------------- Check the neighbors table Usage:: batctl neighbors|n Example:: IF Neighbor last-seen enp0s1 16:7b:3c:c2:bf:b8 4.612s enp0s1 ae:1b:bf:52:25:58 0.740s batctl originators ------------------ Check the Originators table Usage:: batctl originators|o Example:: $ batctl originators [B.A.T.M.A.N. adv 2011.4.0, MainIF/MAC: eth0/fe:fe:00:00:01:01 (bat0)] Originator last-seen (#/255) Nexthop [outgoingIF]: Potential nexthops ... fe:fe:00:00:08:01 0.820s (194) fe:fe:00:00:02:01 [ eth0]: fe:fe:00:00:03:01 ( 65) fe:fe:00:00:02:01 (194) fe:fe:00:00:03:01 0.980s (245) fe:fe:00:00:02:01 [ eth0]: fe:fe:00:00:03:01 ( 81) fe:fe:00:00:02:01 (245) fe:fe:00:00:05:01 0.140s (221) fe:fe:00:00:02:01 [ eth0]: fe:fe:00:00:03:01 ( 76) fe:fe:00:00:02:01 (221) fe:fe:00:00:04:01 0.010s (235) fe:fe:00:00:02:01 [ eth0]: fe:fe:00:00:02:01 (235) fe:fe:00:00:03:01 ( 81) fe:fe:00:00:09:01 0.830s (187) fe:fe:00:00:02:01 [ eth0]: fe:fe:00:00:03:01 ( 63) fe:fe:00:00:02:01 (187) fe:fe:00:00:06:01 0.830s (213) fe:fe:00:00:02:01 [ eth0]: fe:fe:00:00:03:01 ( 71) fe:fe:00:00:02:01 (213) fe:fe:00:00:02:01 0.240s (255) fe:fe:00:00:02:01 [ eth0]: fe:fe:00:00:03:01 ( 81) fe:fe:00:00:02:01 (255) fe:fe:00:00:07:01 0.670s (200) fe:fe:00:00:02:01 [ eth0]: fe:fe:00:00:03:01 ( 68) fe:fe:00:00:02:01 (200) Since 2014.1.0, each batman interface has an individual originator table as well which is only used for routing. These table explain to which neighbor a packet is forwarded when the packet is received on the specified interface. Example:: $ batctl originators -i eth0 [B.A.T.M.A.N. adv master-b82b9b2, IF/MAC: eth0/fe:f0:00:00:02:01 (bat0 BATMAN_IV)] Originator last-seen (#/255) Nexthop [outgoingIF]: Potential nexthops ... fe:f1:00:00:03:01 0.170s (255) fe:f1:00:00:03:01 [ eth1]: fe:f1:00:00:03:01 (255) fe:f1:00:00:01:01 0.510s (253) fe:f1:00:00:01:01 [ eth1]: fe:f1:00:00:01:01 (253) fe:f0:00:00:05:01 0.660s (222) fe:f1:00:00:03:01 [ eth1]: fe:f0:00:00:03:01 (198) fe:f1:00:00:03:01 (222) fe:f0:00:00:03:01 0.560s (252) fe:f1:00:00:03:01 [ eth1]: fe:f1:00:00:03:01 (252) fe:f0:00:00:03:01 (240) fe:f0:00:00:04:01 0.250s (240) fe:f1:00:00:03:01 [ eth1]: fe:f1:00:00:03:01 (240) fe:f0:00:00:03:01 (211) fe:f0:00:00:01:01 0.850s (255) fe:f1:00:00:01:01 [ eth1]: fe:f1:00:00:01:01 (255) fe:f0:00:00:01:01 (238) $ batctl originators -i eth1 [B.A.T.M.A.N. adv master-b82b9b2, IF/MAC: eth1/fe:f1:00:00:02:01 (bat0 BATMAN_IV)] Originator last-seen (#/255) Nexthop [outgoingIF]: Potential nexthops ... fe:f1:00:00:03:01 0.880s (240) fe:f1:00:00:03:01 [ eth1]: fe:f1:00:00:03:01 (240) fe:f1:00:00:01:01 0.250s (239) fe:f1:00:00:01:01 [ eth1]: fe:f1:00:00:01:01 (239) fe:f0:00:00:05:01 0.340s (211) fe:f1:00:00:03:01 [ eth1]: fe:f0:00:00:03:01 (210) fe:f1:00:00:03:01 (211) fe:f0:00:00:03:01 0.260s (253) fe:f0:00:00:03:01 [ eth0]: fe:f1:00:00:03:01 (240) fe:f0:00:00:03:01 (253) fe:f0:00:00:04:01 0.010s (225) fe:f0:00:00:03:01 [ eth0]: fe:f1:00:00:03:01 (224) fe:f0:00:00:03:01 (225) fe:f0:00:00:01:01 0.510s (255) fe:f0:00:00:01:01 [ eth0]: fe:f1:00:00:01:01 (240) fe:f0:00:00:01:01 (255) batctl translocal ----------------- display the local translation table Usage:: batctl translocal|tl Example:: $ batctl translocal Locally retrieved addresses (from bat0) announced via TT (TTVN: 1): * fe:fe:00:00:01:01 [RPNXW] In particular, RPNXW are flags which have the following meanings: R/Roaming: this client moved to another node but it is still kept for consistency reasons until the next OGM is sent. P/noPurge: this client represents the local mesh interface and will never be deleted. N/New: this client has recently been added but is not advertised in the mesh until the next OGM is sent (for consistency reasons). X/delete: this client has to be removed for some reason, but it is still kept for consistency reasons until the next OGM is sent. W/Wireless: this client is connected to the node through a wireless device. If any of the flags is not enabled, a '.' will substitute its symbol. batctl transglobal ------------------ display the global translation table Usage:: batctl transglobal|tg Example:: Globally announced TT entries received via the mesh bat0 Client (TTVN) Originator (Curr TTVN) Flags * fe:fe:00:00:01:01 ( 12) via fe:fe:00:00:01:02 ( 50) [RXW] where TTVN: is the translation-table-version-number which introduced this client Curr TTVN: is the translation-table-version-number currently advertised by the originator serving this client (different clients advertised by the same originator have the same Curr TTVN) Flags that mean: R/Roaming: this client moved to another node but it is still kept for consistency reasons until the next OGM is sent. X/delete: this client has to be removed for some reason, but it is still kept for consistency reasons until the next OGM is sent. W/Wireless: this client is connected to the node through a wireless device. If any of the flags is not enabled, a '.' will substitute its symbol. Settings ======== batctl aggregation ------------------ display or modify the packet aggregation setting Usage:: batctl aggregation|ag [0|1] ap_isolation ------------ display or modify the client isolation setting Usage:: batctl ap_isolation|ap [0|1] bonding ------- display or modify the bonding setting Usage:: batctl bonding|b [0|1] bridge_loop_avoidance --------------------- display or modify the bridge_loop_avoidance setting Usage:: batctl bridge_loop_avoidance|bl [0|1] distributed_arp_table --------------------- display or modify the distributed_arp_table setting Usage:: batctl distributed_arp_table|dat [0|1] batctl elp interval ------------------- display or modify the elp interval in ms for hard interface Usage:: batctl hardif $hardif elp_interval|et [interval] Example:: $ batctl hardif eth0 elp_interval 200 $ batctl hardif eth0 elp_interval 200 fragmentation ------------- display or modify the fragmentation setting Usage:: batctl fragmentation|f [0|1] gw_mode ------- display or modify the gw_mode setting Usage:: batctl gw_mode|gw [0|1] batctl hop_penalty ------------------ display or modify the hop_penalty (0-255) Usage:: batctl hop_penalty|hp [penalty] Example:: $ batctl hop_penalty 30 $ batctl hardif eth0 hop_penalty 0 $ batctl hardif eth0 hop_penalty 50 $ batctl hardif eth0 hop_penalty 50 batctl isolation_mark --------------------- display or modify the isolation mark. This value is used by Extended Isolation feature. Usage:: batctl isolation_mark|mark $value[/0x$mask] * Example 1: ``batctl mark 0x00000001/0xffffffff`` * Example 2: ``batctl mark 0x00040000/0xffff0000`` * Example 3: ``batctl mark 16`` * Example 4: ``batctl mark 0x0f`` batctl loglevel --------------- display or modify the log level Usage:: batctl loglevel|ll [level] Example:: $ batctl loglevel [x] all debug output disabled (none) [ ] messages related to routing / flooding / broadcasting (batman) [ ] messages related to route added / changed / deleted (routes) [ ] messages related to translation table operations (tt) [ ] messages related to bridge loop avoidance (bla) [ ] messages related to arp snooping and distributed arp table (dat) [ ] messages related to network coding (nc) [ ] messages related to multicast (mcast) [ ] messages related to throughput meter (tp) batctl multicast_fanout ----------------------- display or modify the multicast fanout setting Usage:: batctl multicast_fanout|mo [fanout] batctl multicast_forceflood --------------------------- display or modify the multicast forceflood setting Usage:: batctl multicast_forceflood|mff [0|1] batctl network_coding --------------------- display or modify the network coding setting Usage:: batctl network_coding|nc [0|1] Note that network coding requires a working promiscuous mode on all interfaces. batctl orig_interval -------------------- display or modify the originator interval in ms Usage:: batctl orig_interval|it [interval] Example:: $ batctl interval 1000 batctl throughput override -------------------------- display or modify the throughput override in kbit/s for hard interface Usage:: batctl hardif $hardif throughput_override|to [kbit] Example:: $ batctl hardif eth0 throughput_override 15000 $ batctl hardif eth0 throughput_override 15mbit $ batctl hardif eth0 throughput_override 15.0 MBit JSON netlink query helper ========================= batctl bla_backbone_json ------------------------ Query batman-adv for the entries in the known backbones table of bridge loop avoidance. Usage:: batctl meshif bla_backbone_json|bbj Example:: $ batctl meshif bat0 bla_backbone_json | json_pp [ { "bla_backbone": "02:ba:de:af:fe:01", "bla_crc": 0, "bla_own": true, "bla_vid": -1, "last_seen_msecs": 920 }, { "bla_backbone": "02:ba:de:af:fe:01", "bla_crc": 33755, "bla_own": true, "bla_vid": -1, "last_seen_msecs": 44 } ] batctl bla_claim_json --------------------- Query batman-adv for the entries in the known claims table of bridge loop avoidance. Only claims from the current node will have have the key-value ``"bla_own": true``. Usage:: batctl meshif bla_claim_json|clj Example:: $ batctl meshif bat0 bla_claim_json | json_pp [ { "bla_address": "a2:30:36:05:e6:32", "bla_backbone": "02:ba:de:af:fe:01", "bla_crc": 60445, "bla_own": true, "bla_vid": -1 }, { "bla_address": "24:18:1d:1c:d2:13", "bla_backbone": "02:ba:de:af:fe:01", "bla_crc": 60445, "bla_own": true, "bla_vid": -1 }, { "bla_address": "68:72:51:68:67:7a", "bla_backbone": "02:ba:de:af:fe:01", "bla_crc": 60445, "bla_own": true, "bla_vid": -1 }, [...] ] batctl dat_cache_json --------------------- Query batman-adv for the entries in cache of the distributed arp table. Usage:: batctl meshif dat_cache_json|dcj Example:: $ batctl meshif bat0 dat_cache_json | json_pp [ { "dat_cache_hwaddress": "10:8e:e0:62:dc:e8", "dat_cache_ip4address": "10.204.32.109", "dat_cache_vid": -1, "last_seen_msecs": 165752 }, { "dat_cache_hwaddress": "02:ba:7a:df:06:01", "dat_cache_ip4address": "10.204.32.7", "dat_cache_vid": -1, "last_seen_msecs": 364 }, [...] ] batctl gateways_json -------------------- Query batman-adv for the entries in the gateways list. Only selected gateways (for the gateway mode "client) will have have the key-value ``"best": true``. Usage:: batctl meshif gateways_json|gwj Example:: $ batctl meshif bat0 gateways_json | json_pp [ { "bandwidth_down": 100, "bandwidth_up": 20, "hard_ifindex": 3, "hard_ifname": "enp0s1", "orig_address": "02:62:e7:ab:01:01", "router": "ae:1b:bf:52:25:58", "tq": 180 }, { "bandwidth_down": 100, "bandwidth_up": 20, "hard_ifindex": 3, "hard_ifname": "enp0s1", "orig_address": "02:62:e7:ab:05:01", "router": "ae:1b:bf:52:25:58", "tq": 180 }, { "bandwidth_down": 100, "bandwidth_up": 20, "best": true, "hard_ifindex": 3, "hard_ifname": "enp0s1", "orig_address": "02:62:e7:ab:06:01", "router": "ae:1b:bf:52:25:58", "tq": 236 }, [...] ] batctl hardif_json ------------------ Read the interface state for an interface which is part of a batman-adv interface. Usage:: batctl hardif hardif_json|hj Example:: $ batctl hardif enp0s1 hardif_json | json_pp { "active": true, "elp_interval": 500, "hard_address": "02:ba:de:af:fe:01", "hard_ifindex": 3, "hard_ifname": "enp0s1", "hop_penalty": 0, "mesh_ifindex": 9, "mesh_ifname": "bat0", "throughput_override": 0 } batctl hardifs_json -------------------- Query batman-adv for entries in the list of interfaces added to a batadv interface. Usage:: batctl meshif hardifs_json|hj Example:: $ batctl meshif bat0 hardifs_json | json_pp [ { "active": true, "elp_interval": 500, "hard_address": "4a:97:a4:b8:fc:17", "hard_ifindex": 2, "hard_ifname": "dummy0", "hop_penalty": 0, "mesh_ifindex": 9, "mesh_ifname": "bat0", "throughput_override": 0 }, { "active": true, "elp_interval": 500, "hard_address": "02:ba:de:af:fe:01", "hard_ifindex": 3, "hard_ifname": "enp0s1", "hop_penalty": 0, "mesh_ifindex": 9, "mesh_ifname": "bat0", "throughput_override": 0 } ] batctl mcast_flags_json ----------------------- Query batman-adv for entries multicast optimization table. Usage:: batctl meshif mcast_flags_json|mfj Example:: $ batctl meshif bat0 mcast_flags_json | json_pp [ { "mcast_flags" : { "all_unsnoopables" : true, "have_mc_ptype_capa" : true, "raw" : 57, "want_all_ipv4" : false, "want_all_ipv6" : false, "want_no_rtr_ipv4" : true, "want_no_rtr_ipv6" : true }, "orig_address" : "02:04:64:a4:39:c1" }, { "mcast_flags" : { "all_unsnoopables" : false, "have_mc_ptype_capa" : true, "raw" : 40, "want_all_ipv4" : false, "want_all_ipv6" : false, "want_no_rtr_ipv4" : true, "want_no_rtr_ipv6" : false }, "orig_address" : "02:04:64:a4:39:c2" }, { "mcast_flags" : { "all_unsnoopables" : false, "have_mc_ptype_capa" : false, "raw" : 24, "want_all_ipv4" : false, "want_all_ipv6" : false, "want_no_rtr_ipv4" : true, "want_no_rtr_ipv6" : true }, "orig_address" : "02:04:64:a4:39:c3" }, [...] ] batctl mesh_json ---------------- Get the current configuration of the batman-adv mesh interface and its global state. The ``hard_ifindex``/``hard_ifname`` only refers to the primary interface. More interfaces might be attached to theis interface. They can for example be queried using:: ip -json link show master bat0 It also doesn't show all batman-adv interfaces on the system. Such information must be queried using:: ip -json link show type batadv Usage:: batctl meshif mesh_json|mj Example:: $ batctl meshif bat0 mesh_json | json_pp { "aggregated_ogms_enabled": true, "algo_name": "BATMAN_IV", "ap_isolation_enabled": false, "bla_crc": 44249, "bonding_enabled": false, "bridge_loop_avoidance_enabled": true, "distributed_arp_table_enabled": true, "fragmentation_enabled": true, "gw_bandwidth_down": 100, "gw_bandwidth_up": 20, "gw_mode": "client", "gw_sel_class": 20, "hard_address": "02:ba:de:af:fe:01", "hard_ifindex": 3, "hard_ifname": "enp0s1", "hop_penalty": 30, "isolation_mark": 0, "isolation_mask": 0, "mcast_flags": { "all_unsnoopables": false, "raw": 24, "want_all_ipv4": false, "want_all_ipv6": false, "want_no_rtr_ipv4": true, "want_no_rtr_ipv6": true }, "mcast_flags_priv": { "bridged": false, "querier_ipv4_exists": false, "querier_ipv4_shadowing": false, "querier_ipv6_exists": false, "querier_ipv6_shadowing": false, "raw": 0 }, "mesh_address": "3e:dc:94:68:80:e8", "mesh_ifindex": 9, "mesh_ifname": "bat0", "multicast_fanout": 16, "multicast_forceflood_enabled": false, "orig_interval": 5000, "tt_ttvn": 2, "version": "2021.0-15-gc84e5217" } batctl neighbors_json --------------------- Query batman-adv for the entries in the (direct) neighbors table. Usage:: batctl meshif neighbors_json|nj Example:: $ batctl meshif bat0 neighbors_json [ { "hard_ifindex": 3, "hard_ifname": "enp0s1", "last_seen_msecs": 708, "neigh_address": "16:7b:3c:c2:bf:b8" }, { "hard_ifindex": 3, "hard_ifname": "enp0s1", "last_seen_msecs": 1872, "neigh_address": "ae:1b:bf:52:25:58" } ] batctl originators_json ----------------------- Query batman-adv for the entries in the originators table. The table doesn't only contain the list of best next hops but also all other known entries. Only next best hops have the key-value ``"best": true``. Usage:: batctl meshif originators_json|oj Example:: $ batctl meshif bat0 originators_json | json_pp [ { "hard_ifindex": 3, "hard_ifname": "enp0s1", "last_seen_msecs": 4380, "neigh_address": "ae:1b:bf:52:25:58", "orig_address": "16:7b:3c:c2:bf:b8", "tq": 236 }, { "best": true, "hard_ifindex": 3, "hard_ifname": "enp0s1", "last_seen_msecs": 4380, "neigh_address": "16:7b:3c:c2:bf:b8", "orig_address": "16:7b:3c:c2:bf:b8", "tq": 251 }, [...] { "best": true, "hard_ifindex": 3, "hard_ifname": "enp0s1", "last_seen_msecs": 4728, "neigh_address": "ae:1b:bf:52:25:58", "orig_address": "12:6d:7d:6f:f9:03", "tq": 77 } ] batctl transtable_global_json ----------------------------- Query batman-adv for the entries in the global (mac-to-originator) translation table. Only next best hops have the key-value ``"best": true``. Usage:: batctl meshif transtable_global_json|tgj Example:: $ batctl meshif bat0 transtable_global_json [ { "best": true, "orig_address": "ea:88:36:b0:fa:4b", "tt_address": "1e:df:a8:43:c4:d5", "tt_crc32": 3191293109, "tt_flags": { "del": false, "isolated": false, "new": false, "nopurge": false, "pending": false, "raw": 2048, "roam": false, "temp": true, "wifi": false }, "tt_last_ttvn": 255, "tt_ttvn": 255, "tt_vid": -1 }, { "best": true, "orig_address": "6a:d0:7f:eb:86:83", "tt_address": "01:00:5e:7f:ff:fa", "tt_crc32": 2358926211, "tt_flags": { "del": false, "isolated": false, "new": false, "nopurge": false, "pending": false, "raw": 0, "roam": false, "temp": false, "wifi": false }, "tt_last_ttvn": 246, "tt_ttvn": 245, "tt_vid": -1 }, { "orig_address": "0e:68:8c:7c:0f:1b", "tt_address": "01:00:5e:7f:ff:fa", "tt_crc32": 1334456817, "tt_flags": { "del": false, "isolated": false, "new": false, "nopurge": false, "pending": false, "raw": 0, "roam": false, "temp": false, "wifi": false }, "tt_last_ttvn": 21, "tt_ttvn": 20, "tt_vid": -1 }, [...] batctl transtable_local_json ---------------------------- Query batman-adv for the entries in the (mac-to-originator) translation table for locally detected MAC addresses. Usage:: batctl meshif transtable_local_json|tgj Example:: $ batctl meshif bat0 transtable_local_json [ { "tt_address": "3e:dc:94:68:80:e8", "tt_crc32": 3361904636, "tt_flags": { "del": false, "isolated": false, "new": false, "nopurge": true, "pending": false, "raw": 256, "roam": false, "temp": false, "wifi": false }, "tt_vid": -1 }, { "tt_address": "3e:dc:94:68:80:e8", "tt_crc32": 1436598566, "tt_flags": { "del": false, "isolated": false, "new": false, "nopurge": true, "pending": false, "raw": 256, "roam": false, "temp": false, "wifi": false }, "tt_vid": 0 }, { "tt_address": "01:00:5e:00:00:01", "tt_crc32": 3361904636, "tt_flags": { "del": false, "isolated": false, "new": false, "nopurge": true, "pending": false, "raw": 256, "roam": false, "temp": false, "wifi": false }, "tt_vid": -1 }, { "tt_address": "33:33:ff:68:80:e8", "tt_crc32": 3361904636, "tt_flags": { "del": false, "isolated": false, "new": false, "nopurge": true, "pending": false, "raw": 256, "roam": false, "temp": false, "wifi": false }, "tt_vid": -1 }, { "tt_address": "33:33:00:00:00:01", "tt_crc32": 3361904636, "tt_flags": { "del": false, "isolated": false, "new": false, "nopurge": true, "pending": false, "raw": 256, "roam": false, "temp": false, "wifi": false }, "tt_vid": -1 } ] batctl vlan_json ---------------- Read the vlan state for an vlan on top a batman-adv interface. Usage:: batctl meshif vid vlan_json|vj batctl vlan vlan_json|vj Example:: $ batctl meshif bat0 vid 0 vlan_json { "ap_isolation_enabled": false, "mesh_ifindex": 9, "mesh_ifname": "bat0", "vlanid": 0 } $ batctl vlan bat0.1 vlan_json { "ap_isolation_enabled": false, "mesh_ifindex": 9, "mesh_ifname": "bat0", "vlanid": 1 } Advanced Analytics ================== batctl bisect_iv ---------------- Analyzes the B.A.T.M.A.N. IV logfiles to build a small internal database of all sent sequence numbers and routing table changes. This database can be used to search for routing loops (default action), to trace OGMs of a host (use "-t" to specify the mac address or bat-host name) throughout the network or to display routing tables of the nodes (use "-r" to specify the mac address or bat-host name). You can name a specific sequence number or a range using the "-s" option to limit the output's range. Furthermore you can filter the output by specifying an originator (use "-o" to specify the mac address or bat-host name) to only see data connected to this originator. If "-n" was given batctl will not replace the mac addresses with bat-host names in the output. Usage:: batctl bisect_iv [parameters] .. parameters: -h print this help -l run a loop detection of given mac address or bat-host (default) -n don't convert addresses to bat-host names -r print routing tables of given mac address or bat-host -s seqno range to limit the output -t trace seqnos of given mac address or bat-host Examples:: $ batctl bisect_iv log/* -l uml3 Analyzing routing tables of originator: uml3 [all sequence numbers] Checking host: uml3 Path towards uml7 (seqno 9 via neigh uml5): -> uml5 -> uml6 Path towards uml7 (seqno 10 via neigh uml4): -> uml4 -> uml5 -> uml6 Path towards uml6 (seqno 4 via neigh uml4): -> uml4 Path towards uml8 (seqno 12 via neigh uml4): -> uml4 -> uml5 -> uml6 -> uml7 Path towards uml8 (seqno 203 via neigh uml4): -> uml4 -> uml6 -> uml7 Path towards uml8 (seqno 391 via neigh uml2): -> uml2 -> uml3 -> uml2 aborted due to loop! Path towards uml8 (seqno 396 via neigh uml4): -> uml4 -> uml6 -> uml7 Path towards uml9 (seqno 10 via neigh uml5): -> uml5 -> uml6 -> uml7 -> uml9. Path towards uml9 (seqno 10 via neigh uml4): -> uml4 -> uml5 -> uml6 -> uml7 -> uml9. Path towards uml9 (seqno 11 via neigh uml4): -> uml4 -> uml6 -> uml7 -> uml8 -> uml9. Path towards uml9 (seqno 12 via neigh uml4): -> uml4 -> uml5 -> uml6 -> uml7 -> uml8 -> uml9. Path towards uml9 (seqno 21 via neigh uml5): -> uml5 -> uml6 -> uml7 -> uml8 -> uml9. Path towards uml9 (seqno 22 via neigh uml4): -> uml4 -> uml5 -> uml6 -> uml7 -> uml8 -> uml9. $ ./batctl bisect_iv -t uml3 log/* Sequence number flow of originator: uml3 [all sequence numbers] [...] +=> uml3 (seqno 19) |- uml2 [tq: 255, ttl: 50, neigh: uml3, prev_sender: uml3] | |- uml3 [tq: 154, ttl: 49, neigh: uml2, prev_sender: uml3] | \- uml1 [tq: 154, ttl: 49, neigh: uml2, prev_sender: uml3] | |- uml3 [tq: 51, ttl: 48, neigh: uml1, prev_sender: uml2] | \- uml2 [tq: 51, ttl: 48, neigh: uml1, prev_sender: uml2] |- uml5 [tq: 255, ttl: 50, neigh: uml3, prev_sender: uml3] | |- uml6 [tq: 33, ttl: 48, neigh: uml5, prev_sender: uml3] | | |- uml5 [tq: 11, ttl: 47, neigh: uml6, prev_sender: uml5] | | |- uml7 [tq: 11, ttl: 47, neigh: uml6, prev_sender: uml5] | | | |- uml8 [tq: 3, ttl: 46, neigh: uml7, prev_sender: uml6] | | | | |- uml6 [tq: 0, ttl: 45, neigh: uml8, prev_sender: uml7] | | | | |- uml9 [tq: 0, ttl: 45, neigh: uml8, prev_sender: uml7] | | | | \- uml7 [tq: 0, ttl: 45, neigh: uml8, prev_sender: uml7] | | | |- uml6 [tq: 3, ttl: 46, neigh: uml7, prev_sender: uml6] | | | |- uml9 [tq: 3, ttl: 46, neigh: uml7, prev_sender: uml6] | | | \- uml5 [tq: 3, ttl: 46, neigh: uml7, prev_sender: uml6] | | \- uml4 [tq: 11, ttl: 47, neigh: uml6, prev_sender: uml5] | |- uml7 [tq: 33, ttl: 48, neigh: uml5, prev_sender: uml3] | \- uml4 [tq: 33, ttl: 48, neigh: uml5, prev_sender: uml3] \- uml4 [tq: 255, ttl: 50, neigh: uml3, prev_sender: uml3] |- uml3 [tq: 106, ttl: 49, neigh: uml4, prev_sender: uml3] |- uml6 [tq: 106, ttl: 49, neigh: uml4, prev_sender: uml3] |- uml2 [tq: 106, ttl: 49, neigh: uml4, prev_sender: uml3] \- uml5 [tq: 106, ttl: 49, neigh: uml4, prev_sender: uml3] +=> uml3 (seqno 20) |- uml2 [tq: 255, ttl: 50, neigh: uml3, prev_sender: uml3] | |- uml3 [tq: 160, ttl: 49, neigh: uml2, prev_sender: uml3] | |- uml1 [tq: 160, ttl: 49, neigh: uml2, prev_sender: uml3] | \- uml4 [tq: 160, ttl: 49, neigh: uml2, prev_sender: uml3] |- uml5 [tq: 255, ttl: 50, neigh: uml3, prev_sender: uml3] | |- uml3 [tq: 43, ttl: 48, neigh: uml5, prev_sender: uml3] | |- uml6 [tq: 43, ttl: 48, neigh: uml5, prev_sender: uml3] | | |- uml8 [tq: 16, ttl: 47, neigh: uml6, prev_sender: uml5] | | |- uml5 [tq: 16, ttl: 47, neigh: uml6, prev_sender: uml5] | | |- uml7 [tq: 16, ttl: 47, neigh: uml6, prev_sender: uml5] | | | |- uml8 [tq: 5, ttl: 46, neigh: uml7, prev_sender: uml6] | | | | |- uml6 [tq: 0, ttl: 45, neigh: uml8, prev_sender: uml7] | | | | |- uml9 [tq: 0, ttl: 45, neigh: uml8, prev_sender: uml7] | | | | \- uml7 [tq: 0, ttl: 45, neigh: uml8, prev_sender: uml7] | | | \- uml6 [tq: 5, ttl: 46, neigh: uml7, prev_sender: uml6] | | \- uml4 [tq: 16, ttl: 47, neigh: uml6, prev_sender: uml5] | \- uml4 [tq: 43, ttl: 48, neigh: uml5, prev_sender: uml3] |- uml1 [tq: 255, ttl: 50, neigh: uml3, prev_sender: uml3] | \- uml2 [tq: 49, ttl: 48, neigh: uml1, prev_sender: uml3] \- uml4 [tq: 255, ttl: 50, neigh: uml3, prev_sender: uml3] |- uml3 [tq: 114, ttl: 49, neigh: uml4, prev_sender: uml3] |- uml6 [tq: 114, ttl: 49, neigh: uml4, prev_sender: uml3] |- uml2 [tq: 114, ttl: 49, neigh: uml4, prev_sender: uml3] \- uml5 [tq: 114, ttl: 49, neigh: uml4, prev_sender: uml3] [...] Appendix ======== batctl and network name spaces ------------------------------ The batman-adv kernel module is netns aware. Mesh instances can be created in name spaces, and interfaces in that name space added to the mesh. The mesh interface cannot be moved between name spaces, as is typical for virtual interfaces. The following example creates two network namespaces, and uses veth pairs to connect them together into a mesh of three nodes:: EMU1="ip netns exec emu1" EMU2="ip netns exec emu2" ip netns add emu1 ip netns add emu2 ip link add emu1-veth1 type veth peer name emu2-veth1 ip link set emu1-veth1 netns emu1 ip link set emu2-veth1 netns emu2 $EMU1 ip link set emu1-veth1 name veth1 $EMU2 ip link set emu2-veth1 name veth1 $EMU1 ip link set veth1 up $EMU2 ip link set veth1 up ip link add emu1-veth2 type veth peer name veth2 ip link set emu1-veth2 netns emu1 $EMU1 ip link set emu1-veth2 name veth2 $EMU1 ip link set veth2 up ip link set veth2 up $EMU1 batctl if add veth1 $EMU1 batctl if add veth2 $EMU1 ip link set bat0 up $EMU2 batctl if add veth1 $EMU2 ip link set bat0 up batctl if add veth2 ip link set bat0 up alfred and batadv-vis can also be used with name spaces. In this example, only netns has been used, so there are no filesystem name spaces. Hence the unix domain socket used by alfred needs to be given a unique name per instance:: ($EMU1 alfred -m -i bat0 -u /var/run/emu1-alfred.soc) & ($EMU2 alfred -m -i bat0 -u /var/run/emu2-alfred.soc) & alfred -m -i bat0 & ($EMU1 batadv-vis -s -u /var/run/emu1-alfred.soc) & ($EMU2 batadv-vis -s -u /var/run/emu2-alfred.soc) & batadv-vis -s & batctl-2025.1/aggregation.c000066400000000000000000000030331500012142700154760ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include "main.h" #include #include #include #include "batman_adv.h" #include "netlink.h" #include "sys.h" static struct simple_boolean_data aggregated_ogms; static int print_aggregated_ogms(struct nl_msg *msg, void *arg) { return sys_simple_print_boolean(msg, arg, BATADV_ATTR_AGGREGATED_OGMS_ENABLED); } static int get_aggregated_ogms(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_GET_MESH, NULL, print_aggregated_ogms); } static int set_attrs_aggregated_ogms(struct nl_msg *msg, void *arg) { struct state *state = arg; struct settings_data *settings = state->cmd->arg; struct simple_boolean_data *data = settings->data; nla_put_u8(msg, BATADV_ATTR_AGGREGATED_OGMS_ENABLED, data->val); return 0; } static int set_aggregated_ogms(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_SET_MESH, set_attrs_aggregated_ogms, NULL); } static struct settings_data batctl_settings_aggregation = { .data = &aggregated_ogms, .parse = parse_simple_boolean, .netlink_get = get_aggregated_ogms, .netlink_set = set_aggregated_ogms, }; COMMAND_NAMED(SUBCOMMAND_MIF, aggregation, "ag", handle_sys_setting, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_settings_aggregation, "[0|1] \tdisplay or modify aggregation setting"); batctl-2025.1/ap_isolation.c000066400000000000000000000043631500012142700156770ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Antonio Quartulli * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include "main.h" #include #include #include #include "batman_adv.h" #include "netlink.h" #include "sys.h" static struct simple_boolean_data ap_isolation; static int print_ap_isolation(struct nl_msg *msg, void *arg) { return sys_simple_print_boolean(msg, arg, BATADV_ATTR_AP_ISOLATION_ENABLED); } static int get_attrs_ap_isolation(struct nl_msg *msg, void *arg) { struct state *state = arg; if (state->selector == SP_VLAN) nla_put_u16(msg, BATADV_ATTR_VLANID, state->vid); return 0; } static int get_ap_isolation(struct state *state) { enum batadv_nl_commands nl_cmd = BATADV_CMD_GET_MESH; if (state->selector == SP_VLAN) nl_cmd = BATADV_CMD_GET_VLAN; return sys_simple_nlquery(state, nl_cmd, get_attrs_ap_isolation, print_ap_isolation); } static int set_attrs_ap_isolation(struct nl_msg *msg, void *arg) { struct state *state = arg; struct settings_data *settings = state->cmd->arg; struct simple_boolean_data *data = settings->data; nla_put_u8(msg, BATADV_ATTR_AP_ISOLATION_ENABLED, data->val); if (state->selector == SP_VLAN) nla_put_u16(msg, BATADV_ATTR_VLANID, state->vid); return 0; } static int set_ap_isolation(struct state *state) { enum batadv_nl_commands nl_cmd = BATADV_CMD_SET_MESH; if (state->selector == SP_VLAN) nl_cmd = BATADV_CMD_SET_VLAN; return sys_simple_nlquery(state, nl_cmd, set_attrs_ap_isolation, NULL); } static struct settings_data batctl_settings_ap_isolation = { .data = &ap_isolation, .parse = parse_simple_boolean, .netlink_get = get_ap_isolation, .netlink_set = set_ap_isolation, }; COMMAND_NAMED(SUBCOMMAND_MIF, ap_isolation, "ap", handle_sys_setting, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_settings_ap_isolation, "[0|1] \tdisplay or modify ap_isolation setting"); COMMAND_NAMED(SUBCOMMAND_VID, ap_isolation, "ap", handle_sys_setting, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_settings_ap_isolation, "[0|1] \tdisplay or modify ap_isolation setting for vlan device or id"); batctl-2025.1/backbonetable.c000066400000000000000000000060071500012142700157670ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Simon Wunderlich * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include #include "batadv_packet_compat.h" #include "batman_adv.h" #include "bat-hosts.h" #include "debug.h" #include "functions.h" #include "main.h" #include "netlink.h" static const int bla_backbone_mandatory[] = { BATADV_ATTR_BLA_VID, BATADV_ATTR_BLA_BACKBONE, BATADV_ATTR_BLA_CRC, BATADV_ATTR_LAST_SEEN_MSECS, }; static int bla_backbone_callback(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct print_opts *opts = arg; struct bat_host *bat_host; struct genlmsghdr *ghdr; uint16_t backbone_crc; int last_seen_msecs; int last_seen_secs; uint8_t *backbone; uint16_t vid; if (!genlmsg_valid_hdr(nlh, 0)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } ghdr = nlmsg_data(nlh); if (ghdr->cmd != BATADV_CMD_GET_BLA_BACKBONE) return NL_OK; if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } if (missing_mandatory_attrs(attrs, bla_backbone_mandatory, ARRAY_SIZE(bla_backbone_mandatory))) { fputs("Missing attributes from kernel\n", stderr); exit(1); } /* don't show own backbones */ if (attrs[BATADV_ATTR_BLA_OWN]) return NL_OK; vid = nla_get_u16(attrs[BATADV_ATTR_BLA_VID]); backbone = nla_data(attrs[BATADV_ATTR_BLA_BACKBONE]); backbone_crc = nla_get_u16(attrs[BATADV_ATTR_BLA_CRC]); last_seen_msecs = nla_get_u32(attrs[BATADV_ATTR_LAST_SEEN_MSECS]); last_seen_secs = last_seen_msecs / 1000; last_seen_msecs = last_seen_msecs % 1000; bat_host = bat_hosts_find_by_mac((char *)backbone); if (!(opts->read_opt & USE_BAT_HOSTS) || !bat_host) printf("%02x:%02x:%02x:%02x:%02x:%02x ", backbone[0], backbone[1], backbone[2], backbone[3], backbone[4], backbone[5]); else printf("%17s ", bat_host->name); printf("on %5d %4i.%03is (0x%04x)\n", BATADV_PRINT_VID(vid), last_seen_secs, last_seen_msecs, backbone_crc); return NL_OK; } static int netlink_print_bla_backbone(struct state *state, char *orig_iface, int read_opts, float orig_timeout, float watch_interval) { return netlink_print_common(state, orig_iface, read_opts, orig_timeout, watch_interval, "Originator VID last seen (CRC )\n", BATADV_CMD_GET_BLA_BACKBONE, bla_backbone_callback); } static struct debug_table_data batctl_debug_table_backbonetable = { .netlink_fn = netlink_print_bla_backbone, }; COMMAND_NAMED(DEBUGTABLE, backbonetable, "bbt", handle_debug_table, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_debug_table_backbonetable, ""); batctl-2025.1/bat-hosts.c000066400000000000000000000135011500012142700151140ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Andreas Langer , Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include #include #include "bat-hosts.h" #include "hash.h" #include "functions.h" static struct hashtable_t *host_hash; const char *bat_hosts_path[3] = {"/etc/bat-hosts", "~/bat-hosts", "bat-hosts"}; static int compare_mac(void *data1, void *data2) { return (memcmp(data1, data2, sizeof(struct ether_addr)) == 0 ? 1 : 0); } static int choose_mac(void *data, int32_t size) { uint32_t m_size = sizeof(struct ether_addr); unsigned char *key = data; uint32_t hash = 0; size_t i; for (i = 0; i < m_size; i++) { hash += key[i]; hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return (hash % size); } static void parse_hosts_file(struct hashtable_t **hash, const char path[], int read_opt) { struct hashtable_t *swaphash; char name[HOST_NAME_MAX_LEN]; struct ether_addr *mac_addr; struct bat_host *bat_host; char *line_ptr = NULL; char mac_str[18]; size_t len = 0; FILE *fd; name[0] = '\0'; mac_str[0] = '\0'; fd = fopen(path, "r"); if (!fd) return; while (getline(&line_ptr, &len, fd) != -1) { /* ignore empty lines and comments */ if ((line_ptr[0] == '\n') || (line_ptr[0] == '#')) continue; if (sscanf(line_ptr, "%17[^ \t]%49s\n", mac_str, name) != 2) { if (read_opt & USE_BAT_HOSTS) fprintf(stderr, "Warning - unrecognized bat-host definition: %s", line_ptr); continue; } mac_addr = ether_aton(mac_str); if (!mac_addr) { if (read_opt & USE_BAT_HOSTS) fprintf(stderr, "Warning - invalid mac address in '%s' detected: %s\n", path, mac_str); continue; } bat_host = bat_hosts_find_by_mac((char *)mac_addr); /* mac entry already exists - we found a new name for it */ if (bat_host) { /* if the mac addresses and the names are the same we * can safely ignore the entry */ if (strcmp(bat_host->name, name) == 0) continue; if (read_opt & USE_BAT_HOSTS) fprintf(stderr, "Warning - mac already known (changing name from '%s' to '%s'): %s\n", bat_host->name, name, mac_str); strncpy(bat_host->name, name, HOST_NAME_MAX_LEN); bat_host->name[HOST_NAME_MAX_LEN - 1] = '\0'; continue; } bat_host = bat_hosts_find_by_name(name); /* name entry already exists - we found a new mac address for it */ if (bat_host) { if (read_opt & USE_BAT_HOSTS) fprintf(stderr, "Warning - name already known (changing mac from '%s' to '%s'): %s\n", ether_ntoa(&bat_host->mac_addr), mac_str, name); hash_remove(*hash, bat_host); free(bat_host); } bat_host = malloc(sizeof(struct bat_host)); if (!bat_host) { if (read_opt & USE_BAT_HOSTS) perror("Error - could not allocate memory"); goto out; } memcpy(&bat_host->mac_addr, mac_addr, sizeof(struct ether_addr)); strncpy(bat_host->name, name, HOST_NAME_MAX_LEN); bat_host->name[HOST_NAME_MAX_LEN - 1] = '\0'; hash_add(*hash, bat_host); if ((*hash)->elements * 4 > (*hash)->size) { swaphash = hash_resize((*hash), (*hash)->size * 2); if (swaphash) *hash = swaphash; else if (read_opt & USE_BAT_HOSTS) fprintf(stderr, "Warning - couldn't resize bat hosts hash table\n"); } } out: if (fd) fclose(fd); if (line_ptr) free(line_ptr); } void bat_hosts_init(int read_opt) { size_t locations = sizeof(bat_hosts_path) / sizeof(char *); char confdir[CONF_DIR_LEN]; unsigned int parse; char *normalized; unsigned int i; unsigned int j; char *homedir; /*** * realpath could allocate the memory for us but some embedded libc * implementations seem to expect a buffer as second argument */ normalized = malloc(locations * PATH_MAX); if (!normalized) { if (read_opt & USE_BAT_HOSTS) printf("Warning - could not get memory for bat-hosts file parsing\n"); return; } memset(normalized, 0, locations * PATH_MAX); host_hash = hash_new(64, compare_mac, choose_mac); if (!host_hash) { if (read_opt & USE_BAT_HOSTS) printf("Warning - could not create bat hosts hash table\n"); goto out; } homedir = getenv("HOME"); for (i = 0; i < locations; i++) { strcpy(confdir, ""); if (strlen(bat_hosts_path[i]) >= 2 && bat_hosts_path[i][0] == '~' && bat_hosts_path[i][1] == '/') { if (!homedir) continue; snprintf(confdir, CONF_DIR_LEN, "%s%s", homedir, &bat_hosts_path[i][1]); } else { strncpy(confdir, bat_hosts_path[i], CONF_DIR_LEN); confdir[CONF_DIR_LEN - 1] = '\0'; } if (!realpath(confdir, normalized + (i * PATH_MAX))) continue; /* check for duplicates: don't parse the same file twice */ parse = 1; for (j = 0; j < i; j++) { if (strncmp(normalized + (i * PATH_MAX), normalized + (j * PATH_MAX), CONF_DIR_LEN) == 0) { parse = 0; break; } } if (parse) parse_hosts_file(&host_hash, normalized + (i * PATH_MAX), read_opt); } out: free(normalized); } struct bat_host *bat_hosts_find_by_name(char *name) { struct bat_host *bat_host = NULL; struct hash_it_t *hashit = NULL; struct bat_host *tmp_bat_host; if (!host_hash) return NULL; while (NULL != (hashit = hash_iterate(host_hash, hashit))) { tmp_bat_host = (struct bat_host *)hashit->bucket->data; if (strncmp(tmp_bat_host->name, name, HOST_NAME_MAX_LEN - 1) == 0) bat_host = tmp_bat_host; } return bat_host; } struct bat_host *bat_hosts_find_by_mac(char *mac) { if (!host_hash) return NULL; return (struct bat_host *)hash_find(host_hash, mac); } static void bat_host_free(void *data) { free(data); } void bat_hosts_free(void) { if (host_hash) hash_delete(host_hash, bat_host_free); } batctl-2025.1/bat-hosts.h000066400000000000000000000011421500012142700151170ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #ifndef _BATCTL_BAT_HOSTS_H #define _BATCTL_BAT_HOSTS_H #include #define HOST_NAME_MAX_LEN 50 #define CONF_DIR_LEN 256 struct bat_host { struct ether_addr mac_addr; char name[HOST_NAME_MAX_LEN]; } __attribute__((__packed__)); void bat_hosts_init(int read_opt); struct bat_host *bat_hosts_find_by_name(char *name); struct bat_host *bat_hosts_find_by_mac(char *mac); void bat_hosts_free(void); #endif batctl-2025.1/bat-hosts.sample000066400000000000000000000001541500012142700161530ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0 # License-Filename: LICENSES/preferred/GPL-2.0 0:d2:58:ca:91:e8 example batctl-2025.1/batadv_packet.h000066400000000000000000000524521500012142700160150ustar00rootroot00000000000000/* SPDX-License-Identifier: (GPL-2.0 WITH Linux-syscall-note) */ /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner, Simon Wunderlich */ #ifndef _UAPI_LINUX_BATADV_PACKET_H_ #define _UAPI_LINUX_BATADV_PACKET_H_ #include #include #include #include /** * batadv_tp_is_error() - Check throughput meter return code for error * @n: throughput meter return code * * Return: 0 when not error was detected, != 0 otherwise */ #define batadv_tp_is_error(n) ((__u8)(n) > 127 ? 1 : 0) /** * enum batadv_packettype - types for batman-adv encapsulated packets * @BATADV_IV_OGM: originator messages for B.A.T.M.A.N. IV * @BATADV_BCAST: broadcast packets carrying broadcast payload * @BATADV_CODED: network coded packets * @BATADV_ELP: echo location packets for B.A.T.M.A.N. V * @BATADV_OGM2: originator messages for B.A.T.M.A.N. V * @BATADV_MCAST: multicast packet with multiple destination addresses * * @BATADV_UNICAST: unicast packets carrying unicast payload traffic * @BATADV_UNICAST_FRAG: unicast packets carrying a fragment of the original * payload packet * @BATADV_UNICAST_4ADDR: unicast packet including the originator address of * the sender * @BATADV_ICMP: unicast packet like IP ICMP used for ping or traceroute * @BATADV_UNICAST_TVLV: unicast packet carrying TVLV containers */ enum batadv_packettype { /* 0x00 - 0x3f: local packets or special rules for handling */ BATADV_IV_OGM = 0x00, BATADV_BCAST = 0x01, BATADV_CODED = 0x02, BATADV_ELP = 0x03, BATADV_OGM2 = 0x04, BATADV_MCAST = 0x05, /* 0x40 - 0x7f: unicast */ #define BATADV_UNICAST_MIN 0x40 BATADV_UNICAST = 0x40, BATADV_UNICAST_FRAG = 0x41, BATADV_UNICAST_4ADDR = 0x42, BATADV_ICMP = 0x43, BATADV_UNICAST_TVLV = 0x44, #define BATADV_UNICAST_MAX 0x7f /* 0x80 - 0xff: reserved */ }; /** * enum batadv_subtype - packet subtype for unicast4addr * @BATADV_P_DATA: user payload * @BATADV_P_DAT_DHT_GET: DHT request message * @BATADV_P_DAT_DHT_PUT: DHT store message * @BATADV_P_DAT_CACHE_REPLY: ARP reply generated by DAT */ enum batadv_subtype { BATADV_P_DATA = 0x01, BATADV_P_DAT_DHT_GET = 0x02, BATADV_P_DAT_DHT_PUT = 0x03, BATADV_P_DAT_CACHE_REPLY = 0x04, }; /* this file is included by batctl which needs these defines */ #define BATADV_COMPAT_VERSION 15 /** * enum batadv_iv_flags - flags used in B.A.T.M.A.N. IV OGM packets * @BATADV_NOT_BEST_NEXT_HOP: flag is set when the ogm packet is forwarded and * was previously received from someone other than the best neighbor. * @BATADV_PRIMARIES_FIRST_HOP: flag unused. * @BATADV_DIRECTLINK: flag is for the first hop or if rebroadcasted from a * one hop neighbor on the interface where it was originally received. */ enum batadv_iv_flags { BATADV_NOT_BEST_NEXT_HOP = 1UL << 0, BATADV_PRIMARIES_FIRST_HOP = 1UL << 1, BATADV_DIRECTLINK = 1UL << 2, }; /** * enum batadv_icmp_packettype - ICMP message types * @BATADV_ECHO_REPLY: success reply to BATADV_ECHO_REQUEST * @BATADV_DESTINATION_UNREACHABLE: failure when route to destination not found * @BATADV_ECHO_REQUEST: request BATADV_ECHO_REPLY from destination * @BATADV_TTL_EXCEEDED: error after BATADV_ECHO_REQUEST traversed too many hops * @BATADV_PARAMETER_PROBLEM: return code for malformed messages * @BATADV_TP: throughput meter packet */ enum batadv_icmp_packettype { BATADV_ECHO_REPLY = 0, BATADV_DESTINATION_UNREACHABLE = 3, BATADV_ECHO_REQUEST = 8, BATADV_TTL_EXCEEDED = 11, BATADV_PARAMETER_PROBLEM = 12, BATADV_TP = 15, }; /** * enum batadv_mcast_flags - flags for multicast capabilities and settings * @BATADV_MCAST_WANT_ALL_UNSNOOPABLES: we want all packets destined for * 224.0.0.0/24 or ff02::1 * @BATADV_MCAST_WANT_ALL_IPV4: we want all IPv4 multicast packets * (both link-local and routable ones) * @BATADV_MCAST_WANT_ALL_IPV6: we want all IPv6 multicast packets * (both link-local and routable ones) * @BATADV_MCAST_WANT_NO_RTR4: we have no IPv4 multicast router and therefore * only need routable IPv4 multicast packets we signed up for explicitly * @BATADV_MCAST_WANT_NO_RTR6: we have no IPv6 multicast router and therefore * only need routable IPv6 multicast packets we signed up for explicitly * @BATADV_MCAST_HAVE_MC_PTYPE_CAPA: we can parse, receive and forward * batman-adv multicast packets with a multicast tracker TVLV. And all our * hard interfaces have an MTU of at least 1280 bytes. */ enum batadv_mcast_flags { BATADV_MCAST_WANT_ALL_UNSNOOPABLES = 1UL << 0, BATADV_MCAST_WANT_ALL_IPV4 = 1UL << 1, BATADV_MCAST_WANT_ALL_IPV6 = 1UL << 2, BATADV_MCAST_WANT_NO_RTR4 = 1UL << 3, BATADV_MCAST_WANT_NO_RTR6 = 1UL << 4, BATADV_MCAST_HAVE_MC_PTYPE_CAPA = 1UL << 5, }; /* tt data subtypes */ #define BATADV_TT_DATA_TYPE_MASK 0x0F /** * enum batadv_tt_data_flags - flags for tt data tvlv * @BATADV_TT_OGM_DIFF: TT diff propagated through OGM * @BATADV_TT_REQUEST: TT request message * @BATADV_TT_RESPONSE: TT response message * @BATADV_TT_FULL_TABLE: contains full table to replace existing table */ enum batadv_tt_data_flags { BATADV_TT_OGM_DIFF = 1UL << 0, BATADV_TT_REQUEST = 1UL << 1, BATADV_TT_RESPONSE = 1UL << 2, BATADV_TT_FULL_TABLE = 1UL << 4, }; /** * enum batadv_vlan_flags - flags for the four MSB of any vlan ID field * @BATADV_VLAN_HAS_TAG: whether the field contains a valid vlan tag or not */ enum batadv_vlan_flags { BATADV_VLAN_HAS_TAG = 1UL << 15, }; /** * enum batadv_bla_claimframe - claim frame types for the bridge loop avoidance * @BATADV_CLAIM_TYPE_CLAIM: claim of a client mac address * @BATADV_CLAIM_TYPE_UNCLAIM: unclaim of a client mac address * @BATADV_CLAIM_TYPE_ANNOUNCE: announcement of backbone with current crc * @BATADV_CLAIM_TYPE_REQUEST: request of full claim table * @BATADV_CLAIM_TYPE_LOOPDETECT: mesh-traversing loop detect packet */ enum batadv_bla_claimframe { BATADV_CLAIM_TYPE_CLAIM = 0x00, BATADV_CLAIM_TYPE_UNCLAIM = 0x01, BATADV_CLAIM_TYPE_ANNOUNCE = 0x02, BATADV_CLAIM_TYPE_REQUEST = 0x03, BATADV_CLAIM_TYPE_LOOPDETECT = 0x04, }; /** * enum batadv_tvlv_type - tvlv type definitions * @BATADV_TVLV_GW: gateway tvlv * @BATADV_TVLV_DAT: distributed arp table tvlv * @BATADV_TVLV_NC: network coding tvlv * @BATADV_TVLV_TT: translation table tvlv * @BATADV_TVLV_ROAM: roaming advertisement tvlv * @BATADV_TVLV_MCAST: multicast capability tvlv * @BATADV_TVLV_MCAST_TRACKER: multicast tracker tvlv */ enum batadv_tvlv_type { BATADV_TVLV_GW = 0x01, BATADV_TVLV_DAT = 0x02, BATADV_TVLV_NC = 0x03, BATADV_TVLV_TT = 0x04, BATADV_TVLV_ROAM = 0x05, BATADV_TVLV_MCAST = 0x06, BATADV_TVLV_MCAST_TRACKER = 0x07, }; #pragma pack(2) /* the destination hardware field in the ARP frame is used to * transport the claim type and the group id */ struct batadv_bla_claim_dst { __u8 magic[3]; /* FF:43:05 */ __u8 type; /* bla_claimframe */ __be16 group; /* group id */ }; /** * struct batadv_ogm_packet - ogm (routing protocol) packet * @packet_type: batman-adv packet type, part of the general header * @version: batman-adv protocol version, part of the general header * @ttl: time to live for this packet, part of the general header * @flags: contains routing relevant flags - see enum batadv_iv_flags * @seqno: sequence identification * @orig: address of the source node * @prev_sender: address of the previous sender * @reserved: reserved byte for alignment * @tq: transmission quality * @tvlv_len: length of tvlv data following the ogm header */ struct batadv_ogm_packet { __u8 packet_type; __u8 version; __u8 ttl; __u8 flags; __be32 seqno; __u8 orig[ETH_ALEN]; __u8 prev_sender[ETH_ALEN]; __u8 reserved; __u8 tq; __be16 tvlv_len; }; #define BATADV_OGM_HLEN sizeof(struct batadv_ogm_packet) /** * struct batadv_ogm2_packet - ogm2 (routing protocol) packet * @packet_type: batman-adv packet type, part of the general header * @version: batman-adv protocol version, part of the general header * @ttl: time to live for this packet, part of the general header * @flags: reserved for routing relevant flags - currently always 0 * @seqno: sequence number * @orig: originator mac address * @tvlv_len: length of the appended tvlv buffer (in bytes) * @throughput: the currently flooded path throughput */ struct batadv_ogm2_packet { __u8 packet_type; __u8 version; __u8 ttl; __u8 flags; __be32 seqno; __u8 orig[ETH_ALEN]; __be16 tvlv_len; __be32 throughput; }; #define BATADV_OGM2_HLEN sizeof(struct batadv_ogm2_packet) /** * struct batadv_elp_packet - elp (neighbor discovery) packet * @packet_type: batman-adv packet type, part of the general header * @version: batman-adv protocol version, part of the general header * @orig: originator mac address * @seqno: sequence number * @elp_interval: currently used ELP sending interval in ms */ struct batadv_elp_packet { __u8 packet_type; __u8 version; __u8 orig[ETH_ALEN]; __be32 seqno; __be32 elp_interval; }; #define BATADV_ELP_HLEN sizeof(struct batadv_elp_packet) /** * struct batadv_icmp_header - common members among all the ICMP packets * @packet_type: batman-adv packet type, part of the general header * @version: batman-adv protocol version, part of the general header * @ttl: time to live for this packet, part of the general header * @msg_type: ICMP packet type * @dst: address of the destination node * @orig: address of the source node * @uid: local ICMP socket identifier * @align: not used - useful for alignment purposes only * * This structure is used for ICMP packet parsing only and it is never sent * over the wire. The alignment field at the end is there to ensure that * members are padded the same way as they are in real packets. */ struct batadv_icmp_header { __u8 packet_type; __u8 version; __u8 ttl; __u8 msg_type; /* see ICMP message types above */ __u8 dst[ETH_ALEN]; __u8 orig[ETH_ALEN]; __u8 uid; __u8 align[3]; }; /** * struct batadv_icmp_packet - ICMP packet * @packet_type: batman-adv packet type, part of the general header * @version: batman-adv protocol version, part of the general header * @ttl: time to live for this packet, part of the general header * @msg_type: ICMP packet type * @dst: address of the destination node * @orig: address of the source node * @uid: local ICMP socket identifier * @reserved: not used - useful for alignment * @seqno: ICMP sequence number */ struct batadv_icmp_packet { __u8 packet_type; __u8 version; __u8 ttl; __u8 msg_type; /* see ICMP message types above */ __u8 dst[ETH_ALEN]; __u8 orig[ETH_ALEN]; __u8 uid; __u8 reserved; __be16 seqno; }; /** * struct batadv_icmp_tp_packet - ICMP TP Meter packet * @packet_type: batman-adv packet type, part of the general header * @version: batman-adv protocol version, part of the general header * @ttl: time to live for this packet, part of the general header * @msg_type: ICMP packet type * @dst: address of the destination node * @orig: address of the source node * @uid: local ICMP socket identifier * @subtype: TP packet subtype (see batadv_icmp_tp_subtype) * @session: TP session identifier * @seqno: the TP sequence number * @timestamp: time when the packet has been sent. This value is filled in a * TP_MSG and echoed back in the next TP_ACK so that the sender can compute the * RTT. Since it is read only by the host which wrote it, there is no need to * store it using network order */ struct batadv_icmp_tp_packet { __u8 packet_type; __u8 version; __u8 ttl; __u8 msg_type; /* see ICMP message types above */ __u8 dst[ETH_ALEN]; __u8 orig[ETH_ALEN]; __u8 uid; __u8 subtype; __u8 session[2]; __be32 seqno; __be32 timestamp; }; /** * enum batadv_icmp_tp_subtype - ICMP TP Meter packet subtypes * @BATADV_TP_MSG: Msg from sender to receiver * @BATADV_TP_ACK: acknowledgment from receiver to sender */ enum batadv_icmp_tp_subtype { BATADV_TP_MSG = 0, BATADV_TP_ACK, }; #define BATADV_RR_LEN 16 /** * struct batadv_icmp_packet_rr - ICMP RouteRecord packet * @packet_type: batman-adv packet type, part of the general header * @version: batman-adv protocol version, part of the general header * @ttl: time to live for this packet, part of the general header * @msg_type: ICMP packet type * @dst: address of the destination node * @orig: address of the source node * @uid: local ICMP socket identifier * @rr_cur: number of entries the rr array * @seqno: ICMP sequence number * @rr: route record array */ struct batadv_icmp_packet_rr { __u8 packet_type; __u8 version; __u8 ttl; __u8 msg_type; /* see ICMP message types above */ __u8 dst[ETH_ALEN]; __u8 orig[ETH_ALEN]; __u8 uid; __u8 rr_cur; __be16 seqno; __u8 rr[BATADV_RR_LEN][ETH_ALEN]; }; #define BATADV_ICMP_MAX_PACKET_SIZE sizeof(struct batadv_icmp_packet_rr) /* All packet headers in front of an ethernet header have to be completely * divisible by 2 but not by 4 to make the payload after the ethernet * header again 4 bytes boundary aligned. * * A packing of 2 is necessary to avoid extra padding at the end of the struct * caused by a structure member which is larger than two bytes. Otherwise * the structure would not fulfill the previously mentioned rule to avoid the * misalignment of the payload after the ethernet header. It may also lead to * leakage of information when the padding it not initialized before sending. */ /** * struct batadv_unicast_packet - unicast packet for network payload * @packet_type: batman-adv packet type, part of the general header * @version: batman-adv protocol version, part of the general header * @ttl: time to live for this packet, part of the general header * @ttvn: translation table version number * @dest: originator destination of the unicast packet */ struct batadv_unicast_packet { __u8 packet_type; __u8 version; __u8 ttl; __u8 ttvn; /* destination translation table version number */ __u8 dest[ETH_ALEN]; /* "4 bytes boundary + 2 bytes" long to make the payload after the * following ethernet header again 4 bytes boundary aligned */ }; /** * struct batadv_unicast_4addr_packet - extended unicast packet * @u: common unicast packet header * @src: address of the source * @subtype: packet subtype * @reserved: reserved byte for alignment */ struct batadv_unicast_4addr_packet { struct batadv_unicast_packet u; __u8 src[ETH_ALEN]; __u8 subtype; __u8 reserved; /* "4 bytes boundary + 2 bytes" long to make the payload after the * following ethernet header again 4 bytes boundary aligned */ }; /** * struct batadv_frag_packet - fragmented packet * @packet_type: batman-adv packet type, part of the general header * @version: batman-adv protocol version, part of the general header * @ttl: time to live for this packet, part of the general header * @dest: final destination used when routing fragments * @orig: originator of the fragment used when merging the packet * @no: fragment number within this sequence * @priority: priority of frame, from ToS IP precedence or 802.1p * @reserved: reserved byte for alignment * @seqno: sequence identification * @total_size: size of the merged packet */ struct batadv_frag_packet { __u8 packet_type; __u8 version; /* batman version field */ __u8 ttl; #if defined(__BIG_ENDIAN_BITFIELD) __u8 no:4; __u8 priority:3; __u8 reserved:1; #elif defined(__LITTLE_ENDIAN_BITFIELD) __u8 reserved:1; __u8 priority:3; __u8 no:4; #else #error "unknown bitfield endianness" #endif __u8 dest[ETH_ALEN]; __u8 orig[ETH_ALEN]; __be16 seqno; __be16 total_size; }; /** * struct batadv_bcast_packet - broadcast packet for network payload * @packet_type: batman-adv packet type, part of the general header * @version: batman-adv protocol version, part of the general header * @ttl: time to live for this packet, part of the general header * @reserved: reserved byte for alignment * @seqno: sequence identification * @orig: originator of the broadcast packet */ struct batadv_bcast_packet { __u8 packet_type; __u8 version; /* batman version field */ __u8 ttl; __u8 reserved; __be32 seqno; __u8 orig[ETH_ALEN]; /* "4 bytes boundary + 2 bytes" long to make the payload after the * following ethernet header again 4 bytes boundary aligned */ }; /** * struct batadv_mcast_packet - multicast packet for network payload * @packet_type: batman-adv packet type, part of the general header * @version: batman-adv protocol version, part of the general header * @ttl: time to live for this packet, part of the general header * @reserved: reserved byte for alignment * @tvlv_len: length of the appended tvlv buffer (in bytes) */ struct batadv_mcast_packet { __u8 packet_type; __u8 version; __u8 ttl; __u8 reserved; __be16 tvlv_len; /* "4 bytes boundary + 2 bytes" long to make the payload after the * following ethernet header again 4 bytes boundary aligned */ }; /** * struct batadv_coded_packet - network coded packet * @packet_type: batman-adv packet type, part of the general header * @version: batman-adv protocol version, part of the general header * @ttl: time to live for this packet, part of the general header * @first_source: original source of first included packet * @first_orig_dest: original destination of first included packet * @first_crc: checksum of first included packet * @first_ttvn: tt-version number of first included packet * @second_ttl: ttl of second packet * @second_dest: second receiver of this coded packet * @second_source: original source of second included packet * @second_orig_dest: original destination of second included packet * @second_crc: checksum of second included packet * @second_ttvn: tt version number of second included packet * @coded_len: length of network coded part of the payload */ struct batadv_coded_packet { __u8 packet_type; __u8 version; /* batman version field */ __u8 ttl; __u8 first_ttvn; /* __u8 first_dest[ETH_ALEN]; - saved in mac header destination */ __u8 first_source[ETH_ALEN]; __u8 first_orig_dest[ETH_ALEN]; __be32 first_crc; __u8 second_ttl; __u8 second_ttvn; __u8 second_dest[ETH_ALEN]; __u8 second_source[ETH_ALEN]; __u8 second_orig_dest[ETH_ALEN]; __be32 second_crc; __be16 coded_len; }; /** * struct batadv_unicast_tvlv_packet - generic unicast packet with tvlv payload * @packet_type: batman-adv packet type, part of the general header * @version: batman-adv protocol version, part of the general header * @ttl: time to live for this packet, part of the general header * @reserved: reserved field (for packet alignment) * @src: address of the source * @dst: address of the destination * @tvlv_len: length of tvlv data following the unicast tvlv header * @align: 2 bytes to align the header to a 4 byte boundary */ struct batadv_unicast_tvlv_packet { __u8 packet_type; __u8 version; /* batman version field */ __u8 ttl; __u8 reserved; __u8 dst[ETH_ALEN]; __u8 src[ETH_ALEN]; __be16 tvlv_len; __u16 align; }; /** * struct batadv_tvlv_hdr - base tvlv header struct * @type: tvlv container type (see batadv_tvlv_type) * @version: tvlv container version * @len: tvlv container length */ struct batadv_tvlv_hdr { __u8 type; __u8 version; __be16 len; }; /** * struct batadv_tvlv_gateway_data - gateway data propagated through gw tvlv * container * @bandwidth_down: advertised uplink download bandwidth * @bandwidth_up: advertised uplink upload bandwidth */ struct batadv_tvlv_gateway_data { __be32 bandwidth_down; __be32 bandwidth_up; }; /** * struct batadv_tvlv_tt_vlan_data - vlan specific tt data propagated through * the tt tvlv container * @crc: crc32 checksum of the entries belonging to this vlan * @vid: vlan identifier * @reserved: unused, useful for alignment purposes */ struct batadv_tvlv_tt_vlan_data { __be32 crc; __be16 vid; __u16 reserved; }; /** * struct batadv_tvlv_tt_data - tt data propagated through the tt tvlv container * @flags: translation table flags (see batadv_tt_data_flags) * @ttvn: translation table version number * @num_vlan: number of announced VLANs. In the TVLV this struct is followed by * one batadv_tvlv_tt_vlan_data object per announced vlan * @vlan_data: array of batadv_tvlv_tt_vlan_data objects */ struct batadv_tvlv_tt_data { __u8 flags; __u8 ttvn; __be16 num_vlan; struct batadv_tvlv_tt_vlan_data vlan_data[] __counted_by_be(num_vlan); }; /** * struct batadv_tvlv_tt_change - translation table diff data * @flags: status indicators concerning the non-mesh client (see * batadv_tt_client_flags) * @reserved: reserved field - useful for alignment purposes only * @addr: mac address of non-mesh client that triggered this tt change * @vid: VLAN identifier */ struct batadv_tvlv_tt_change { __u8 flags; __u8 reserved[3]; __u8 addr[ETH_ALEN]; __be16 vid; }; /** * struct batadv_tvlv_roam_adv - roaming advertisement * @client: mac address of roaming client * @vid: VLAN identifier */ struct batadv_tvlv_roam_adv { __u8 client[ETH_ALEN]; __be16 vid; }; /** * struct batadv_tvlv_mcast_data - payload of a multicast tvlv * @flags: multicast flags announced by the orig node * @reserved: reserved field */ struct batadv_tvlv_mcast_data { __u8 flags; __u8 reserved[3]; }; /** * struct batadv_tvlv_mcast_tracker - payload of a multicast tracker tvlv * @num_dests: number of subsequent destination originator MAC addresses */ struct batadv_tvlv_mcast_tracker { __be16 num_dests; }; #pragma pack() #endif /* _UAPI_LINUX_BATADV_PACKET_H_ */ batctl-2025.1/batadv_packet_compat.h000066400000000000000000000006461500012142700173560ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) B.A.T.M.A.N. contributors: * * Andreas Langer , Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #ifndef _BATCTL_BATADV_PACKET_COMPAT_H #define _BATCTL_BATADV_PACKET_COMPAT_H #include #ifndef __counted_by_be #define __counted_by_be(m) #endif #include "batadv_packet.h" #endif batctl-2025.1/batman_adv.h000066400000000000000000000410101500012142700153050ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright (C) B.A.T.M.A.N. contributors: * * Matthias Schiffer */ #ifndef _UAPI_LINUX_BATMAN_ADV_H_ #define _UAPI_LINUX_BATMAN_ADV_H_ #define BATADV_NL_NAME "batadv" #define BATADV_NL_MCAST_GROUP_CONFIG "config" #define BATADV_NL_MCAST_GROUP_TPMETER "tpmeter" /** * enum batadv_tt_client_flags - TT client specific flags * * Bits from 0 to 7 are called _remote flags_ because they are sent on the wire. * Bits from 8 to 15 are called _local flags_ because they are used for local * computations only. * * Bits from 4 to 7 - a subset of remote flags - are ensured to be in sync with * the other nodes in the network. To achieve this goal these flags are included * in the TT CRC computation. */ enum batadv_tt_client_flags { /** * @BATADV_TT_CLIENT_DEL: the client has to be deleted from the table */ BATADV_TT_CLIENT_DEL = (1 << 0), /** * @BATADV_TT_CLIENT_ROAM: the client roamed to/from another node and * the new update telling its new real location has not been * received/sent yet */ BATADV_TT_CLIENT_ROAM = (1 << 1), /** * @BATADV_TT_CLIENT_WIFI: this client is connected through a wifi * interface. This information is used by the "AP Isolation" feature */ BATADV_TT_CLIENT_WIFI = (1 << 4), /** * @BATADV_TT_CLIENT_ISOLA: this client is considered "isolated". This * information is used by the Extended Isolation feature */ BATADV_TT_CLIENT_ISOLA = (1 << 5), /** * @BATADV_TT_CLIENT_NOPURGE: this client should never be removed from * the table */ BATADV_TT_CLIENT_NOPURGE = (1 << 8), /** * @BATADV_TT_CLIENT_NEW: this client has been added to the local table * but has not been announced yet */ BATADV_TT_CLIENT_NEW = (1 << 9), /** * @BATADV_TT_CLIENT_PENDING: this client is marked for removal but it * is kept in the table for one more originator interval for consistency * purposes */ BATADV_TT_CLIENT_PENDING = (1 << 10), /** * @BATADV_TT_CLIENT_TEMP: this global client has been detected to be * part of the network but no node has already announced it */ BATADV_TT_CLIENT_TEMP = (1 << 11), }; /** * enum batadv_mcast_flags_priv - Private, own multicast flags * * These are internal, multicast related flags. Currently they describe certain * multicast related attributes of the segment this originator bridges into the * mesh. * * Those attributes are used to determine the public multicast flags this * originator is going to announce via TT. * * For netlink, if BATADV_MCAST_FLAGS_BRIDGED is unset then all querier * related flags are undefined. */ enum batadv_mcast_flags_priv { /** * @BATADV_MCAST_FLAGS_BRIDGED: There is a bridge on top of the mesh * interface. */ BATADV_MCAST_FLAGS_BRIDGED = (1 << 0), /** * @BATADV_MCAST_FLAGS_QUERIER_IPV4_EXISTS: Whether an IGMP querier * exists in the mesh */ BATADV_MCAST_FLAGS_QUERIER_IPV4_EXISTS = (1 << 1), /** * @BATADV_MCAST_FLAGS_QUERIER_IPV6_EXISTS: Whether an MLD querier * exists in the mesh */ BATADV_MCAST_FLAGS_QUERIER_IPV6_EXISTS = (1 << 2), /** * @BATADV_MCAST_FLAGS_QUERIER_IPV4_SHADOWING: If an IGMP querier * exists, whether it is potentially shadowing multicast listeners * (i.e. querier is behind our own bridge segment) */ BATADV_MCAST_FLAGS_QUERIER_IPV4_SHADOWING = (1 << 3), /** * @BATADV_MCAST_FLAGS_QUERIER_IPV6_SHADOWING: If an MLD querier * exists, whether it is potentially shadowing multicast listeners * (i.e. querier is behind our own bridge segment) */ BATADV_MCAST_FLAGS_QUERIER_IPV6_SHADOWING = (1 << 4), }; /** * enum batadv_gw_modes - gateway mode of node */ enum batadv_gw_modes { /** @BATADV_GW_MODE_OFF: gw mode disabled */ BATADV_GW_MODE_OFF, /** @BATADV_GW_MODE_CLIENT: send DHCP requests to gw servers */ BATADV_GW_MODE_CLIENT, /** @BATADV_GW_MODE_SERVER: announce itself as gateway server */ BATADV_GW_MODE_SERVER, }; /** * enum batadv_nl_attrs - batman-adv netlink attributes */ enum batadv_nl_attrs { /** * @BATADV_ATTR_UNSPEC: unspecified attribute to catch errors */ BATADV_ATTR_UNSPEC, /** * @BATADV_ATTR_VERSION: batman-adv version string */ BATADV_ATTR_VERSION, /** * @BATADV_ATTR_ALGO_NAME: name of routing algorithm */ BATADV_ATTR_ALGO_NAME, /** * @BATADV_ATTR_MESH_IFINDEX: index of the batman-adv interface */ BATADV_ATTR_MESH_IFINDEX, /** * @BATADV_ATTR_MESH_IFNAME: name of the batman-adv interface */ BATADV_ATTR_MESH_IFNAME, /** * @BATADV_ATTR_MESH_ADDRESS: mac address of the batman-adv interface */ BATADV_ATTR_MESH_ADDRESS, /** * @BATADV_ATTR_HARD_IFINDEX: index of the non-batman-adv interface */ BATADV_ATTR_HARD_IFINDEX, /** * @BATADV_ATTR_HARD_IFNAME: name of the non-batman-adv interface */ BATADV_ATTR_HARD_IFNAME, /** * @BATADV_ATTR_HARD_ADDRESS: mac address of the non-batman-adv * interface */ BATADV_ATTR_HARD_ADDRESS, /** * @BATADV_ATTR_ORIG_ADDRESS: originator mac address */ BATADV_ATTR_ORIG_ADDRESS, /** * @BATADV_ATTR_TPMETER_RESULT: result of run (see * batadv_tp_meter_status) */ BATADV_ATTR_TPMETER_RESULT, /** * @BATADV_ATTR_TPMETER_TEST_TIME: time (msec) the run took */ BATADV_ATTR_TPMETER_TEST_TIME, /** * @BATADV_ATTR_TPMETER_BYTES: amount of acked bytes during run */ BATADV_ATTR_TPMETER_BYTES, /** * @BATADV_ATTR_TPMETER_COOKIE: session cookie to match tp_meter session */ BATADV_ATTR_TPMETER_COOKIE, /** * @BATADV_ATTR_PAD: attribute used for padding for 64-bit alignment */ BATADV_ATTR_PAD, /** * @BATADV_ATTR_ACTIVE: Flag indicating if the hard interface is active */ BATADV_ATTR_ACTIVE, /** * @BATADV_ATTR_TT_ADDRESS: Client MAC address */ BATADV_ATTR_TT_ADDRESS, /** * @BATADV_ATTR_TT_TTVN: Translation table version */ BATADV_ATTR_TT_TTVN, /** * @BATADV_ATTR_TT_LAST_TTVN: Previous translation table version */ BATADV_ATTR_TT_LAST_TTVN, /** * @BATADV_ATTR_TT_CRC32: CRC32 over translation table */ BATADV_ATTR_TT_CRC32, /** * @BATADV_ATTR_TT_VID: VLAN ID */ BATADV_ATTR_TT_VID, /** * @BATADV_ATTR_TT_FLAGS: Translation table client flags */ BATADV_ATTR_TT_FLAGS, /** * @BATADV_ATTR_FLAG_BEST: Flags indicating entry is the best */ BATADV_ATTR_FLAG_BEST, /** * @BATADV_ATTR_LAST_SEEN_MSECS: Time in milliseconds since last seen */ BATADV_ATTR_LAST_SEEN_MSECS, /** * @BATADV_ATTR_NEIGH_ADDRESS: Neighbour MAC address */ BATADV_ATTR_NEIGH_ADDRESS, /** * @BATADV_ATTR_TQ: TQ to neighbour */ BATADV_ATTR_TQ, /** * @BATADV_ATTR_THROUGHPUT: Estimated throughput to Neighbour */ BATADV_ATTR_THROUGHPUT, /** * @BATADV_ATTR_BANDWIDTH_UP: Reported uplink bandwidth */ BATADV_ATTR_BANDWIDTH_UP, /** * @BATADV_ATTR_BANDWIDTH_DOWN: Reported downlink bandwidth */ BATADV_ATTR_BANDWIDTH_DOWN, /** * @BATADV_ATTR_ROUTER: Gateway router MAC address */ BATADV_ATTR_ROUTER, /** * @BATADV_ATTR_BLA_OWN: Flag indicating own originator */ BATADV_ATTR_BLA_OWN, /** * @BATADV_ATTR_BLA_ADDRESS: Bridge loop avoidance claim MAC address */ BATADV_ATTR_BLA_ADDRESS, /** * @BATADV_ATTR_BLA_VID: BLA VLAN ID */ BATADV_ATTR_BLA_VID, /** * @BATADV_ATTR_BLA_BACKBONE: BLA gateway originator MAC address */ BATADV_ATTR_BLA_BACKBONE, /** * @BATADV_ATTR_BLA_CRC: BLA CRC */ BATADV_ATTR_BLA_CRC, /** * @BATADV_ATTR_DAT_CACHE_IP4ADDRESS: Client IPv4 address */ BATADV_ATTR_DAT_CACHE_IP4ADDRESS, /** * @BATADV_ATTR_DAT_CACHE_HWADDRESS: Client MAC address */ BATADV_ATTR_DAT_CACHE_HWADDRESS, /** * @BATADV_ATTR_DAT_CACHE_VID: VLAN ID */ BATADV_ATTR_DAT_CACHE_VID, /** * @BATADV_ATTR_MCAST_FLAGS: Per originator multicast flags */ BATADV_ATTR_MCAST_FLAGS, /** * @BATADV_ATTR_MCAST_FLAGS_PRIV: Private, own multicast flags */ BATADV_ATTR_MCAST_FLAGS_PRIV, /** * @BATADV_ATTR_VLANID: VLAN id on top of mesh interface */ BATADV_ATTR_VLANID, /** * @BATADV_ATTR_AGGREGATED_OGMS_ENABLED: whether the batman protocol * messages of the mesh interface shall be aggregated or not. */ BATADV_ATTR_AGGREGATED_OGMS_ENABLED, /** * @BATADV_ATTR_AP_ISOLATION_ENABLED: whether the data traffic going * from a wireless client to another wireless client will be silently * dropped. */ BATADV_ATTR_AP_ISOLATION_ENABLED, /** * @BATADV_ATTR_ISOLATION_MARK: the isolation mark which is used to * classify clients as "isolated" by the Extended Isolation feature. */ BATADV_ATTR_ISOLATION_MARK, /** * @BATADV_ATTR_ISOLATION_MASK: the isolation (bit)mask which is used to * classify clients as "isolated" by the Extended Isolation feature. */ BATADV_ATTR_ISOLATION_MASK, /** * @BATADV_ATTR_BONDING_ENABLED: whether the data traffic going through * the mesh will be sent using multiple interfaces at the same time. */ BATADV_ATTR_BONDING_ENABLED, /** * @BATADV_ATTR_BRIDGE_LOOP_AVOIDANCE_ENABLED: whether the bridge loop * avoidance feature is enabled. This feature detects and avoids loops * between the mesh and devices bridged with the mesh interface */ BATADV_ATTR_BRIDGE_LOOP_AVOIDANCE_ENABLED, /** * @BATADV_ATTR_DISTRIBUTED_ARP_TABLE_ENABLED: whether the distributed * arp table feature is enabled. This feature uses a distributed hash * table to answer ARP requests without flooding the request through * the whole mesh. */ BATADV_ATTR_DISTRIBUTED_ARP_TABLE_ENABLED, /** * @BATADV_ATTR_FRAGMENTATION_ENABLED: whether the data traffic going * through the mesh will be fragmented or silently discarded if the * packet size exceeds the outgoing interface MTU. */ BATADV_ATTR_FRAGMENTATION_ENABLED, /** * @BATADV_ATTR_GW_BANDWIDTH_DOWN: defines the download bandwidth which * is propagated by this node if %BATADV_ATTR_GW_BANDWIDTH_MODE was set * to 'server'. */ BATADV_ATTR_GW_BANDWIDTH_DOWN, /** * @BATADV_ATTR_GW_BANDWIDTH_UP: defines the upload bandwidth which * is propagated by this node if %BATADV_ATTR_GW_BANDWIDTH_MODE was set * to 'server'. */ BATADV_ATTR_GW_BANDWIDTH_UP, /** * @BATADV_ATTR_GW_MODE: defines the state of the gateway features. * Possible values are specified in enum batadv_gw_modes */ BATADV_ATTR_GW_MODE, /** * @BATADV_ATTR_GW_SEL_CLASS: defines the selection criteria this node * will use to choose a gateway if gw_mode was set to 'client'. */ BATADV_ATTR_GW_SEL_CLASS, /** * @BATADV_ATTR_HOP_PENALTY: defines the penalty which will be applied * to an originator message's tq-field on every hop and/or per * hard interface */ BATADV_ATTR_HOP_PENALTY, /** * @BATADV_ATTR_LOG_LEVEL: bitmask with to define which debug messages * should be send to the debug log/trace ring buffer */ BATADV_ATTR_LOG_LEVEL, /** * @BATADV_ATTR_MULTICAST_FORCEFLOOD_ENABLED: whether multicast * optimizations should be replaced by simple broadcast-like flooding * of multicast packets. If set to non-zero then all nodes in the mesh * are going to use classic flooding for any multicast packet with no * optimizations. */ BATADV_ATTR_MULTICAST_FORCEFLOOD_ENABLED, /** * @BATADV_ATTR_NETWORK_CODING_ENABLED: whether Network Coding (using * some magic to send fewer wifi packets but still the same content) is * enabled or not. */ BATADV_ATTR_NETWORK_CODING_ENABLED, /** * @BATADV_ATTR_ORIG_INTERVAL: defines the interval in milliseconds in * which batman sends its protocol messages. */ BATADV_ATTR_ORIG_INTERVAL, /** * @BATADV_ATTR_ELP_INTERVAL: defines the interval in milliseconds in * which batman emits probing packets for neighbor sensing (ELP). */ BATADV_ATTR_ELP_INTERVAL, /** * @BATADV_ATTR_THROUGHPUT_OVERRIDE: defines the throughput value to be * used by B.A.T.M.A.N. V when estimating the link throughput using * this interface. If the value is set to 0 then batman-adv will try to * estimate the throughput by itself. */ BATADV_ATTR_THROUGHPUT_OVERRIDE, /** * @BATADV_ATTR_MULTICAST_FANOUT: defines the maximum number of packet * copies that may be generated for a multicast-to-unicast conversion. * Once this limit is exceeded distribution will fall back to broadcast. */ BATADV_ATTR_MULTICAST_FANOUT, /* add attributes above here, update the policy in netlink.c */ /** * @__BATADV_ATTR_AFTER_LAST: internal use */ __BATADV_ATTR_AFTER_LAST, /** * @NUM_BATADV_ATTR: total number of batadv_nl_attrs available */ NUM_BATADV_ATTR = __BATADV_ATTR_AFTER_LAST, /** * @BATADV_ATTR_MAX: highest attribute number currently defined */ BATADV_ATTR_MAX = __BATADV_ATTR_AFTER_LAST - 1 }; /** * enum batadv_nl_commands - supported batman-adv netlink commands */ enum batadv_nl_commands { /** * @BATADV_CMD_UNSPEC: unspecified command to catch errors */ BATADV_CMD_UNSPEC, /** * @BATADV_CMD_GET_MESH: Get attributes from mesh(if) */ BATADV_CMD_GET_MESH, /** * @BATADV_CMD_GET_MESH_INFO: Alias for @BATADV_CMD_GET_MESH */ BATADV_CMD_GET_MESH_INFO = BATADV_CMD_GET_MESH, /** * @BATADV_CMD_TP_METER: Start a tp meter session */ BATADV_CMD_TP_METER, /** * @BATADV_CMD_TP_METER_CANCEL: Cancel a tp meter session */ BATADV_CMD_TP_METER_CANCEL, /** * @BATADV_CMD_GET_ROUTING_ALGOS: Query the list of routing algorithms. */ BATADV_CMD_GET_ROUTING_ALGOS, /** * @BATADV_CMD_GET_HARDIF: Get attributes from a hardif of the * current mesh(if) */ BATADV_CMD_GET_HARDIF, /** * @BATADV_CMD_GET_HARDIFS: Alias for @BATADV_CMD_GET_HARDIF */ BATADV_CMD_GET_HARDIFS = BATADV_CMD_GET_HARDIF, /** * @BATADV_CMD_GET_TRANSTABLE_LOCAL: Query list of local translations */ BATADV_CMD_GET_TRANSTABLE_LOCAL, /** * @BATADV_CMD_GET_TRANSTABLE_GLOBAL: Query list of global translations */ BATADV_CMD_GET_TRANSTABLE_GLOBAL, /** * @BATADV_CMD_GET_ORIGINATORS: Query list of originators */ BATADV_CMD_GET_ORIGINATORS, /** * @BATADV_CMD_GET_NEIGHBORS: Query list of neighbours */ BATADV_CMD_GET_NEIGHBORS, /** * @BATADV_CMD_GET_GATEWAYS: Query list of gateways */ BATADV_CMD_GET_GATEWAYS, /** * @BATADV_CMD_GET_BLA_CLAIM: Query list of bridge loop avoidance claims */ BATADV_CMD_GET_BLA_CLAIM, /** * @BATADV_CMD_GET_BLA_BACKBONE: Query list of bridge loop avoidance * backbones */ BATADV_CMD_GET_BLA_BACKBONE, /** * @BATADV_CMD_GET_DAT_CACHE: Query list of DAT cache entries */ BATADV_CMD_GET_DAT_CACHE, /** * @BATADV_CMD_GET_MCAST_FLAGS: Query list of multicast flags */ BATADV_CMD_GET_MCAST_FLAGS, /** * @BATADV_CMD_SET_MESH: Set attributes for mesh(if) */ BATADV_CMD_SET_MESH, /** * @BATADV_CMD_SET_HARDIF: Set attributes for hardif of the * current mesh(if) */ BATADV_CMD_SET_HARDIF, /** * @BATADV_CMD_GET_VLAN: Get attributes from a VLAN of the * current mesh(if) */ BATADV_CMD_GET_VLAN, /** * @BATADV_CMD_SET_VLAN: Set attributes for VLAN of the * current mesh(if) */ BATADV_CMD_SET_VLAN, /* add new commands above here */ /** * @__BATADV_CMD_AFTER_LAST: internal use */ __BATADV_CMD_AFTER_LAST, /** * @BATADV_CMD_MAX: highest used command number */ BATADV_CMD_MAX = __BATADV_CMD_AFTER_LAST - 1 }; /** * enum batadv_tp_meter_reason - reason of a tp meter test run stop */ enum batadv_tp_meter_reason { /** * @BATADV_TP_REASON_COMPLETE: sender finished tp run */ BATADV_TP_REASON_COMPLETE = 3, /** * @BATADV_TP_REASON_CANCEL: sender was stopped during run */ BATADV_TP_REASON_CANCEL = 4, /* error status >= 128 */ /** * @BATADV_TP_REASON_DST_UNREACHABLE: receiver could not be reached or * didn't answer */ BATADV_TP_REASON_DST_UNREACHABLE = 128, /** * @BATADV_TP_REASON_RESEND_LIMIT: (unused) sender retry reached limit */ BATADV_TP_REASON_RESEND_LIMIT = 129, /** * @BATADV_TP_REASON_ALREADY_ONGOING: test to or from the same node * already ongoing */ BATADV_TP_REASON_ALREADY_ONGOING = 130, /** * @BATADV_TP_REASON_MEMORY_ERROR: test was stopped due to low memory */ BATADV_TP_REASON_MEMORY_ERROR = 131, /** * @BATADV_TP_REASON_CANT_SEND: failed to send via outgoing interface */ BATADV_TP_REASON_CANT_SEND = 132, /** * @BATADV_TP_REASON_TOO_MANY: too many ongoing sessions */ BATADV_TP_REASON_TOO_MANY = 133, }; /** * enum batadv_ifla_attrs - batman-adv ifla nested attributes */ enum batadv_ifla_attrs { /** * @IFLA_BATADV_UNSPEC: unspecified attribute which is not parsed by * rtnetlink */ IFLA_BATADV_UNSPEC, /** * @IFLA_BATADV_ALGO_NAME: routing algorithm (name) which should be * used by the newly registered batadv net_device. */ IFLA_BATADV_ALGO_NAME, /* add attributes above here, update the policy in mesh-interface.c */ /** * @__IFLA_BATADV_MAX: internal use */ __IFLA_BATADV_MAX, }; #define IFLA_BATADV_MAX (__IFLA_BATADV_MAX - 1) #endif /* _UAPI_LINUX_BATMAN_ADV_H_ */ batctl-2025.1/bisect_iv.c000066400000000000000000001263021500012142700151630ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include #include #include "bisect_iv.h" #include "bat-hosts.h" #include "hash.h" #include "main.h" #include "functions.h" static struct hashtable_t *node_hash; static struct bat_node *curr_bat_node; static void bisect_iv_usage(void) { fprintf(stderr, "Usage: batctl bisect_iv [parameters] .. \n"); fprintf(stderr, "parameters:\n"); fprintf(stderr, " \t -h print this help\n"); fprintf(stderr, " \t -l run a loop detection of given mac address or bat-host (default)\n"); fprintf(stderr, " \t -n don't convert addresses to bat-host names\n"); fprintf(stderr, " \t -o only display orig events that affect given mac address or bat-host\n"); fprintf(stderr, " \t -r print routing tables of given mac address or bat-host\n"); fprintf(stderr, " \t -s seqno range to limit the output\n"); fprintf(stderr, " \t -t trace seqnos of given mac address or bat-host\n"); } static int compare_name(void *data1, void *data2) { return (memcmp(data1, data2, NAME_LEN) == 0 ? 1 : 0); } static int choose_name(void *data, int32_t size) { uint32_t m_size = NAME_LEN - 1; unsigned char *key = data; uint32_t hash = 0; size_t i; for (i = 0; i < m_size; i++) { hash += key[i]; hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return (hash % size); } static struct bat_node *node_get(char *name) { struct bat_node *bat_node; if (!name) return NULL; bat_node = (struct bat_node *)hash_find(node_hash, name); if (bat_node) goto out; bat_node = malloc(sizeof(struct bat_node)); if (!bat_node) { fprintf(stderr, "Could not allocate memory for data structure (out of mem?) - skipping"); return NULL; } strncpy(bat_node->name, name, NAME_LEN); bat_node->name[NAME_LEN - 1] = '\0'; INIT_LIST_HEAD(&bat_node->orig_event_list); INIT_LIST_HEAD(&bat_node->rt_table_list); memset(bat_node->loop_magic, 0, sizeof(bat_node->loop_magic)); memset(bat_node->loop_magic2, 0, sizeof(bat_node->loop_magic2)); hash_add(node_hash, bat_node); out: return bat_node; } static struct orig_event *orig_event_new(struct bat_node *bat_node, struct bat_node *orig_node) { struct orig_event *orig_event; orig_event = malloc(sizeof(struct orig_event)); if (!orig_event) { fprintf(stderr, "Could not allocate memory for orig event structure (out of mem?) - skipping"); return NULL; } INIT_LIST_HEAD(&orig_event->event_list); INIT_LIST_HEAD(&orig_event->rt_hist_list); orig_event->orig_node = orig_node; list_add_tail(&orig_event->list, &bat_node->orig_event_list); return orig_event; } static struct orig_event *orig_event_get_by_name(struct bat_node *bat_node, char *orig) { struct orig_event *orig_event; struct bat_node *orig_node; if (!bat_node) return NULL; list_for_each_entry(orig_event, &bat_node->orig_event_list, list) { if (compare_name(orig_event->orig_node->name, orig)) return orig_event; } orig_node = node_get(orig); if (!orig_node) return NULL; return orig_event_new(bat_node, orig_node); } static struct orig_event *orig_event_get_by_ptr(struct bat_node *bat_node, struct bat_node *orig_node) { struct orig_event *orig_event; if (!bat_node) return NULL; list_for_each_entry(orig_event, &bat_node->orig_event_list, list) { if (orig_event->orig_node == orig_node) return orig_event; } return orig_event_new(bat_node, orig_node); } static void node_free(void *data) { struct bat_node *bat_node = (struct bat_node *)data; struct seqno_event *seqno_event_tmp; struct orig_event *orig_event_tmp; struct seqno_event *seqno_event; struct orig_event *orig_event; struct rt_table *rt_table_tmp; struct rt_hist *rt_hist_tmp; struct rt_table *rt_table; struct rt_hist *rt_hist; list_for_each_entry_safe(orig_event, orig_event_tmp, &bat_node->orig_event_list, list) { list_for_each_entry_safe(seqno_event, seqno_event_tmp, &orig_event->event_list, list) { list_del(&seqno_event->list); free(seqno_event); } list_for_each_entry_safe(rt_hist, rt_hist_tmp, &orig_event->rt_hist_list, list) { list_del(&rt_hist->list); free(rt_hist); } list_del(&orig_event->list); free(orig_event); } list_for_each_entry_safe(rt_table, rt_table_tmp, &bat_node->rt_table_list, list) { list_del(&rt_table->list); free(rt_table->entries); free(rt_table); } free(bat_node); } static int routing_table_new(char *orig, char *next_hop, char *old_next_hop, char rt_flag) { struct rt_table *prev_rt_table = NULL; struct seqno_event *seqno_event; struct bat_node *next_hop_node; struct orig_event *orig_event; struct rt_table *rt_table; struct rt_hist *rt_hist; int j = -1; int i; if (!curr_bat_node) { fprintf(stderr, "Routing table change without preceding OGM - skipping"); goto err; } if (!orig) { fprintf(stderr, "Invalid originator found - skipping"); goto err; } if (rt_flag != RT_FLAG_DELETE && !next_hop) { fprintf(stderr, "Invalid next hop found - skipping"); goto err; } if (rt_flag == RT_FLAG_UPDATE && !old_next_hop) { fprintf(stderr, "Invalid old next hop found - skipping"); goto err; } next_hop_node = node_get(next_hop); if (rt_flag != RT_FLAG_DELETE && !next_hop_node) goto err; orig_event = orig_event_get_by_name(curr_bat_node, orig); if (!orig_event) goto err; if (list_empty(&orig_event->event_list)) { fprintf(stderr, "Routing table change without any preceding OGM of that originator - skipping"); goto err; } if (!compare_name(((struct seqno_event *)orig_event->event_list.prev)->orig->name, orig)) { fprintf(stderr, "Routing table change does not match with last received OGM - skipping"); goto err; } rt_table = malloc(sizeof(struct rt_table)); if (!rt_table) { fprintf(stderr, "Could not allocate memory for routing table (out of mem?) - skipping"); goto err; } rt_hist = malloc(sizeof(struct rt_hist)); if (!rt_hist) { fprintf(stderr, "Could not allocate memory for routing history (out of mem?) - skipping"); goto table_free; } rt_table->num_entries = 1; rt_hist->prev_rt_hist = NULL; rt_hist->next_hop = next_hop_node; rt_hist->flags = rt_flag; memset(rt_hist->loop_magic, 0, sizeof(rt_hist->loop_magic)); if (!list_empty(&orig_event->rt_hist_list)) rt_hist->prev_rt_hist = (struct rt_hist *)(orig_event->rt_hist_list.prev); if (!list_empty(&curr_bat_node->rt_table_list)) prev_rt_table = (struct rt_table *)(curr_bat_node->rt_table_list.prev); switch (rt_flag) { case RT_FLAG_ADD: if (prev_rt_table) rt_table->num_entries = prev_rt_table->num_entries + 1; break; case RT_FLAG_UPDATE: if (prev_rt_table) { rt_table->num_entries = prev_rt_table->num_entries + 1; /* if we had that route already we just change the entry */ for (i = 0; i < prev_rt_table->num_entries; i++) { if (compare_name(orig, prev_rt_table->entries[i].orig)) { rt_table->num_entries = prev_rt_table->num_entries; break; } } } break; case RT_FLAG_DELETE: if (prev_rt_table) { rt_table->num_entries = prev_rt_table->num_entries + 1; /* if we had that route already we just change the entry */ for (i = 0; i < prev_rt_table->num_entries; i++) { if (compare_name(orig, prev_rt_table->entries[i].orig)) { rt_table->num_entries = prev_rt_table->num_entries; break; } } if (rt_table->num_entries != prev_rt_table->num_entries) { fprintf(stderr, "Found a delete entry of orig '%s' but no existing record - skipping", orig); goto rt_hist_free; } /* we need to create a special seqno event as a timer instead * of an OGM triggered that event */ seqno_event = malloc(sizeof(struct seqno_event)); if (!seqno_event) { fprintf(stderr, "Could not allocate memory for delete seqno event (out of mem?) - skipping"); goto rt_hist_free; } seqno_event->orig = node_get(orig); seqno_event->neigh = NULL; seqno_event->prev_sender = NULL; seqno_event->seqno = -1; seqno_event->tq = -1; seqno_event->ttl = -1; seqno_event->rt_hist = NULL; list_add_tail(&seqno_event->list, &orig_event->event_list); } break; default: fprintf(stderr, "Unknown rt_flag received: %i - skipping", rt_flag); goto rt_hist_free; } rt_table->entries = malloc(sizeof(struct rt_entry) * rt_table->num_entries); if (!rt_table->entries) { fprintf(stderr, "Could not allocate memory for routing table entries (out of mem?) - skipping"); goto rt_hist_free; } if (prev_rt_table) { for (i = 0; i < prev_rt_table->num_entries; i++) { /* if we have a previously deleted item don't copy it over */ if (prev_rt_table->entries[i].flags == RT_FLAG_DELETE) { rt_table->num_entries--; continue; } /* if we delete one item the entries are not in sync anymore, * therefore we need to counters: one for the old and one for * the new routing table */ j++; memcpy((char *)&rt_table->entries[j], (char *)&prev_rt_table->entries[i], sizeof(struct rt_entry)); if (compare_name(orig, rt_table->entries[j].orig)) { if (rt_flag != RT_FLAG_DELETE) rt_table->entries[j].next_hop = next_hop_node; rt_table->entries[j].flags = rt_flag; continue; } rt_table->entries[j].flags = 0; } } if (rt_table->num_entries == 1 || rt_table->num_entries != j + 1) { i = rt_table->num_entries; strncpy(rt_table->entries[i - 1].orig, orig, NAME_LEN); rt_table->entries[i - 1].orig[NAME_LEN - 1] = '\0'; rt_table->entries[i - 1].next_hop = next_hop_node; rt_table->entries[i - 1].flags = rt_flag; } rt_table->rt_hist = rt_hist; rt_hist->seqno_event = (struct seqno_event *)(orig_event->event_list.prev); rt_hist->seqno_event->rt_hist = rt_hist; rt_hist->rt_table = rt_table; list_add_tail(&rt_table->list, &curr_bat_node->rt_table_list); list_add_tail(&rt_hist->list, &orig_event->rt_hist_list); return 1; rt_hist_free: free(rt_hist); table_free: free(rt_table); err: return 0; } static int seqno_event_new(char *iface_addr, char *orig, char *prev_sender, char *neigh, long long seqno, int tq, int ttl) { struct bat_node *prev_sender_node; struct seqno_event *seqno_event; struct orig_event *orig_event; struct bat_node *neigh_node; struct bat_node *orig_node; if (!iface_addr) { fprintf(stderr, "Invalid interface address found - skipping"); goto err; } if (!orig) { fprintf(stderr, "Invalid originator found - skipping"); goto err; } if (!neigh) { fprintf(stderr, "Invalid neighbor found - skipping"); goto err; } if (seqno < 0 || seqno > UINT32_MAX) { fprintf(stderr, "Invalid sequence number found (%lli) - skipping", seqno); goto err; } if (tq < 0 || tq > UINT8_MAX) { fprintf(stderr, "Invalid tq value found (%i) - skipping", tq); goto err; } if (ttl < 0 || ttl > UINT8_MAX) { fprintf(stderr, "Invalid ttl value found (%i) - skipping", ttl); goto err; } curr_bat_node = node_get(iface_addr); if (!curr_bat_node) goto err; orig_node = node_get(orig); if (!orig_node) goto err; neigh_node = node_get(neigh); if (!neigh_node) goto err; prev_sender_node = node_get(prev_sender); if (!prev_sender_node) goto err; orig_event = orig_event_get_by_ptr(curr_bat_node, orig_node); if (!orig_event) goto err; seqno_event = malloc(sizeof(struct seqno_event)); if (!seqno_event) { fprintf(stderr, "Could not allocate memory for seqno event (out of mem?) - skipping"); goto err; } seqno_event->orig = orig_node; seqno_event->neigh = neigh_node; seqno_event->prev_sender = prev_sender_node; seqno_event->seqno = seqno; seqno_event->tq = tq; seqno_event->ttl = ttl; seqno_event->rt_hist = NULL; list_add_tail(&seqno_event->list, &orig_event->event_list); return 1; err: return 0; } static int parse_log_file(char *file_path) { char line_buff[MAX_LINE]; char *start_ptr_safe; int line_count = 0; char *prev_sender; char *iface_addr; char *start_ptr; long long seqno; char *tok_ptr; char rt_flag; char *neigh; char *orig; FILE *fd; int ttl; int res; int max; int tq; int i; fd = fopen(file_path, "r"); if (!fd) { fprintf(stderr, "Error - could not open file '%s': %s\n", file_path, strerror(errno)); return 0; } while (fgets(line_buff, sizeof(line_buff), fd)) { /* ignore the timestamp at the beginning of each line */ start_ptr = line_buff + 13; line_count++; if (strstr(start_ptr, "Received BATMAN packet via NB")) { strtok_r(start_ptr, " ", &start_ptr_safe); neigh = NULL; iface_addr = NULL; orig = NULL; prev_sender = NULL; seqno = -1; tq = -1; ttl = -1; for (i = 0; i < 21; i++) { tok_ptr = strtok_r(NULL, " ", &start_ptr_safe); if (!tok_ptr) break; switch (i) { case 4: neigh = tok_ptr; neigh[strlen(neigh) - 1] = 0; break; case 7: iface_addr = tok_ptr + 1; iface_addr[strlen(iface_addr) - 1] = 0; break; case 10: orig = tok_ptr; orig[strlen(orig) - 1] = 0; break; case 14: prev_sender = tok_ptr; prev_sender[strlen(prev_sender) - 1] = 0; break; case 16: seqno = strtoll(tok_ptr, NULL, 10); break; case 18: tq = strtol(tok_ptr, NULL, 10); break; case 20: ttl = strtol(tok_ptr, NULL, 10); break; } } if (ttl == -1) { fprintf(stderr, "Broken 'received packet' line found - skipping [file: %s, line: %i]\n", file_path, line_count); continue; } res = seqno_event_new(iface_addr, orig, prev_sender, neigh, seqno, tq, ttl); if (res < 1) fprintf(stderr, " [file: %s, line: %i]\n", file_path, line_count); } else if (strstr(start_ptr, "Adding route towards") || strstr(start_ptr, "Changing route towards") || strstr(start_ptr, "Deleting route towards")) { rt_flag = RT_FLAG_UPDATE; max = 12; if (strstr(start_ptr, "Adding route towards")) { rt_flag = RT_FLAG_ADD; max = 5; } else if (strstr(start_ptr, "Deleting route towards")) { rt_flag = RT_FLAG_DELETE; max = 3; } strtok_r(start_ptr, " ", &start_ptr_safe); orig = NULL; neigh = NULL; prev_sender = NULL; for (i = 0; i < max; i++) { tok_ptr = strtok_r(NULL, " ", &start_ptr_safe); if (!tok_ptr) break; switch (i) { case 2: orig = tok_ptr; if (rt_flag == RT_FLAG_DELETE) orig[strlen(orig) - 1] = 0; break; case 4: if (rt_flag == RT_FLAG_ADD) { neigh = tok_ptr; neigh[strlen(neigh) - 2] = 0; } break; case 5: neigh = tok_ptr; break; case 9: prev_sender = tok_ptr; prev_sender[strlen(prev_sender) - 2] = 0; break; } } if ((rt_flag == RT_FLAG_ADD && !neigh) || (rt_flag == RT_FLAG_UPDATE && !prev_sender) || (rt_flag == RT_FLAG_DELETE && !orig)) { fprintf(stderr, "Broken '%s route' line found - skipping [file: %s, line: %i]\n", (rt_flag == RT_FLAG_UPDATE ? "changing" : (rt_flag == RT_FLAG_ADD ? "adding" : "deleting")), file_path, line_count); continue; } res = routing_table_new(orig, neigh, prev_sender, rt_flag); if (res < 1) fprintf(stderr, " [file: %s, line: %i]\n", file_path, line_count); } } // printf("File '%s' parsed (lines: %i)\n", file_path, line_count); fclose(fd); curr_bat_node = NULL; return 1; } static struct rt_hist *get_rt_hist_by_seqno(struct orig_event *orig_event, long long seqno) { struct seqno_event *seqno_event; struct rt_hist *rt_hist = NULL; list_for_each_entry(seqno_event, &orig_event->event_list, list) { if (seqno_event->seqno > seqno) break; if (seqno_event->rt_hist) rt_hist = seqno_event->rt_hist; } return rt_hist; } static struct rt_hist *get_rt_hist_by_node_seqno(struct bat_node *bat_node, struct bat_node *orig_node, long long seqno) { struct orig_event *orig_event; struct rt_hist *rt_hist; orig_event = orig_event_get_by_ptr(bat_node, orig_node); if (!orig_event) return NULL; rt_hist = get_rt_hist_by_seqno(orig_event, seqno); return rt_hist; } static int print_rt_path_at_seqno(struct bat_node *src_node, struct bat_node *dst_node, struct bat_node *next_hop, long long seqno, long long seqno_rand, int read_opt) { char curr_loop_magic[LOOP_MAGIC_LEN]; struct bat_node *next_hop_tmp; struct orig_event *orig_event; struct rt_hist *rt_hist; snprintf(curr_loop_magic, sizeof(curr_loop_magic), "%s%s%lli%lli", src_node->name, dst_node->name, seqno, seqno_rand); printf("Path towards %s (seqno %lli ", get_name_by_macstr(dst_node->name, read_opt), seqno); printf("via neigh %s):", get_name_by_macstr(next_hop->name, read_opt)); next_hop_tmp = next_hop; while (1) { printf(" -> %s%s", get_name_by_macstr(next_hop_tmp->name, read_opt), (dst_node == next_hop_tmp ? "." : "")); /* destination reached */ if (dst_node == next_hop_tmp) break; orig_event = orig_event_get_by_ptr(next_hop_tmp, dst_node); if (!orig_event) goto out; /* no more data - path seems[tm] fine */ if (list_empty(&orig_event->event_list)) goto out; /* same here */ if (list_empty(&orig_event->rt_hist_list)) goto out; /* we are running in a loop */ if (memcmp(curr_loop_magic, next_hop_tmp->loop_magic, LOOP_MAGIC_LEN) == 0) { printf(" aborted due to loop!"); goto out; } memcpy(next_hop_tmp->loop_magic, curr_loop_magic, sizeof(next_hop_tmp->loop_magic)); rt_hist = get_rt_hist_by_seqno(orig_event, seqno); /* no more routing data - what can we do ? */ if (!rt_hist) break; next_hop_tmp = rt_hist->next_hop; } out: printf("\n"); return 1; } static int find_rt_table_change(struct bat_node *src_node, struct bat_node *dst_node, struct bat_node *curr_node, long long seqno_min, long long seqno_max, long long seqno_rand, int read_opt) { char curr_loop_magic[LOOP_MAGIC_LEN]; long long seqno_min_tmp = seqno_min; struct orig_event *orig_event; struct rt_hist *rt_hist_tmp; struct rt_hist *rt_hist; char loop_check = 0; long long seqno_tmp; int res; /* recursion ends here */ if (curr_node == dst_node) { rt_hist = get_rt_hist_by_node_seqno(src_node, dst_node, seqno_max); if (rt_hist) print_rt_path_at_seqno(src_node, dst_node, rt_hist->next_hop, seqno_max, seqno_rand, read_opt); return 0; } snprintf(curr_loop_magic, sizeof(curr_loop_magic), "%s%s%lli%lli", src_node->name, dst_node->name, seqno_min_tmp, seqno_rand); orig_event = orig_event_get_by_ptr(curr_node, dst_node); if (!orig_event) goto out; list_for_each_entry(rt_hist, &orig_event->rt_hist_list, list) { /* special seqno that indicates an originator timeout */ if (rt_hist->seqno_event->seqno == -1) { printf("Woot - originator timeout ??\n"); continue; } if (seqno_min_tmp != -1 && rt_hist->seqno_event->seqno < seqno_min_tmp) continue; if (seqno_max != -1 && rt_hist->seqno_event->seqno >= seqno_max) continue; /* we are running in a loop */ if (memcmp(curr_loop_magic, rt_hist->loop_magic, LOOP_MAGIC_LEN) == 0) { rt_hist_tmp = get_rt_hist_by_node_seqno(src_node, dst_node, rt_hist->seqno_event->seqno); if (rt_hist_tmp) print_rt_path_at_seqno(src_node, dst_node, rt_hist_tmp->next_hop, rt_hist->seqno_event->seqno, seqno_rand, read_opt); goto loop; } memcpy(rt_hist->loop_magic, curr_loop_magic, sizeof(rt_hist->loop_magic)); loop_check = 1; res = find_rt_table_change(src_node, dst_node, rt_hist->next_hop, seqno_min_tmp, rt_hist->seqno_event->seqno, seqno_rand, read_opt); seqno_min_tmp = rt_hist->seqno_event->seqno + 1; /* find_rt_table_change() did not run into a loop and printed the path */ if (res == 0) continue; /* retrieve routing table towards dst at that point and * print the routing path */ rt_hist_tmp = get_rt_hist_by_node_seqno(src_node, dst_node, rt_hist->seqno_event->seqno); if (!rt_hist_tmp) continue; print_rt_path_at_seqno(src_node, dst_node, rt_hist_tmp->next_hop, rt_hist->seqno_event->seqno, seqno_rand, read_opt); } /* if we have no routing table changes within the seqno range * the loop detection above won't be triggered */ if (!loop_check) { if (memcmp(curr_loop_magic, curr_node->loop_magic2, LOOP_MAGIC_LEN) == 0) { rt_hist_tmp = get_rt_hist_by_node_seqno(src_node, dst_node, seqno_min); if (rt_hist_tmp) print_rt_path_at_seqno(src_node, dst_node, rt_hist_tmp->next_hop, seqno_min, seqno_rand, read_opt); /* no need to print the path twice */ if (seqno_min == seqno_max) goto out; else goto loop; } memcpy(curr_node->loop_magic2, curr_loop_magic, sizeof(curr_node->loop_magic2)); } seqno_tmp = seqno_max - 1; if (seqno_min == seqno_max) seqno_tmp = seqno_max; rt_hist = get_rt_hist_by_seqno(orig_event, seqno_tmp); if (rt_hist) return find_rt_table_change(src_node, dst_node, rt_hist->next_hop, seqno_min_tmp, seqno_max, seqno_rand, read_opt); out: return -1; loop: return -2; } static void loop_detection(char *loop_orig, long long seqno_min, long long seqno_max, char *filter_orig, int read_opt) { struct hash_it_t *hashit = NULL; struct orig_event *orig_event; struct rt_hist *prev_rt_hist; struct bat_node *bat_node; long long last_seqno = -1; long long seqno_count = 0; char check_orig[NAME_LEN]; struct rt_hist *rt_hist; int res; printf("\nAnalyzing routing tables "); /* if no option was given loop_orig is empty */ memset(check_orig, 0, NAME_LEN); if (!compare_name(loop_orig, check_orig)) printf("of originator: %s ", get_name_by_macstr(loop_orig, read_opt)); if ((seqno_min == -1) && (seqno_max == -1)) printf("[all sequence numbers]"); else if (seqno_min == seqno_max) printf("[sequence number: %lli]", seqno_min); else printf("[sequence number range: %lli-%lli]", seqno_min, seqno_max); if (!compare_name(filter_orig, check_orig)) printf(" [filter originator: %s]", get_name_by_macstr(filter_orig, read_opt)); printf("\n"); while (NULL != (hashit = hash_iterate(node_hash, hashit))) { bat_node = hashit->bucket->data; if (!compare_name(loop_orig, check_orig) && !compare_name(loop_orig, bat_node->name)) continue; printf("\nChecking host: %s\n", get_name_by_macstr(bat_node->name, read_opt)); list_for_each_entry(orig_event, &bat_node->orig_event_list, list) { if (bat_node == orig_event->orig_node) continue; if (!compare_name(filter_orig, check_orig) && !compare_name(filter_orig, orig_event->orig_node->name)) continue; /* we might have no log file from this node */ if (list_empty(&orig_event->event_list)) { fprintf(stderr, "No seqno data of originator '%s' - skipping\n", get_name_by_macstr(orig_event->orig_node->name, read_opt)); continue; } /* or routing tables */ if (list_empty(&orig_event->rt_hist_list)) { fprintf(stderr, "No routing history of originator '%s' - skipping\n", get_name_by_macstr(orig_event->orig_node->name, read_opt)); continue; } list_for_each_entry(rt_hist, &orig_event->rt_hist_list, list) { /* special seqno that indicates an originator timeout */ if (rt_hist->seqno_event->seqno == -1) continue; if (seqno_min != -1 && rt_hist->seqno_event->seqno < seqno_min) continue; if (seqno_max != -1 && rt_hist->seqno_event->seqno > seqno_max) continue; /* sometime we change the routing table more than once * with the same seqno */ if (last_seqno == rt_hist->seqno_event->seqno) seqno_count++; else seqno_count = 0; last_seqno = rt_hist->seqno_event->seqno; if (rt_hist->flags == RT_FLAG_DELETE) { printf("Path towards %s deleted (originator timeout)\n", get_name_by_macstr(rt_hist->seqno_event->orig->name, read_opt)); continue; } prev_rt_hist = rt_hist->prev_rt_hist; if (prev_rt_hist && rt_hist->seqno_event->seqno != prev_rt_hist->seqno_event->seqno) { if (rt_hist->seqno_event->seqno < prev_rt_hist->seqno_event->seqno) { fprintf(stderr, "Smaller seqno (%lli) than previously received seqno (%lli) of orig %s triggered routing table change - skipping recursive check\n", rt_hist->seqno_event->seqno, prev_rt_hist->seqno_event->seqno, get_name_by_macstr(rt_hist->seqno_event->orig->name, read_opt)); goto validate_path; } if (rt_hist->seqno_event->seqno == prev_rt_hist->seqno_event->seqno + 1) goto validate_path; res = find_rt_table_change(bat_node, rt_hist->seqno_event->orig, prev_rt_hist->next_hop, prev_rt_hist->seqno_event->seqno + 1, rt_hist->seqno_event->seqno, seqno_count, read_opt); if (res != -2) continue; } validate_path: print_rt_path_at_seqno(bat_node, rt_hist->seqno_event->orig, rt_hist->next_hop, rt_hist->seqno_event->seqno, seqno_count, read_opt); } } } } static void seqno_trace_print_neigh(struct seqno_trace_neigh *seqno_trace_neigh, struct seqno_event *seqno_event_parent, int num_sisters, char *head, int read_opt) { char new_head[MAX_LINE]; int i; printf("%s%s- %s [tq: %i, ttl: %i", head, (strlen(head) == 1 ? "" : num_sisters == 0 ? "\\" : "|"), get_name_by_macstr(seqno_trace_neigh->bat_node->name, read_opt), seqno_trace_neigh->seqno_event->tq, seqno_trace_neigh->seqno_event->ttl); printf(", neigh: %s", get_name_by_macstr(seqno_trace_neigh->seqno_event->neigh->name, read_opt)); printf(", prev_sender: %s]", get_name_by_macstr(seqno_trace_neigh->seqno_event->prev_sender->name, read_opt)); if (seqno_event_parent && seqno_trace_neigh->seqno_event->tq > seqno_event_parent->tq) printf(" TQ UP!\n"); else printf("\n"); for (i = 0; i < seqno_trace_neigh->num_neighbors; i++) { snprintf(new_head, sizeof(new_head), "%s%s", (strlen(head) > 1 ? head : num_sisters == 0 ? " " : head), (strlen(head) == 1 ? " " : num_sisters == 0 ? " " : "| ")); seqno_trace_print_neigh(seqno_trace_neigh->seqno_trace_neigh[i], seqno_trace_neigh->seqno_event, seqno_trace_neigh->num_neighbors - i - 1, new_head, read_opt); } } static void seqno_trace_print(struct list_head *trace_list, char *trace_orig, long long seqno_min, long long seqno_max, char *filter_orig, int read_opt) { struct seqno_trace *seqno_trace; char check_orig[NAME_LEN]; char head[MAX_LINE]; int i; /* if no option was given filter_orig is empty */ memset(check_orig, 0, NAME_LEN); printf("Sequence number flow of originator: %s ", get_name_by_macstr(trace_orig, read_opt)); if ((seqno_min == -1) && (seqno_max == -1)) printf("[all sequence numbers]"); else if (seqno_min == seqno_max) printf("[sequence number: %lli]", seqno_min); else printf("[sequence number range: %lli-%lli]", seqno_min, seqno_max); if (!compare_name(filter_orig, check_orig)) printf(" [filter originator: %s]", get_name_by_macstr(filter_orig, read_opt)); printf("\n"); list_for_each_entry(seqno_trace, trace_list, list) { if (!seqno_trace->print) continue; printf("+=> %s (seqno %lli)\n", get_name_by_macstr(trace_orig, read_opt), seqno_trace->seqno); for (i = 0; i < seqno_trace->seqno_trace_neigh.num_neighbors; i++) { snprintf(head, sizeof(head), "%c", seqno_trace->seqno_trace_neigh.num_neighbors == i + 1 ? '\\' : '|'); seqno_trace_print_neigh(seqno_trace->seqno_trace_neigh.seqno_trace_neigh[i], NULL, seqno_trace->seqno_trace_neigh.num_neighbors - i - 1, head, read_opt); } printf("\n"); } } static int _seqno_trace_neigh_add(struct seqno_trace_neigh *seqno_trace_mom, struct seqno_trace_neigh *seqno_trace_child) { struct seqno_trace_neigh **data_ptr; data_ptr = calloc(seqno_trace_mom->num_neighbors + 1, sizeof(*data_ptr)); if (!data_ptr) return 0; if (seqno_trace_mom->num_neighbors > 0) { memcpy(data_ptr, seqno_trace_mom->seqno_trace_neigh, seqno_trace_mom->num_neighbors * sizeof(struct seqno_trace_neigh *)); free(seqno_trace_mom->seqno_trace_neigh); } seqno_trace_mom->num_neighbors++; seqno_trace_mom->seqno_trace_neigh = data_ptr; seqno_trace_mom->seqno_trace_neigh[seqno_trace_mom->num_neighbors - 1] = seqno_trace_child; return 1; } static struct seqno_trace_neigh *seqno_trace_neigh_add(struct seqno_trace_neigh *seqno_trace_neigh, struct bat_node *bat_node, struct seqno_event *seqno_event) { struct seqno_trace_neigh *seqno_trace_neigh_new; int res; seqno_trace_neigh_new = malloc(sizeof(struct seqno_trace_neigh)); if (!seqno_trace_neigh_new) goto err; seqno_trace_neigh_new->bat_node = bat_node; seqno_trace_neigh_new->seqno_event = seqno_event; seqno_trace_neigh_new->num_neighbors = 0; res = _seqno_trace_neigh_add(seqno_trace_neigh, seqno_trace_neigh_new); if (res < 1) goto free_neigh; return seqno_trace_neigh_new; free_neigh: free(seqno_trace_neigh_new); err: return NULL; } static struct seqno_trace_neigh *seqno_trace_find_neigh(struct bat_node *neigh, struct bat_node *prev_sender, struct seqno_trace_neigh *seqno_trace_neigh) { struct seqno_trace_neigh *seqno_trace_neigh_tmp; struct seqno_trace_neigh *seqno_trace_neigh_ret; int i; for (i = 0; i < seqno_trace_neigh->num_neighbors; i++) { seqno_trace_neigh_tmp = seqno_trace_neigh->seqno_trace_neigh[i]; if (neigh == seqno_trace_neigh_tmp->bat_node && prev_sender == seqno_trace_neigh_tmp->seqno_event->neigh) return seqno_trace_neigh_tmp; seqno_trace_neigh_ret = seqno_trace_find_neigh(neigh, prev_sender, seqno_trace_neigh_tmp); if (seqno_trace_neigh_ret) return seqno_trace_neigh_ret; } return NULL; } static void seqno_trace_neigh_free(struct seqno_trace_neigh *seqno_trace_neigh) { int i; for (i = 0; i < seqno_trace_neigh->num_neighbors; i++) seqno_trace_neigh_free(seqno_trace_neigh->seqno_trace_neigh[i]); if (seqno_trace_neigh->num_neighbors > 0) free(seqno_trace_neigh->seqno_trace_neigh); free(seqno_trace_neigh); } static int seqno_trace_fix_leaf(struct seqno_trace_neigh *seqno_trace_mom, struct seqno_trace_neigh *seqno_trace_old_mom, struct seqno_trace_neigh *seqno_trace_child) { struct seqno_trace_neigh *seqno_trace_neigh; struct seqno_trace_neigh **data_ptr; int i, j = 0; data_ptr = calloc(seqno_trace_old_mom->num_neighbors - 1, sizeof(*data_ptr)); if (!data_ptr) return 0; /* copy all children except the child that is going to move */ for (i = 0; i < seqno_trace_old_mom->num_neighbors; i++) { seqno_trace_neigh = seqno_trace_old_mom->seqno_trace_neigh[i]; if (seqno_trace_neigh != seqno_trace_child) { data_ptr[j] = seqno_trace_neigh; j++; } } seqno_trace_old_mom->num_neighbors--; free(seqno_trace_old_mom->seqno_trace_neigh); seqno_trace_old_mom->seqno_trace_neigh = data_ptr; return _seqno_trace_neigh_add(seqno_trace_mom, seqno_trace_child); } static int seqno_trace_check_leaves(struct seqno_trace *seqno_trace, struct seqno_trace_neigh *seqno_trace_neigh_new) { struct seqno_trace_neigh *seqno_trace_neigh_tmp; int i, res; for (i = 0; i < seqno_trace->seqno_trace_neigh.num_neighbors; i++) { seqno_trace_neigh_tmp = seqno_trace->seqno_trace_neigh.seqno_trace_neigh[i]; if (seqno_trace_neigh_tmp->seqno_event->neigh == seqno_trace_neigh_new->bat_node && seqno_trace_neigh_tmp->seqno_event->prev_sender == seqno_trace_neigh_new->seqno_event->neigh) { res = seqno_trace_fix_leaf(seqno_trace_neigh_new, &seqno_trace->seqno_trace_neigh, seqno_trace_neigh_tmp); if (res < 1) return res; /* restart checking procedure because we just changed * the array we are working on */ return seqno_trace_check_leaves(seqno_trace, seqno_trace_neigh_new); } } return 1; } static struct seqno_trace *seqno_trace_new(struct seqno_event *seqno_event) { struct seqno_trace *seqno_trace; seqno_trace = malloc(sizeof(struct seqno_trace)); if (!seqno_trace) { fprintf(stderr, "Could not allocate memory for seqno tracing data (out of mem?)\n"); return NULL; } seqno_trace->seqno = seqno_event->seqno; seqno_trace->print = 0; seqno_trace->seqno_trace_neigh.num_neighbors = 0; return seqno_trace; } static void seqno_trace_free(struct seqno_trace *seqno_trace) { int i; for (i = 0; i < seqno_trace->seqno_trace_neigh.num_neighbors; i++) seqno_trace_neigh_free(seqno_trace->seqno_trace_neigh.seqno_trace_neigh[i]); free(seqno_trace); } static int seqno_trace_add(struct list_head *trace_list, struct bat_node *bat_node, struct seqno_event *seqno_event, char print_trace) { struct seqno_trace *seqno_trace_prev = NULL; struct seqno_trace_neigh *seqno_trace_neigh; struct seqno_trace *seqno_trace_tmp = NULL; struct seqno_trace *seqno_trace = NULL; list_for_each_entry(seqno_trace_tmp, trace_list, list) { if (seqno_trace_tmp->seqno == seqno_event->seqno) { seqno_trace = seqno_trace_tmp; break; } if (seqno_trace_tmp->seqno > seqno_event->seqno) break; seqno_trace_prev = seqno_trace_tmp; } if (!seqno_trace) { seqno_trace = seqno_trace_new(seqno_event); if (!seqno_trace) goto err; if (list_empty(trace_list) || seqno_event->seqno > list_last_entry(trace_list, struct seqno_trace, list)->seqno) list_add_tail(&seqno_trace->list, trace_list); else if (seqno_event->seqno < list_first_entry(trace_list, struct seqno_trace, list)->seqno) list_add(&seqno_trace->list, trace_list); else list_add_behind(&seqno_trace->list, &seqno_trace_prev->list); } if (print_trace) seqno_trace->print = print_trace; seqno_trace_neigh = seqno_trace_find_neigh(seqno_event->neigh, seqno_event->prev_sender, &seqno_trace->seqno_trace_neigh); /* no neighbor found to hook up to - adding new root node */ if (!seqno_trace_neigh) seqno_trace_neigh = seqno_trace_neigh_add(&seqno_trace->seqno_trace_neigh, bat_node, seqno_event); else seqno_trace_neigh = seqno_trace_neigh_add(seqno_trace_neigh, bat_node, seqno_event); if (seqno_trace_neigh) seqno_trace_check_leaves(seqno_trace, seqno_trace_neigh); return 1; err: return 0; } static void trace_seqnos(char *trace_orig, long long seqno_min, long long seqno_max, char *filter_orig, int read_opt) { struct seqno_trace *seqno_trace_tmp; struct seqno_trace *seqno_trace; struct seqno_event *seqno_event; struct hash_it_t *hashit = NULL; struct orig_event *orig_event; struct list_head trace_list; struct bat_node *bat_node; char check_orig[NAME_LEN]; char print_trace; int res; /* if no option was given filter_orig is empty */ memset(check_orig, 0, NAME_LEN); INIT_LIST_HEAD(&trace_list); while (NULL != (hashit = hash_iterate(node_hash, hashit))) { bat_node = hashit->bucket->data; list_for_each_entry(orig_event, &bat_node->orig_event_list, list) { /* we might have no log file from this node */ if (list_empty(&orig_event->event_list)) continue; list_for_each_entry(seqno_event, &orig_event->event_list, list) { /* special seqno that indicates an originator timeout */ if (seqno_event->seqno == -1) continue; if (!compare_name(trace_orig, seqno_event->orig->name)) continue; if (seqno_min != -1 && seqno_event->seqno < seqno_min) continue; if (seqno_max != -1 && seqno_event->seqno > seqno_max) continue; /* if no filter option was given all seqno * traces are to be printed */ print_trace = compare_name(filter_orig, check_orig); if (!compare_name(filter_orig, check_orig) && compare_name(filter_orig, bat_node->name)) print_trace = 1; res = seqno_trace_add(&trace_list, bat_node, seqno_event, print_trace); if (res < 1) { hash_iterate_free(hashit); goto out; } } } } seqno_trace_print(&trace_list, trace_orig, seqno_min, seqno_max, filter_orig, read_opt); out: list_for_each_entry_safe(seqno_trace, seqno_trace_tmp, &trace_list, list) { list_del(&seqno_trace->list); seqno_trace_free(seqno_trace); } } static void print_rt_tables(char *rt_orig, long long seqno_min, long long seqno_max, char *filter_orig, int read_opt) { struct seqno_event *seqno_event; struct bat_node *bat_node; struct rt_table *rt_table; char check_orig[NAME_LEN]; int i; /* if no option was given filter_orig is empty */ memset(check_orig, 0, NAME_LEN); printf("Routing tables of originator: %s ", get_name_by_macstr(rt_orig, read_opt)); if ((seqno_min == -1) && (seqno_max == -1)) printf("[all sequence numbers]"); else if (seqno_min == seqno_max) printf("[sequence number: %lli]", seqno_min); else printf("[sequence number range: %lli-%lli]", seqno_min, seqno_max); if (!compare_name(filter_orig, check_orig)) printf(" [filter originator: %s]", get_name_by_macstr(filter_orig, read_opt)); printf("\n"); bat_node = node_get(rt_orig); if (!bat_node) goto out; /* we might have no log file from this node */ if (list_empty(&bat_node->rt_table_list)) goto out; list_for_each_entry(rt_table, &bat_node->rt_table_list, list) { seqno_event = rt_table->rt_hist->seqno_event; if (!compare_name(filter_orig, check_orig) && !compare_name(filter_orig, seqno_event->orig->name)) continue; if (seqno_min != -1 && seqno_event->seqno < seqno_min) continue; if (seqno_max != -1 && seqno_event->seqno > seqno_max) continue; if (seqno_event->seqno > -1) { printf("rt change triggered by OGM from: %s (tq: %i, ttl: %i, seqno %lli", get_name_by_macstr(seqno_event->orig->name, read_opt), seqno_event->tq, seqno_event->ttl, seqno_event->seqno); printf(", neigh: %s", get_name_by_macstr(seqno_event->neigh->name, read_opt)); printf(", prev_sender: %s)\n", get_name_by_macstr(seqno_event->prev_sender->name, read_opt)); } else { printf("rt change triggered by originator timeout:\n"); } for (i = 0; i < rt_table->num_entries; i++) { printf("%s %s via next hop", (rt_table->entries[i].flags ? " *" : " "), get_name_by_macstr(rt_table->entries[i].orig, read_opt)); printf(" %s", get_name_by_macstr(rt_table->entries[i].next_hop->name, read_opt)); switch (rt_table->entries[i].flags) { case RT_FLAG_ADD: printf(" (route added)\n"); break; case RT_FLAG_UPDATE: printf(" (next hop changed)\n"); break; case RT_FLAG_DELETE: printf(" (route deleted)\n"); break; default: printf("\n"); break; } } printf("\n"); } out: return; } static int get_orig_addr(char *orig_name, char *orig_addr) { char *orig_name_tmp = orig_name; struct ether_addr *orig_mac; struct bat_host *bat_host; bat_host = bat_hosts_find_by_name(orig_name_tmp); if (bat_host) { orig_name_tmp = ether_ntoa_long((struct ether_addr *)&bat_host->mac_addr); goto copy_name; } orig_mac = ether_aton(orig_name_tmp); if (!orig_mac) { fprintf(stderr, "Error - the originator is not a mac address or bat-host name: %s\n", orig_name); goto err; } /* convert the given mac address to the long format to * make sure we can find it */ orig_name_tmp = ether_ntoa_long(orig_mac); copy_name: strncpy(orig_addr, orig_name_tmp, NAME_LEN); orig_addr[NAME_LEN - 1] = '\0'; return 1; err: return 0; } static int bisect_iv(struct state *state __maybe_unused, int argc, char **argv) { int read_opt = USE_BAT_HOSTS; char *filter_orig_ptr = NULL; char *trace_orig_ptr = NULL; char *loop_orig_ptr = NULL; char filter_orig[NAME_LEN]; char *rt_orig_ptr = NULL; long long seqno_max = -1; long long seqno_min = -1; int ret = EXIT_FAILURE; int num_parsed_files; long long tmp_seqno; char orig[NAME_LEN]; int found_args = 1; char *dash_ptr; int optchar; int res; memset(orig, 0, NAME_LEN); memset(filter_orig, 0, NAME_LEN); while ((optchar = getopt(argc, argv, "hl:no:r:s:t:")) != -1) { switch (optchar) { case 'h': bisect_iv_usage(); return EXIT_SUCCESS; case 'l': loop_orig_ptr = optarg; found_args += ((*((char *)(optarg - 1)) == optchar) ? 1 : 2); break; case 'n': read_opt &= ~USE_BAT_HOSTS; found_args += 1; break; case 'o': filter_orig_ptr = optarg; found_args += ((*((char *)(optarg - 1)) == optchar) ? 1 : 2); break; case 'r': rt_orig_ptr = optarg; found_args += ((*((char *)(optarg - 1)) == optchar) ? 1 : 2); break; case 's': dash_ptr = strchr(optarg, '-'); if (dash_ptr) *dash_ptr = 0; tmp_seqno = strtol(optarg, NULL, 10); if (tmp_seqno >= 0 && tmp_seqno <= UINT32_MAX) seqno_min = tmp_seqno; else fprintf(stderr, "Warning - given sequence number is out of range: %lli\n", tmp_seqno); if (dash_ptr) { tmp_seqno = strtol(dash_ptr + 1, NULL, 10); if (tmp_seqno >= 0 && tmp_seqno <= UINT32_MAX) seqno_max = tmp_seqno; else fprintf(stderr, "Warning - given sequence number is out of range: %lli\n", tmp_seqno); *dash_ptr = '-'; } found_args += ((*((char *)(optarg - 1)) == optchar) ? 1 : 2); break; case 't': trace_orig_ptr = optarg; found_args += ((*((char *)(optarg - 1)) == optchar) ? 1 : 2); break; default: bisect_iv_usage(); return EXIT_FAILURE; } } if (argc <= found_args + 1) { fprintf(stderr, "Error - need at least 2 log files to compare\n"); bisect_iv_usage(); goto err; } node_hash = hash_new(64, compare_name, choose_name); if (!node_hash) { fprintf(stderr, "Error - could not create node hash table\n"); goto err; } bat_hosts_init(read_opt); num_parsed_files = 0; if ((rt_orig_ptr) && (trace_orig_ptr)) { fprintf(stderr, "Error - the 'print routing table' option can't be used together with the 'trace seqno' option\n"); goto err; } else if ((loop_orig_ptr) && (trace_orig_ptr)) { fprintf(stderr, "Error - the 'loop detection' option can't be used together with the 'trace seqno' option\n"); goto err; } else if ((loop_orig_ptr) && (rt_orig_ptr)) { fprintf(stderr, "Error - the 'loop detection' option can't be used together with the 'print routing table' option\n"); goto err; } else if (rt_orig_ptr) { res = get_orig_addr(rt_orig_ptr, orig); if (res < 1) goto err; } else if (trace_orig_ptr) { res = get_orig_addr(trace_orig_ptr, orig); if (res < 1) goto err; } else if (loop_orig_ptr) { res = get_orig_addr(loop_orig_ptr, orig); if (res < 1) goto err; } /* we search a specific seqno - no range */ if (seqno_min > 0 && seqno_max == -1) seqno_max = seqno_min; if (seqno_min > seqno_max) { fprintf(stderr, "Error - the sequence range minimum (%lli) should be smaller than the maximum (%lli)\n", seqno_min, seqno_max); goto err; } if (filter_orig_ptr) { res = get_orig_addr(filter_orig_ptr, filter_orig); if (res < 1) goto err; } while (argc > found_args) { res = parse_log_file(argv[found_args]); if (res > 0) num_parsed_files++; found_args++; } if (num_parsed_files < 2) { fprintf(stderr, "Error - need at least 2 log files to compare\n"); goto err; } if (trace_orig_ptr) trace_seqnos(orig, seqno_min, seqno_max, filter_orig, read_opt); else if (rt_orig_ptr) print_rt_tables(orig, seqno_min, seqno_max, filter_orig, read_opt); else loop_detection(orig, seqno_min, seqno_max, filter_orig, read_opt); ret = EXIT_SUCCESS; err: if (node_hash) hash_delete(node_hash, node_free); bat_hosts_free(); return ret; } COMMAND(SUBCOMMAND, bisect_iv, "bisect_iv", 0, NULL, " .. \tanalyze given batman iv log files for routing stability"); batctl-2025.1/bisect_iv.h000066400000000000000000000031751500012142700151720ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #ifndef _BATCTL_BISECT_IV_H #define _BATCTL_BISECT_IV_H #include "list.h" #define NAME_LEN 18 #define MAX_LINE 256 #define LOOP_MAGIC_LEN ((2 * NAME_LEN) + (2 * sizeof(int)) - 2) #define RT_FLAG_ADD 1 #define RT_FLAG_UPDATE 2 #define RT_FLAG_DELETE 3 struct bat_node { char name[NAME_LEN]; struct list_head orig_event_list; struct list_head rt_table_list; char loop_magic[LOOP_MAGIC_LEN]; char loop_magic2[LOOP_MAGIC_LEN]; }; struct orig_event { struct list_head list; struct bat_node *orig_node; struct list_head event_list; struct list_head rt_hist_list; }; struct rt_table { struct list_head list; int num_entries; struct rt_entry *entries; struct rt_hist *rt_hist; }; struct rt_hist { struct list_head list; struct rt_table *rt_table; struct rt_hist *prev_rt_hist; struct seqno_event *seqno_event; struct bat_node *next_hop; char flags; char loop_magic[LOOP_MAGIC_LEN]; }; struct rt_entry { char orig[NAME_LEN]; struct bat_node *next_hop; char flags; }; struct seqno_event { struct list_head list; struct bat_node *orig; struct bat_node *neigh; struct bat_node *prev_sender; long long seqno; int tq; int ttl; struct rt_hist *rt_hist; }; struct seqno_trace_neigh { struct bat_node *bat_node; struct seqno_event *seqno_event; int num_neighbors; struct seqno_trace_neigh **seqno_trace_neigh; }; struct seqno_trace { struct list_head list; long long seqno; char print; struct seqno_trace_neigh seqno_trace_neigh; }; #endif batctl-2025.1/bla_backbone_json.c000066400000000000000000000010111500012142700166140ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Sven Eckelmann * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include "main.h" #include #include #include #include "batman_adv.h" #include "netlink.h" #include "sys.h" static struct simple_boolean_data bonding; static int print_bonding(struct nl_msg *msg, void *arg) { return sys_simple_print_boolean(msg, arg, BATADV_ATTR_BONDING_ENABLED); } static int get_bonding(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_GET_MESH, NULL, print_bonding); } static int set_attrs_bonding(struct nl_msg *msg, void *arg) { struct state *state = arg; struct settings_data *settings = state->cmd->arg; struct simple_boolean_data *data = settings->data; nla_put_u8(msg, BATADV_ATTR_BONDING_ENABLED, data->val); return 0; } static int set_bonding(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_SET_MESH, set_attrs_bonding, NULL); } static struct settings_data batctl_settings_bonding = { .data = &bonding, .parse = parse_simple_boolean, .netlink_get = get_bonding, .netlink_set = set_bonding, }; COMMAND_NAMED(SUBCOMMAND_MIF, bonding, "b", handle_sys_setting, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_settings_bonding, "[0|1] \tdisplay or modify bonding setting"); batctl-2025.1/bridge_loop_avoidance.c000066400000000000000000000032121500012142700175040ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Simon Wunderlich * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include "main.h" #include #include #include #include "batman_adv.h" #include "netlink.h" #include "sys.h" static struct simple_boolean_data bridge_loop_avoidance; static int print_bridge_loop_avoidance(struct nl_msg *msg, void *arg) { return sys_simple_print_boolean(msg, arg, BATADV_ATTR_BRIDGE_LOOP_AVOIDANCE_ENABLED); } static int get_bridge_loop_avoidance(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_GET_MESH, NULL, print_bridge_loop_avoidance); } static int set_attrs_bridge_loop_avoidance(struct nl_msg *msg, void *arg) { struct state *state = arg; struct settings_data *settings = state->cmd->arg; struct simple_boolean_data *data = settings->data; nla_put_u8(msg, BATADV_ATTR_BRIDGE_LOOP_AVOIDANCE_ENABLED, data->val); return 0; } static int set_bridge_loop_avoidance(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_SET_MESH, set_attrs_bridge_loop_avoidance, NULL); } static struct settings_data batctl_settings_bridge_loop_avoidance = { .data = &bridge_loop_avoidance, .parse = parse_simple_boolean, .netlink_get = get_bridge_loop_avoidance, .netlink_set = set_bridge_loop_avoidance, }; COMMAND_NAMED(SUBCOMMAND_MIF, bridge_loop_avoidance, "bl", handle_sys_setting, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_settings_bridge_loop_avoidance, "[0|1] \tdisplay or modify bridge_loop_avoidance setting"); batctl-2025.1/claimtable.c000066400000000000000000000061031500012142700153050ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Linus Lüssing * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include #include "batadv_packet_compat.h" #include "batman_adv.h" #include "bat-hosts.h" #include "debug.h" #include "functions.h" #include "main.h" #include "netlink.h" static const int bla_claim_mandatory[] = { BATADV_ATTR_BLA_ADDRESS, BATADV_ATTR_BLA_VID, BATADV_ATTR_BLA_BACKBONE, BATADV_ATTR_BLA_CRC, }; static int bla_claim_callback(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct print_opts *opts = arg; struct bat_host *bat_host; struct genlmsghdr *ghdr; uint16_t backbone_crc; uint8_t *backbone; uint8_t *client; uint16_t vid; char c = ' '; if (!genlmsg_valid_hdr(nlh, 0)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } ghdr = nlmsg_data(nlh); if (ghdr->cmd != BATADV_CMD_GET_BLA_CLAIM) return NL_OK; if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } if (missing_mandatory_attrs(attrs, bla_claim_mandatory, ARRAY_SIZE(bla_claim_mandatory))) { fputs("Missing attributes from kernel\n", stderr); exit(1); } if (attrs[BATADV_ATTR_BLA_OWN]) c = '*'; client = nla_data(attrs[BATADV_ATTR_BLA_ADDRESS]); vid = nla_get_u16(attrs[BATADV_ATTR_BLA_VID]); backbone = nla_data(attrs[BATADV_ATTR_BLA_BACKBONE]); backbone_crc = nla_get_u16(attrs[BATADV_ATTR_BLA_CRC]); bat_host = bat_hosts_find_by_mac((char *)client); if (!(opts->read_opt & USE_BAT_HOSTS) || !bat_host) printf("%02x:%02x:%02x:%02x:%02x:%02x ", client[0], client[1], client[2], client[3], client[4], client[5]); else printf("%17s ", bat_host->name); printf("on %5d by ", BATADV_PRINT_VID(vid)); bat_host = bat_hosts_find_by_mac((char *)backbone); if (!(opts->read_opt & USE_BAT_HOSTS) || !bat_host) printf("%02x:%02x:%02x:%02x:%02x:%02x ", backbone[0], backbone[1], backbone[2], backbone[3], backbone[4], backbone[5]); else printf("%17s ", bat_host->name); printf("[%c] (0x%04x)\n", c, backbone_crc); return NL_OK; } static int netlink_print_bla_claim(struct state *state, char *orig_iface, int read_opts, float orig_timeout, float watch_interval) { return netlink_print_common(state, orig_iface, read_opts, orig_timeout, watch_interval, "Client VID Originator [o] (CRC )\n", BATADV_CMD_GET_BLA_CLAIM, bla_claim_callback); } static struct debug_table_data batctl_debug_table_claimtable = { .netlink_fn = netlink_print_bla_claim, }; COMMAND_NAMED(DEBUGTABLE, claimtable, "cl", handle_debug_table, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_debug_table_claimtable, ""); batctl-2025.1/dat_cache.c000066400000000000000000000065711500012142700151140ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include #include #include #include #include "batman_adv.h" #include "bat-hosts.h" #include "debug.h" #include "functions.h" #include "main.h" #include "netlink.h" static const int dat_cache_mandatory[] = { BATADV_ATTR_DAT_CACHE_IP4ADDRESS, BATADV_ATTR_DAT_CACHE_HWADDRESS, BATADV_ATTR_DAT_CACHE_VID, BATADV_ATTR_LAST_SEEN_MSECS, }; static int dat_cache_callback(struct nl_msg *msg, void *arg) { int last_seen_msecs, last_seen_secs, last_seen_mins; struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct print_opts *opts = arg; struct bat_host *bat_host; struct genlmsghdr *ghdr; struct in_addr in_addr; uint8_t *hwaddr; int16_t vid; char *addr; if (!genlmsg_valid_hdr(nlh, 0)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } ghdr = nlmsg_data(nlh); if (ghdr->cmd != BATADV_CMD_GET_DAT_CACHE) return NL_OK; if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } if (missing_mandatory_attrs(attrs, dat_cache_mandatory, ARRAY_SIZE(dat_cache_mandatory))) { fputs("Missing attributes from kernel\n", stderr); exit(1); } in_addr.s_addr = nla_get_u32(attrs[BATADV_ATTR_DAT_CACHE_IP4ADDRESS]); addr = inet_ntoa(in_addr); hwaddr = nla_data(attrs[BATADV_ATTR_DAT_CACHE_HWADDRESS]); vid = nla_get_u16(attrs[BATADV_ATTR_DAT_CACHE_VID]); last_seen_msecs = nla_get_u32(attrs[BATADV_ATTR_LAST_SEEN_MSECS]); last_seen_mins = last_seen_msecs / 60000; last_seen_msecs = last_seen_msecs % 60000; last_seen_secs = last_seen_msecs / 1000; if (opts->read_opt & MULTICAST_ONLY && !(addr[0] & 0x01)) return NL_OK; if (opts->read_opt & UNICAST_ONLY && (addr[0] & 0x01)) return NL_OK; printf(" * %15s ", addr); bat_host = bat_hosts_find_by_mac((char *)hwaddr); if (!(opts->read_opt & USE_BAT_HOSTS) || !bat_host) printf("%02x:%02x:%02x:%02x:%02x:%02x ", hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5]); else printf("%17s ", bat_host->name); printf("%4i %6i:%02i\n", BATADV_PRINT_VID(vid), last_seen_mins, last_seen_secs); return NL_OK; } static int netlink_print_dat_cache(struct state *state, char *orig_iface, int read_opts, float orig_timeout, float watch_interval) { char *header; int ret; ret = asprintf(&header, "Distributed ARP Table (%s):\n%s\n", state->mesh_iface, " IPv4 MAC VID last-seen"); if (ret < 0) return ret; ret = netlink_print_common(state, orig_iface, read_opts, orig_timeout, watch_interval, header, BATADV_CMD_GET_DAT_CACHE, dat_cache_callback); free(header); return ret; } static struct debug_table_data batctl_debug_table_dat_cache = { .netlink_fn = netlink_print_dat_cache, }; COMMAND_NAMED(DEBUGTABLE, dat_cache, "dc", handle_debug_table, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_debug_table_dat_cache, ""); batctl-2025.1/dat_cache_json.c000066400000000000000000000007751500012142700161450ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Sven Eckelmann * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include "debug.h" #include "functions.h" #include "netlink.h" #include "sys.h" static void debug_table_usage(struct state *state) { struct debug_table_data *debug_table = state->cmd->arg; fprintf(stderr, "Usage: batctl [options] %s|%s [parameters]\n", state->cmd->name, state->cmd->abbr); fprintf(stderr, "parameters:\n"); fprintf(stderr, " \t -h print this help\n"); fprintf(stderr, " \t -n don't replace mac addresses with bat-host names\n"); fprintf(stderr, " \t -H don't show the header\n"); fprintf(stderr, " \t -w [interval] watch mode - refresh the table continuously\n"); if (debug_table->option_timeout_interval) fprintf(stderr, " \t -t timeout interval - don't print originators not seen for x.y seconds\n"); if (debug_table->option_orig_iface) fprintf(stderr, " \t -i [interface] - show multiif originator table for a specific interface\n"); if (debug_table->option_unicast_only) fprintf(stderr, " \t -u print unicast mac addresses only\n"); if (debug_table->option_multicast_only) fprintf(stderr, " \t -m print multicast mac addresses only\n"); } int handle_debug_table(struct state *state, int argc, char **argv) { struct debug_table_data *debug_table = state->cmd->arg; int read_opt = USE_BAT_HOSTS; float orig_timeout = 0.0f; float watch_interval = 1; char *orig_iface = NULL; int optchar; int err; while ((optchar = getopt(argc, argv, "hnw:t:Humi:")) != -1) { switch (optchar) { case 'h': debug_table_usage(state); return EXIT_SUCCESS; case 'n': read_opt &= ~USE_BAT_HOSTS; break; case 'w': read_opt |= CLR_CONT_READ; if (optarg[0] == '-') { optind--; break; } if (!sscanf(optarg, "%f", &watch_interval)) { fprintf(stderr, "Error - provided argument of '-%c' is not a number\n", optchar); return EXIT_FAILURE; } break; case 't': if (!debug_table->option_timeout_interval) { fprintf(stderr, "Error - unrecognised option '-%c'\n", optchar); debug_table_usage(state); return EXIT_FAILURE; } read_opt |= NO_OLD_ORIGS; if (!sscanf(optarg, "%f", &orig_timeout)) { fprintf(stderr, "Error - provided argument of '-%c' is not a number\n", optchar); return EXIT_FAILURE; } break; case 'H': read_opt |= SKIP_HEADER; break; case 'u': if (!debug_table->option_unicast_only) { fprintf(stderr, "Error - unrecognised option '-%c'\n", optchar); debug_table_usage(state); return EXIT_FAILURE; } read_opt |= UNICAST_ONLY; break; case 'm': if (!debug_table->option_multicast_only) { fprintf(stderr, "Error - unrecognised option '-%c'\n", optchar); debug_table_usage(state); return EXIT_FAILURE; } read_opt |= MULTICAST_ONLY; break; case 'i': if (!debug_table->option_orig_iface) { fprintf(stderr, "Error - unrecognised option '-%c'\n", optchar); debug_table_usage(state); return EXIT_FAILURE; } if (check_mesh_iface_ownership(state, optarg) != EXIT_SUCCESS) return EXIT_FAILURE; orig_iface = optarg; break; case '?': if (optopt == 't') { fprintf(stderr, "Error - option '-t' needs a number as argument\n"); } else if (optopt == 'i') { fprintf(stderr, "Error - option '-i' needs an interface as argument\n"); } else if (optopt == 'w') { read_opt |= CLR_CONT_READ; break; } else { fprintf(stderr, "Error - unrecognised option: '-%c'\n", optopt); } return EXIT_FAILURE; default: debug_table_usage(state); return EXIT_FAILURE; } } if (read_opt & UNICAST_ONLY && read_opt & MULTICAST_ONLY) { fprintf(stderr, "Error - '-u' and '-m' are exclusive options\n"); debug_table_usage(state); return EXIT_FAILURE; } err = debug_table->netlink_fn(state, orig_iface, read_opt, orig_timeout, watch_interval); return err; } batctl-2025.1/debug.h000066400000000000000000000012151500012142700143020ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #ifndef _BATCTL_DEBUG_H #define _BATCTL_DEBUG_H #include #include "main.h" struct debug_table_data { int (*netlink_fn)(struct state *state, char *hard_iface, int read_opt, float orig_timeout, float watch_interval); unsigned int option_unicast_only:1; unsigned int option_multicast_only:1; unsigned int option_timeout_interval:1; unsigned int option_orig_iface:1; }; int handle_debug_table(struct state *state, int argc, char **argv); #endif batctl-2025.1/distributed_arp_table.c000066400000000000000000000032041500012142700175420ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Antonio Quartulli * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include "main.h" #include #include #include #include "batman_adv.h" #include "netlink.h" #include "sys.h" static struct simple_boolean_data distributed_arp_table; static int print_distributed_arp_table(struct nl_msg *msg, void *arg) { return sys_simple_print_boolean(msg, arg, BATADV_ATTR_DISTRIBUTED_ARP_TABLE_ENABLED); } static int get_distributed_arp_table(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_GET_MESH, NULL, print_distributed_arp_table); } static int set_attrs_distributed_arp_table(struct nl_msg *msg, void *arg) { struct state *state = arg; struct settings_data *settings = state->cmd->arg; struct simple_boolean_data *data = settings->data; nla_put_u8(msg, BATADV_ATTR_DISTRIBUTED_ARP_TABLE_ENABLED, data->val); return 0; } static int set_distributed_arp_table(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_SET_MESH, set_attrs_distributed_arp_table, NULL); } static struct settings_data batctl_settings_distributed_arp_table = { .data = &distributed_arp_table, .parse = parse_simple_boolean, .netlink_get = get_distributed_arp_table, .netlink_set = set_distributed_arp_table, }; COMMAND_NAMED(SUBCOMMAND_MIF, distributed_arp_table, "dat", handle_sys_setting, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_settings_distributed_arp_table, "[0|1] \tdisplay or modify distributed_arp_table setting"); batctl-2025.1/elp_interval.c000066400000000000000000000051701500012142700156770ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include "main.h" #include "sys.h" static struct elp_interval_data { uint32_t elp_interval; } elp_interval; static int parse_elp_interval(struct state *state, int argc, char *argv[]) { struct settings_data *settings = state->cmd->arg; struct elp_interval_data *data = settings->data; char *endptr; if (argc != 2) { fprintf(stderr, "Error - incorrect number of arguments (expected 1)\n"); return -EINVAL; } data->elp_interval = strtoul(argv[1], &endptr, 0); if (!endptr || *endptr != '\0') { fprintf(stderr, "Error - the supplied argument is invalid: %s\n", argv[1]); return -EINVAL; } return 0; } static int print_elp_interval(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct genlmsghdr *ghdr; int *result = arg; if (!genlmsg_valid_hdr(nlh, 0)) return NL_OK; ghdr = nlmsg_data(nlh); if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { return NL_OK; } if (!attrs[BATADV_ATTR_ELP_INTERVAL]) return NL_OK; printf("%u\n", nla_get_u32(attrs[BATADV_ATTR_ELP_INTERVAL])); *result = 0; return NL_STOP; } static int get_attrs_elp_interval(struct nl_msg *msg, void *arg) { struct state *state = arg; nla_put_u32(msg, BATADV_ATTR_HARD_IFINDEX, state->hif); return 0; } static int get_elp_interval(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_GET_HARDIF, get_attrs_elp_interval, print_elp_interval); } static int set_attrs_elp_interval(struct nl_msg *msg, void *arg) { struct state *state = arg; struct settings_data *settings = state->cmd->arg; struct elp_interval_data *data = settings->data; nla_put_u32(msg, BATADV_ATTR_HARD_IFINDEX, state->hif); nla_put_u32(msg, BATADV_ATTR_ELP_INTERVAL, data->elp_interval); return 0; } static int set_elp_interval(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_SET_HARDIF, set_attrs_elp_interval, NULL); } static struct settings_data batctl_settings_elp_interval = { .data = &elp_interval, .parse = parse_elp_interval, .netlink_get = get_elp_interval, .netlink_set = set_elp_interval, }; COMMAND_NAMED(SUBCOMMAND_HIF, elp_interval, "et", handle_sys_setting, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_settings_elp_interval, "[interval] \tdisplay or modify elp_interval setting"); batctl-2025.1/event.c000066400000000000000000000305201500012142700143310ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Sven Eckelmann * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include "batman_adv.h" #include "bat-hosts.h" #include "debug.h" #include "functions.h" #include "genl.h" #include "main.h" #include "netlink.h" enum event_time_mode { EVENT_TIME_NO, EVENT_TIME_LOCAL, EVENT_TIME_RELATIVE, }; struct event_args { enum event_time_mode mode; struct timeval tv; }; static const char *u8_to_boolstr(struct nlattr *attrs) { if (nla_get_u8(attrs)) return "true"; else return "false"; } static void event_usage(void) { fprintf(stderr, "Usage: batctl [options] event [parameters]\n"); fprintf(stderr, "parameters:\n"); fprintf(stderr, " \t -h print this help\n"); fprintf(stderr, " \t -t print local timestamp\n"); fprintf(stderr, " \t -r print relative timestamp\n"); } static int event_prepare(struct state *state) { int mcid; int ret; if (!state->sock) return -EOPNOTSUPP; mcid = nl_get_multicast_id(state->sock, BATADV_NL_NAME, BATADV_NL_MCAST_GROUP_TPMETER); if (mcid < 0) { fprintf(stderr, "Failed to resolve batadv tp_meter multicast group: %d\n", mcid); /* ignore error for now */ goto skip_tp_meter; } ret = nl_socket_add_membership(state->sock, mcid); if (ret) { fprintf(stderr, "Failed to join batadv tp_meter multicast group: %d\n", ret); /* ignore error for now */ goto skip_tp_meter; } skip_tp_meter: mcid = nl_get_multicast_id(state->sock, BATADV_NL_NAME, BATADV_NL_MCAST_GROUP_CONFIG); if (mcid < 0) { fprintf(stderr, "Failed to resolve batadv config multicast group: %d\n", mcid); /* ignore error for now */ goto skip_config; } ret = nl_socket_add_membership(state->sock, mcid); if (ret) { fprintf(stderr, "Failed to join batadv config multicast group: %d\n", ret); /* ignore error for now */ goto skip_config; } skip_config: return 0; } static int no_seq_check(struct nl_msg *msg __maybe_unused, void *arg __maybe_unused) { return NL_OK; } static const int tp_meter_mandatory[] = { BATADV_ATTR_TPMETER_COOKIE, BATADV_ATTR_TPMETER_RESULT, }; static void event_parse_tp_meter(struct nlattr **attrs) { const char *result_str; uint32_t cookie; uint8_t result; /* ignore entry when attributes are missing */ if (missing_mandatory_attrs(attrs, tp_meter_mandatory, ARRAY_SIZE(tp_meter_mandatory))) return; cookie = nla_get_u32(attrs[BATADV_ATTR_TPMETER_COOKIE]); result = nla_get_u8(attrs[BATADV_ATTR_TPMETER_RESULT]); switch (result) { case BATADV_TP_REASON_DST_UNREACHABLE: result_str = "Destination unreachable"; break; case BATADV_TP_REASON_RESEND_LIMIT: result_str = "The number of retry for the same window exceeds the limit, test aborted"; break; case BATADV_TP_REASON_ALREADY_ONGOING: result_str = "Cannot run two test towards the same node"; break; case BATADV_TP_REASON_MEMORY_ERROR: result_str = "Kernel cannot allocate memory, aborted"; break; case BATADV_TP_REASON_TOO_MANY: result_str = "Too many ongoing sessions"; break; case BATADV_TP_REASON_CANCEL: result_str = "CANCEL received: test aborted"; break; case BATADV_TP_REASON_COMPLETE: result_str = "complete"; break; default: result_str = "unknown"; break; } printf("tp_meter 0x%08x: %s\n", cookie, result_str); } static void event_parse_set_mesh(struct nlattr **attrs) { static const int mesh_mandatory[] = { BATADV_ATTR_MESH_IFINDEX, BATADV_ATTR_ALGO_NAME, }; char meshif_buf[IF_NAMESIZE]; uint32_t mesh_ifindex; char *meshif_name; /* ignore entry when attributes are missing */ if (missing_mandatory_attrs(attrs, mesh_mandatory, ARRAY_SIZE(mesh_mandatory))) return; if (attrs[BATADV_ATTR_MESH_IFNAME]) { meshif_name = nla_get_string(attrs[BATADV_ATTR_MESH_IFNAME]); } else { /* compatibility for Linux < 5.14/batman-adv < 2021.2 */ mesh_ifindex = nla_get_u32(attrs[BATADV_ATTR_MESH_IFINDEX]); meshif_name = if_indextoname(mesh_ifindex, meshif_buf); if (!meshif_name) return; } printf("%s: set mesh:\n", meshif_name); if (attrs[BATADV_ATTR_AGGREGATED_OGMS_ENABLED]) printf("* aggregated_ogms %s\n", u8_to_boolstr(attrs[BATADV_ATTR_AGGREGATED_OGMS_ENABLED])); if (attrs[BATADV_ATTR_AP_ISOLATION_ENABLED]) printf("* ap_isolation %s\n", u8_to_boolstr(attrs[BATADV_ATTR_AP_ISOLATION_ENABLED])); if (attrs[BATADV_ATTR_ISOLATION_MARK]) printf("* isolation_mark 0x%08x\n", nla_get_u32(attrs[BATADV_ATTR_ISOLATION_MARK])); if (attrs[BATADV_ATTR_ISOLATION_MASK]) printf("* isolation_mask 0x%08x\n", nla_get_u32(attrs[BATADV_ATTR_ISOLATION_MASK])); if (attrs[BATADV_ATTR_BONDING_ENABLED]) printf("* bonding %s\n", u8_to_boolstr(attrs[BATADV_ATTR_BONDING_ENABLED])); if (attrs[BATADV_ATTR_BRIDGE_LOOP_AVOIDANCE_ENABLED]) printf("* bridge_loop_avoidance %s\n", u8_to_boolstr(attrs[BATADV_ATTR_BRIDGE_LOOP_AVOIDANCE_ENABLED])); if (attrs[BATADV_ATTR_DISTRIBUTED_ARP_TABLE_ENABLED]) printf("* distributed_arp_table %s\n", u8_to_boolstr(attrs[BATADV_ATTR_DISTRIBUTED_ARP_TABLE_ENABLED])); if (attrs[BATADV_ATTR_FRAGMENTATION_ENABLED]) printf("* fragmentation %s\n", u8_to_boolstr(attrs[BATADV_ATTR_FRAGMENTATION_ENABLED])); if (attrs[BATADV_ATTR_GW_BANDWIDTH_DOWN]) { uint32_t val; val = nla_get_u32(attrs[BATADV_ATTR_GW_BANDWIDTH_DOWN]); printf("* gw_bandwidth_down %u.%01u MBit/s\n", val / 10, val % 10); } if (attrs[BATADV_ATTR_GW_BANDWIDTH_UP]) { uint32_t val; val = nla_get_u32(attrs[BATADV_ATTR_GW_BANDWIDTH_UP]); printf("* gw_bandwidth_up %u.%01u MBit/s\n", val / 10, val % 10); } if (attrs[BATADV_ATTR_GW_MODE]) { uint8_t val = nla_get_u8(attrs[BATADV_ATTR_GW_MODE]); const char *valstr; switch (val) { case BATADV_GW_MODE_OFF: valstr = "off"; break; case BATADV_GW_MODE_CLIENT: valstr = "client"; break; case BATADV_GW_MODE_SERVER: valstr = "server"; break; default: valstr = "unknown"; break; } printf("* gw_mode %s\n", valstr); } if (attrs[BATADV_ATTR_GW_SEL_CLASS]) { uint32_t val = nla_get_u32(attrs[BATADV_ATTR_GW_SEL_CLASS]); const char *algo = nla_data(attrs[BATADV_ATTR_ALGO_NAME]); if (strcmp(algo, "BATMAN_V") == 0) printf("* gw_sel_class %u.%01u MBit/s\n", val / 10, val % 10); else printf("* gw_sel_class %u\n", val); } if (attrs[BATADV_ATTR_HOP_PENALTY]) printf("* hop_penalty %u\n", nla_get_u8(attrs[BATADV_ATTR_HOP_PENALTY])); if (attrs[BATADV_ATTR_LOG_LEVEL]) printf("* log_level 0x%08x\n", nla_get_u32(attrs[BATADV_ATTR_LOG_LEVEL])); if (attrs[BATADV_ATTR_MULTICAST_FANOUT]) printf("* multicast_fanout %u\n", nla_get_u32(attrs[BATADV_ATTR_MULTICAST_FANOUT])); if (attrs[BATADV_ATTR_MULTICAST_FORCEFLOOD_ENABLED]) printf("* multicast_forceflood %s\n", u8_to_boolstr(attrs[BATADV_ATTR_MULTICAST_FORCEFLOOD_ENABLED])); if (attrs[BATADV_ATTR_NETWORK_CODING_ENABLED]) printf("* network_coding %s\n", u8_to_boolstr(attrs[BATADV_ATTR_NETWORK_CODING_ENABLED])); if (attrs[BATADV_ATTR_ORIG_INTERVAL]) printf("* orig_interval %u ms\n", nla_get_u32(attrs[BATADV_ATTR_ORIG_INTERVAL])); } static void event_parse_set_hardif(struct nlattr **attrs) { static const int hardif_mandatory[] = { BATADV_ATTR_MESH_IFINDEX, BATADV_ATTR_HARD_IFINDEX, }; char meshif_buf[IF_NAMESIZE]; char hardif_buf[IF_NAMESIZE]; uint32_t hardif_ifindex; uint32_t mesh_ifindex; char *meshif_name; char *hardif_name; /* ignore entry when attributes are missing */ if (missing_mandatory_attrs(attrs, hardif_mandatory, ARRAY_SIZE(hardif_mandatory))) return; if (attrs[BATADV_ATTR_MESH_IFNAME]) { meshif_name = nla_get_string(attrs[BATADV_ATTR_MESH_IFNAME]); } else { /* compatibility for Linux < 5.14/batman-adv < 2021.2 */ mesh_ifindex = nla_get_u32(attrs[BATADV_ATTR_MESH_IFINDEX]); meshif_name = if_indextoname(mesh_ifindex, meshif_buf); if (!meshif_name) return; } if (attrs[BATADV_ATTR_MESH_IFNAME]) { hardif_name = nla_get_string(attrs[BATADV_ATTR_HARD_IFNAME]); } else { /* compatibility for Linux < 5.14/batman-adv < 2021.2 */ hardif_ifindex = nla_get_u32(attrs[BATADV_ATTR_HARD_IFINDEX]); hardif_name = if_indextoname(hardif_ifindex, hardif_buf); if (!hardif_name) return; } printf("%s (%s): set hardif:\n", meshif_name, hardif_name); if (attrs[BATADV_ATTR_HOP_PENALTY]) printf("* hop_penalty %u\n", nla_get_u8(attrs[BATADV_ATTR_HOP_PENALTY])); if (attrs[BATADV_ATTR_ELP_INTERVAL]) printf("* elp_interval %u ms\n", nla_get_u32(attrs[BATADV_ATTR_ELP_INTERVAL])); if (attrs[BATADV_ATTR_THROUGHPUT_OVERRIDE]) { uint32_t val; val = nla_get_u32(attrs[BATADV_ATTR_THROUGHPUT_OVERRIDE]); printf("* throughput_override %u.%01u MBit/s\n", val / 10, val % 10); } } static void event_parse_set_vlan(struct nlattr **attrs) { static const int vlan_mandatory[] = { BATADV_ATTR_MESH_IFINDEX, BATADV_ATTR_VLANID, }; char meshif_buf[IF_NAMESIZE]; uint32_t mesh_ifindex; char *meshif_name; uint16_t vid; /* ignore entry when attributes are missing */ if (missing_mandatory_attrs(attrs, vlan_mandatory, ARRAY_SIZE(vlan_mandatory))) return; if (attrs[BATADV_ATTR_MESH_IFNAME]) { meshif_name = nla_get_string(attrs[BATADV_ATTR_MESH_IFNAME]); } else { /* compatibility for Linux < 5.14/batman-adv < 2021.2 */ mesh_ifindex = nla_get_u32(attrs[BATADV_ATTR_MESH_IFINDEX]); meshif_name = if_indextoname(mesh_ifindex, meshif_buf); if (!meshif_name) return; } vid = nla_get_u16(attrs[BATADV_ATTR_VLANID]); printf("%s (vid %u): set vlan:\n", meshif_name, vid); if (attrs[BATADV_ATTR_AP_ISOLATION_ENABLED]) printf("* ap_isolation %s\n", u8_to_boolstr(attrs[BATADV_ATTR_AP_ISOLATION_ENABLED])); } static unsigned long long get_timestamp(struct event_args *event_args) { unsigned long long prevtime = 0; unsigned long long now; struct timeval tv; gettimeofday(&tv, NULL); now = 1000000ULL * tv.tv_sec + tv.tv_usec; if (event_args->mode == EVENT_TIME_RELATIVE) { prevtime = 1000000ULL * event_args->tv.tv_sec + event_args->tv.tv_usec; event_args->tv = tv; } return now - prevtime; } static int event_parse(struct nl_msg *msg, void *arg) { struct nlmsghdr *nlh = nlmsg_hdr(msg); struct nlattr *attrs[NUM_BATADV_ATTR]; struct event_args *event_args = arg; unsigned long long timestamp; struct genlmsghdr *ghdr; if (!genlmsg_valid_hdr(nlh, 0)) return NL_OK; ghdr = nlmsg_data(nlh); if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { fputs("Received invalid data from kernel.\n", stderr); return NL_OK; } if (event_args->mode != EVENT_TIME_NO) { timestamp = get_timestamp(event_args); printf("%llu.%06llu: ", timestamp / 1000000, timestamp % 1000000); } switch (ghdr->cmd) { case BATADV_CMD_TP_METER: event_parse_tp_meter(attrs); break; case BATADV_CMD_SET_MESH: event_parse_set_mesh(attrs); break; case BATADV_CMD_SET_HARDIF: event_parse_set_hardif(attrs); break; case BATADV_CMD_SET_VLAN: event_parse_set_vlan(attrs); break; default: printf("Received unknown event %u\n", ghdr->cmd); break; } return NL_OK; } static int event(struct state *state, int argc, char **argv) { struct event_args event_args = { .mode = EVENT_TIME_NO, }; int opt; int ret; while ((opt = getopt(argc, argv, "htr")) != -1) { switch (opt) { case 'h': event_usage(); return EXIT_SUCCESS; case 't': event_args.mode = EVENT_TIME_LOCAL; break; case 'r': event_args.mode = EVENT_TIME_RELATIVE; break; default: event_usage(); return EXIT_FAILURE; } } ret = event_prepare(state); if (ret < 0) { fprintf(stderr, "Failed to prepare event netlink: %s (%d)\n", strerror(-ret), -ret); return 1; } if (event_args.mode == EVENT_TIME_RELATIVE) get_timestamp(&event_args); nl_cb_set(state->cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, no_seq_check, NULL); nl_cb_set(state->cb, NL_CB_VALID, NL_CB_CUSTOM, event_parse, &event_args); while (1) nl_recvmsgs(state->sock, state->cb); return 0; } COMMAND(SUBCOMMAND, event, "e", COMMAND_FLAG_NETLINK, NULL, " \tdisplay events from batman-adv"); batctl-2025.1/fragmentation.c000066400000000000000000000030121500012142700160420ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include "main.h" #include #include #include #include "batman_adv.h" #include "netlink.h" #include "sys.h" static struct simple_boolean_data fragmentation; static int print_fragmentation(struct nl_msg *msg, void *arg) { return sys_simple_print_boolean(msg, arg, BATADV_ATTR_FRAGMENTATION_ENABLED); } static int get_fragmentation(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_GET_MESH, NULL, print_fragmentation); } static int set_attrs_fragmentation(struct nl_msg *msg, void *arg) { struct state *state = arg; struct settings_data *settings = state->cmd->arg; struct simple_boolean_data *data = settings->data; nla_put_u8(msg, BATADV_ATTR_FRAGMENTATION_ENABLED, data->val); return 0; } static int set_fragmentation(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_SET_MESH, set_attrs_fragmentation, NULL); } static struct settings_data batctl_settings_fragmentation = { .data = &fragmentation, .parse = parse_simple_boolean, .netlink_get = get_fragmentation, .netlink_set = set_fragmentation, }; COMMAND_NAMED(SUBCOMMAND_MIF, fragmentation, "f", handle_sys_setting, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_settings_fragmentation, "[0|1] \tdisplay or modify fragmentation setting"); batctl-2025.1/functions.c000066400000000000000000000505311500012142700152240ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Andreas Langer , Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "main.h" #include "functions.h" #include "bat-hosts.h" #include "sys.h" #include "debug.h" #include "netlink.h" #define PATH_BUFF_LEN 400 static struct timespec start_time; static char *host_name; char *line_ptr; void start_timer(void) { clock_gettime(CLOCK_MONOTONIC, &start_time); } double end_timer(void) { struct timespec end_time; struct timespec diff; clock_gettime(CLOCK_MONOTONIC, &end_time); diff.tv_sec = end_time.tv_sec - start_time.tv_sec; diff.tv_nsec = end_time.tv_nsec - start_time.tv_nsec; if (diff.tv_nsec < 0) { diff.tv_sec--; diff.tv_nsec += 1000000000; } return (((double)diff.tv_sec * 1000) + ((double)diff.tv_nsec / 1000000)); } char *ether_ntoa_long(const struct ether_addr *addr) { static char asc[18]; sprintf(asc, "%02x:%02x:%02x:%02x:%02x:%02x", addr->ether_addr_octet[0], addr->ether_addr_octet[1], addr->ether_addr_octet[2], addr->ether_addr_octet[3], addr->ether_addr_octet[4], addr->ether_addr_octet[5]); return asc; } char *get_name_by_macaddr(struct ether_addr *mac_addr, int read_opt) { struct bat_host *bat_host = NULL; if (read_opt & USE_BAT_HOSTS) bat_host = bat_hosts_find_by_mac((char *)mac_addr); if (!bat_host) host_name = ether_ntoa_long((struct ether_addr *)mac_addr); else host_name = bat_host->name; return host_name; } char *get_name_by_macstr(char *mac_str, int read_opt) { struct ether_addr *mac_addr; mac_addr = ether_aton(mac_str); if (!mac_addr) return mac_str; return get_name_by_macaddr(mac_addr, read_opt); } int file_exists(const char *fpath) { struct stat st; return stat(fpath, &st) == 0; } static void file_open_problem_dbg(const char *full_path) { if (!file_exists(module_ver_path)) { fprintf(stderr, "Error - batman-adv module has not been loaded\n"); return; } fprintf(stderr, "Error - can't open file '%s': %s\n", full_path, strerror(errno)); fprintf(stderr, "The option you called seems not to be compiled into your batman-adv kernel module.\n"); fprintf(stderr, "Consult the README if you wish to learn more about compiling options into batman-adv.\n"); } static bool ether_addr_valid(const uint8_t *addr) { /* no multicast address */ if (addr[0] & 0x01) return false; /* no zero address */ if ((addr[0] | addr[1] | addr[2] | addr[3] | addr[4] | addr[5]) == 0) return false; return true; } int read_file(const char *full_path, int read_opt) { int res = EXIT_FAILURE; size_t len = 0; FILE *fp = NULL; fp = fopen(full_path, "r"); if (!fp) { if (!(read_opt & SILENCE_ERRORS)) file_open_problem_dbg(full_path); return res; } while (getline(&line_ptr, &len, fp) != -1) { /* the buffer will be handled elsewhere */ if (read_opt & USE_READ_BUFF) break; printf("%s", line_ptr); } if (line_ptr) res = EXIT_SUCCESS; fclose(fp); return res; } struct ether_addr *translate_mac(struct state *state, const struct ether_addr *mac) { static struct ether_addr out_mac; struct ether_addr *mac_result; struct ether_addr in_mac; /* input mac has to be copied because it could be in the shared * ether_aton buffer */ memcpy(&in_mac, mac, sizeof(in_mac)); memcpy(&out_mac, mac, sizeof(out_mac)); mac_result = &out_mac; if (!ether_addr_valid(in_mac.ether_addr_octet)) return mac_result; translate_mac_netlink(state, &in_mac, mac_result); return mac_result; } int get_algoname(struct state *state, unsigned int mesh_ifindex, char *algoname, size_t algoname_len) { return get_algoname_netlink(state, mesh_ifindex, algoname, algoname_len); } static int resolve_l3addr(int ai_family, const char *asc, void *l3addr) { struct sockaddr_in6 *inet6; struct sockaddr_in *inet4; struct addrinfo hints; struct addrinfo *res; int ret; memset(&hints, 0, sizeof(hints)); hints.ai_family = ai_family; ret = getaddrinfo(asc, NULL, &hints, &res); if (ret) return -EADDRNOTAVAIL; if (res) { switch (ai_family) { case AF_INET: inet4 = (struct sockaddr_in *)res->ai_addr; memcpy(l3addr, &inet4->sin_addr.s_addr, sizeof(inet4->sin_addr.s_addr)); break; case AF_INET6: inet6 = (struct sockaddr_in6 *)res->ai_addr; memcpy(l3addr, &inet6->sin6_addr.s6_addr, sizeof(inet6->sin6_addr.s6_addr)); break; default: ret = -EINVAL; } } freeaddrinfo(res); return ret; } static void request_mac_resolve(int ai_family, const void *l3addr) { const struct sockaddr *sockaddr; struct sockaddr_in6 inet6; struct sockaddr_in inet4; size_t sockaddr_len; char t = 0; int sock; sock = socket(ai_family, SOCK_DGRAM, IPPROTO_UDP); if (sock < 0) return; switch (ai_family) { case AF_INET: memset(&inet4, 0, sizeof(inet4)); inet4.sin_family = ai_family; inet4.sin_port = htons(9); memcpy(&inet4.sin_addr.s_addr, l3addr, sizeof(inet4.sin_addr.s_addr)); sockaddr = (const struct sockaddr *)&inet4; sockaddr_len = sizeof(inet4); break; case AF_INET6: memset(&inet6, 0, sizeof(inet6)); inet6.sin6_family = ai_family; inet6.sin6_port = htons(9); memcpy(&inet6.sin6_addr.s6_addr, l3addr, sizeof(inet6.sin6_addr.s6_addr)); sockaddr = (const struct sockaddr *)&inet6; sockaddr_len = sizeof(inet6); break; default: close(sock); return; } sendto(sock, &t, sizeof(t), 0, sockaddr, sockaddr_len); close(sock); } struct resolve_mac_nl_arg { int ai_family; const void *l3addr; struct ether_addr *mac_result; int found; }; static struct nla_policy neigh_policy[NDA_MAX + 1] = { [NDA_CACHEINFO] = { .minlen = sizeof(struct nda_cacheinfo) }, [NDA_PROBES] = { .type = NLA_U32 }, }; static int resolve_mac_from_parse(struct nl_msg *msg, void *arg) { struct resolve_mac_nl_arg *nl_arg = arg; struct nlattr *tb[NDA_MAX + 1]; struct ndmsg *nm; uint8_t *l3addr; uint8_t *mac; int l3_len; int ret; nm = nlmsg_data(nlmsg_hdr(msg)); ret = nlmsg_parse(nlmsg_hdr(msg), sizeof(*nm), tb, NDA_MAX, neigh_policy); if (ret < 0) goto err; if (nl_arg->ai_family != nm->ndm_family) goto err; switch (nl_arg->ai_family) { case AF_INET: l3_len = 4; break; case AF_INET6: l3_len = 16; break; default: l3_len = 0; } if (l3_len == 0) goto err; if (!tb[NDA_LLADDR] || !tb[NDA_DST]) goto err; if (nla_len(tb[NDA_LLADDR]) != ETH_ALEN) goto err; if (nla_len(tb[NDA_DST]) != l3_len) goto err; mac = nla_data(tb[NDA_LLADDR]); l3addr = nla_data(tb[NDA_DST]); if (!ether_addr_valid(mac)) goto err; if (memcmp(nl_arg->l3addr, l3addr, l3_len) == 0) { memcpy(nl_arg->mac_result, mac, ETH_ALEN); nl_arg->found = 1; } err: return NL_OK; } static struct ether_addr *resolve_mac_from_cache(int ai_family, const void *l3addr) { struct ether_addr *mac_result = NULL; static struct ether_addr mac_tmp; struct resolve_mac_nl_arg arg = { .ai_family = ai_family, .l3addr = l3addr, .mac_result = &mac_tmp, .found = 0, }; struct rtgenmsg gmsg = { .rtgen_family = ai_family, }; struct nl_cb *cb = NULL; struct nl_sock *sock; int ret; sock = nl_socket_alloc(); if (!sock) goto err; ret = nl_connect(sock, NETLINK_ROUTE); if (ret < 0) goto err; ret = nl_send_simple(sock, RTM_GETNEIGH, NLM_F_REQUEST | NLM_F_DUMP, &gmsg, sizeof(gmsg)); if (ret < 0) goto err; cb = nl_cb_alloc(NL_CB_DEFAULT); if (!cb) goto err; nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, resolve_mac_from_parse, &arg); ret = nl_recvmsgs(sock, cb); if (ret < 0) goto err; if (arg.found) mac_result = &mac_tmp; err: if (cb) nl_cb_put(cb); if (sock) nl_socket_free(sock); return mac_result; } static struct ether_addr *resolve_mac_from_addr(int ai_family, const char *asc) { struct ether_addr *mac_result = NULL; uint8_t ipv6_addr[16]; uint8_t ipv4_addr[4]; int retries = 5; void *l3addr; int ret; switch (ai_family) { case AF_INET: l3addr = ipv4_addr; break; case AF_INET6: l3addr = ipv6_addr; break; default: return NULL; } ret = resolve_l3addr(ai_family, asc, l3addr); if (ret < 0) return NULL; while (retries-- && !mac_result) { mac_result = resolve_mac_from_cache(ai_family, l3addr); if (!mac_result) { request_mac_resolve(ai_family, l3addr); usleep(200000); } } return mac_result; } struct ether_addr *resolve_mac(const char *asc) { struct ether_addr *mac_result = NULL; static const int ai_families[] = { AF_INET, AF_INET6, }; size_t i; mac_result = ether_aton(asc); if (mac_result) goto out; for (i = 0; i < sizeof(ai_families) / sizeof(*ai_families); i++) { mac_result = resolve_mac_from_addr(ai_families[i], asc); if (mac_result) goto out; } out: return mac_result; } int query_rtnl_link(int ifindex, nl_recvmsg_msg_cb_t func, void *arg) { struct ifinfomsg rt_hdr = { .ifi_family = IFLA_UNSPEC, }; struct nl_sock *sock; struct nl_msg *msg; struct nl_cb *cb; int err = 0; int ret; sock = nl_socket_alloc(); if (!sock) return -ENOMEM; ret = nl_connect(sock, NETLINK_ROUTE); if (ret < 0) { err = -ENOMEM; goto err_free_sock; } cb = nl_cb_alloc(NL_CB_DEFAULT); if (!cb) { err = -ENOMEM; goto err_free_sock; } nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, func, arg); msg = nlmsg_alloc_simple(RTM_GETLINK, NLM_F_REQUEST | NLM_F_DUMP); if (!msg) { err = -ENOMEM; goto err_free_cb; } ret = nlmsg_append(msg, &rt_hdr, sizeof(rt_hdr), NLMSG_ALIGNTO); if (ret < 0) { err = -ENOMEM; goto err_free_msg; } ret = nla_put_u32(msg, IFLA_MASTER, ifindex); if (ret < 0) { err = -ENOMEM; goto err_free_msg; } ret = nl_send_auto_complete(sock, msg); if (ret < 0) goto err_free_msg; nl_recvmsgs(sock, cb); err_free_msg: nlmsg_free(msg); err_free_cb: nl_cb_put(cb); err_free_sock: nl_socket_free(sock); return err; } static int ack_errno_handler(struct sockaddr_nl *nla __maybe_unused, struct nlmsgerr *nlerr, void *arg) { int *err = arg; *err = nlerr->error; return NL_STOP; } static int ack_wait_handler(struct nl_msg *msg __maybe_unused, void *arg __maybe_unused) { return NL_STOP; } int netlink_simple_request(struct nl_msg *msg) { struct nl_sock *sock; struct nl_cb *cb; int err = 0; int ret; sock = nl_socket_alloc(); if (!sock) return -ENOMEM; ret = nl_connect(sock, NETLINK_ROUTE); if (ret < 0) { err = -ENOMEM; goto err_free_sock; } cb = nl_cb_alloc(NL_CB_DEFAULT); if (!cb) { err = -ENOMEM; goto err_free_sock; } nl_cb_err(cb, NL_CB_CUSTOM, ack_errno_handler, &err); nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_wait_handler, NULL); ret = nl_send_auto_complete(sock, msg); if (ret < 0) goto err_free_cb; // ack_errno_handler sets err on errors err = 0; nl_recvmsgs(sock, cb); err_free_cb: nl_cb_put(cb); err_free_sock: nl_socket_free(sock); return err; } struct rtnl_link_iface_data { uint8_t kind_found:1; uint8_t master_found:1; uint8_t link_found:1; uint8_t vid_found:1; char kind[IF_NAMESIZE]; unsigned int master; unsigned int link; uint16_t vid; }; static int query_rtnl_link_single_parse(struct nl_msg *msg, void *arg) { static struct nla_policy link_policy[IFLA_MAX + 1] = { [IFLA_LINKINFO] = { .type = NLA_NESTED }, [IFLA_MASTER] = { .type = NLA_U32 }, [IFLA_LINK] = { .type = NLA_U32 }, }; static struct nla_policy link_info_policy[IFLA_INFO_MAX + 1] = { [IFLA_INFO_KIND] = { .type = NLA_STRING }, [IFLA_INFO_DATA] = { .type = NLA_NESTED }, }; static struct nla_policy vlan_policy[IFLA_VLAN_MAX + 1] = { [IFLA_VLAN_ID] = { .type = NLA_U16 }, }; struct rtnl_link_iface_data *link_data = arg; struct nlattr *li[IFLA_INFO_MAX + 1]; struct nlattr *vi[IFLA_VLAN_MAX + 1]; struct nlmsghdr *n = nlmsg_hdr(msg); struct nlattr *tb[IFLA_MAX + 1]; char *type; int ret; if (!nlmsg_valid_hdr(n, sizeof(struct ifinfomsg))) return NL_OK; ret = nlmsg_parse(n, sizeof(struct ifinfomsg), tb, IFLA_MAX, link_policy); if (ret < 0) return NL_OK; if (tb[IFLA_MASTER]) { link_data->master = nla_get_u32(tb[IFLA_MASTER]); link_data->master_found = true; } if (tb[IFLA_LINK]) { link_data->link = nla_get_u32(tb[IFLA_LINK]); link_data->link_found = true; } /* parse subattributes linkinfo */ if (!tb[IFLA_LINKINFO]) return NL_OK; ret = nla_parse_nested(li, IFLA_INFO_MAX, tb[IFLA_LINKINFO], link_info_policy); if (ret < 0) return NL_OK; if (li[IFLA_INFO_KIND]) { type = nla_get_string(li[IFLA_INFO_KIND]); strncpy(link_data->kind, type, sizeof(link_data->kind)); link_data->kind[sizeof(link_data->kind) - 1] = '\0'; link_data->kind_found = true; } if (!li[IFLA_INFO_DATA]) return NL_OK; ret = nla_parse_nested(vi, IFLA_VLAN_MAX, li[IFLA_INFO_DATA], vlan_policy); if (ret < 0) return NL_OK; if (vi[IFLA_VLAN_ID]) { link_data->vid = nla_get_u16(vi[IFLA_VLAN_ID]); link_data->vid_found = true; } return NL_STOP; } static int query_rtnl_link_single(int mesh_ifindex, struct rtnl_link_iface_data *link_data) { struct ifinfomsg ifinfo = { .ifi_family = AF_UNSPEC, .ifi_index = mesh_ifindex, }; struct nl_cb *cb = NULL; struct nl_sock *sock; int ret; link_data->kind_found = false; link_data->master_found = false; link_data->link_found = false; link_data->vid_found = false; sock = nl_socket_alloc(); if (!sock) return -1; ret = nl_connect(sock, NETLINK_ROUTE); if (ret < 0) goto free_sock; ret = nl_send_simple(sock, RTM_GETLINK, NLM_F_REQUEST, &ifinfo, sizeof(ifinfo)); if (ret < 0) goto free_sock; cb = nl_cb_alloc(NL_CB_DEFAULT); if (!cb) goto free_sock; nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, query_rtnl_link_single_parse, link_data); nl_recvmsgs(sock, cb); nl_cb_put(cb); free_sock: nl_socket_free(sock); return 0; } int translate_vlan_iface(struct state *state, const char *vlandev) { struct rtnl_link_iface_data link_data; unsigned int arg_ifindex; arg_ifindex = if_nametoindex(vlandev); if (arg_ifindex == 0) return -ENODEV; query_rtnl_link_single(arg_ifindex, &link_data); if (!link_data.vid_found) return -ENODEV; if (!link_data.link_found) return -EINVAL; if (!link_data.kind_found) return -EINVAL; if (strcmp(link_data.kind, "vlan") != 0) return -EINVAL; if (!if_indextoname(link_data.link, state->mesh_iface)) return -ENODEV; state->vid = link_data.vid; state->selector = SP_VLAN; return 0; } int translate_mesh_iface_vlan(struct state *state, const char *vlandev) { int ret; ret = translate_vlan_iface(state, vlandev); if (ret < 0) goto fallback_meshif; return 0; fallback_meshif: /* if there is no vid then the argument must be the * mesh interface */ snprintf(state->mesh_iface, sizeof(state->mesh_iface), "%s", vlandev); state->selector = SP_NONE_OR_MESHIF; return 0; } int translate_vid(struct state *state, const char *vidstr) { unsigned long vid; char *endptr; if (vidstr[0] == '\0') { fprintf(stderr, "Error - unparsable vid\n"); return -EINVAL; } vid = strtoul(vidstr, &endptr, 0); if (!endptr || *endptr != '\0') { fprintf(stderr, "Error - unparsable vid\n"); return -EINVAL; } if (vid > 4095) { fprintf(stderr, "Error - too large vid (max 4095)\n"); return -ERANGE; } /* get mesh interface and overwrite vid afterwards */ state->vid = vid; state->selector = SP_VLAN; return 0; } int translate_hard_iface(struct state *state, const char *hardif) { struct rtnl_link_iface_data link_data; unsigned int arg_ifindex; arg_ifindex = if_nametoindex(hardif); if (arg_ifindex == 0) return -ENODEV; query_rtnl_link_single(arg_ifindex, &link_data); if (!link_data.master_found) return -ENOLINK; if (!if_indextoname(link_data.master, state->mesh_iface)) return -ENOLINK; state->hif = arg_ifindex; state->selector = SP_HARDIF; return 0; } static int check_mesh_iface_netlink(unsigned int ifindex) { struct rtnl_link_iface_data link_data; query_rtnl_link_single(ifindex, &link_data); if (!link_data.kind_found) return -1; if (strcmp(link_data.kind, "batadv") != 0) return -1; return 0; } int guess_netdev_type(const char *netdev, enum selector_prefix *type) { struct rtnl_link_iface_data link_data; unsigned int netdev_ifindex; netdev_ifindex = if_nametoindex(netdev); if (netdev_ifindex == 0) return -ENODEV; query_rtnl_link_single(netdev_ifindex, &link_data); if (link_data.kind_found && strcmp(link_data.kind, "batadv") == 0) { *type = SP_MESHIF; return 0; } if (link_data.master_found && check_mesh_iface_netlink(link_data.master) >= 0) { *type = SP_HARDIF; return 0; } if (link_data.kind_found && strcmp(link_data.kind, "vlan") == 0) { *type = SP_VLAN; return 0; } return -EINVAL; } int check_mesh_iface(struct state *state) { state->mesh_ifindex = if_nametoindex(state->mesh_iface); if (state->mesh_ifindex == 0) return -1; return check_mesh_iface_netlink(state->mesh_ifindex); } int check_mesh_iface_ownership(struct state *state, char *hard_iface) { struct rtnl_link_iface_data link_data; unsigned int hardif_index; hardif_index = if_nametoindex(hard_iface); if (hardif_index == 0) return EXIT_FAILURE; query_rtnl_link_single(hardif_index, &link_data); if (!link_data.master_found) return EXIT_FAILURE; if (state->mesh_ifindex != link_data.master) return EXIT_FAILURE; return EXIT_SUCCESS; } static int get_random_bytes_syscall(void *buf __maybe_unused, size_t buflen __maybe_unused) { #ifdef SYS_getrandom return syscall(SYS_getrandom, buf, buflen, 0); #else return -EOPNOTSUPP; #endif } static int get_random_bytes_urandom(void *buf, size_t buflen) { ssize_t r; int fd; fd = open("/dev/urandom", O_RDONLY); if (fd < 0) return -EOPNOTSUPP; r = read(fd, buf, buflen); close(fd); if (r < 0) return -EOPNOTSUPP; if ((size_t)r != buflen) return -EOPNOTSUPP; return 0; } static int get_random_bytes_fallback(void *buf, size_t buflen) { static int initialized; struct timespec now; uint8_t *bufc = buf; size_t i; /* this is not a good source for randomness */ if (!initialized) { clock_gettime(CLOCK_MONOTONIC, &now); srand(now.tv_sec ^ now.tv_nsec); initialized = 1; } for (i = 0; i < buflen; i++) bufc[i] = rand() & 0xff; return 0; } void get_random_bytes(void *buf, size_t buflen) { int ret; ret = get_random_bytes_syscall(buf, buflen); if (ret != -EOPNOTSUPP) return; ret = get_random_bytes_urandom(buf, buflen); if (ret != -EOPNOTSUPP) return; get_random_bytes_fallback(buf, buflen); } int parse_bool(const char *val, bool *res) { if (strcasecmp(val, "0") == 0 || strcasecmp(val, "disable") == 0 || strcasecmp(val, "disabled") == 0) { *res = false; return 0; } else if (strcasecmp(val, "1") == 0 || strcasecmp(val, "enable") == 0 || strcasecmp(val, "enabled") == 0) { *res = true; return 0; } return -EINVAL; } bool parse_throughput(char *buff, const char *description, uint32_t *throughput) { enum batadv_bandwidth_units bw_unit_type = BATADV_BW_UNIT_KBIT; uint64_t lthroughput; char *tmp_ptr; char *endptr; if (strlen(buff) > 4) { tmp_ptr = buff + strlen(buff) - 4; if (strncasecmp(tmp_ptr, "mbit", 4) == 0) bw_unit_type = BATADV_BW_UNIT_MBIT; if (strncasecmp(tmp_ptr, "kbit", 4) == 0 || bw_unit_type == BATADV_BW_UNIT_MBIT) *tmp_ptr = '\0'; } lthroughput = strtoull(buff, &endptr, 10); if (!endptr || *endptr != '\0') { fprintf(stderr, "Invalid throughput speed for %s: %s\n", description, buff); return false; } switch (bw_unit_type) { case BATADV_BW_UNIT_MBIT: /* prevent overflow */ if (UINT64_MAX / 10 < lthroughput) { fprintf(stderr, "Throughput speed for %s too large: %s\n", description, buff); return false; } lthroughput *= 10; break; case BATADV_BW_UNIT_KBIT: default: lthroughput = lthroughput / 100; break; } if (lthroughput > UINT32_MAX) { fprintf(stderr, "Throughput speed for %s too large: %s\n", description, buff); return false; } *throughput = lthroughput; return true; } batctl-2025.1/functions.h000066400000000000000000000046121500012142700152300ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) B.A.T.M.A.N. contributors: * * Andreas Langer , Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #ifndef _BATCTL_FUNCTIONS_H #define _BATCTL_FUNCTIONS_H #include #include #include #include #include #include #include "main.h" /** * enum batadv_bandwidth_units - bandwidth unit types */ enum batadv_bandwidth_units { /** @BATADV_BW_UNIT_KBIT: unit type kbit */ BATADV_BW_UNIT_KBIT, /** @BATADV_BW_UNIT_MBIT: unit type mbit */ BATADV_BW_UNIT_MBIT, }; #define ETH_STR_LEN 17 #define BATMAN_ADV_TAG "batman-adv:" #define PATH_BUFF_LEN 400 struct state; /* return time delta from start to end in milliseconds */ void start_timer(void); double end_timer(void); char *ether_ntoa_long(const struct ether_addr *addr); char *get_name_by_macaddr(struct ether_addr *mac_addr, int read_opt); char *get_name_by_macstr(char *mac_str, int read_opt); int file_exists(const char *fpath); int read_file(const char *full_path, int read_opt); struct ether_addr *translate_mac(struct state *state, const struct ether_addr *mac); struct ether_addr *resolve_mac(const char *asc); int query_rtnl_link(int ifindex, nl_recvmsg_msg_cb_t func, void *arg); int netlink_simple_request(struct nl_msg *msg); int translate_mesh_iface_vlan(struct state *state, const char *vlandev); int translate_vlan_iface(struct state *state, const char *vlandev); int translate_vid(struct state *state, const char *vidstr); int translate_hard_iface(struct state *state, const char *hardif); int guess_netdev_type(const char *netdev, enum selector_prefix *type); int get_algoname(struct state *state, unsigned int mesh_ifindex, char *algoname, size_t algoname_len); int check_mesh_iface(struct state *state); int check_mesh_iface_ownership(struct state *state, char *hard_iface); void get_random_bytes(void *buf, size_t buflen); int parse_bool(const char *val, bool *res); bool parse_throughput(char *buff, const char *description, uint32_t *throughput); extern char *line_ptr; enum { NO_FLAGS = 0x00, CONT_READ = 0x01, CLR_CONT_READ = 0x02, USE_BAT_HOSTS = 0x04, USE_READ_BUFF = 0x10, SILENCE_ERRORS = 0x20, NO_OLD_ORIGS = 0x40, COMPAT_FILTER = 0x80, SKIP_HEADER = 0x100, UNICAST_ONLY = 0x200, MULTICAST_ONLY = 0x400, }; #endif batctl-2025.1/gateways.c000066400000000000000000000100331500012142700150310ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Linus Lüssing * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include #include #include #include #include "batman_adv.h" #include "bat-hosts.h" #include "debug.h" #include "functions.h" #include "main.h" #include "netlink.h" static const int gateways_mandatory[] = { BATADV_ATTR_ORIG_ADDRESS, BATADV_ATTR_ROUTER, BATADV_ATTR_HARD_IFNAME, BATADV_ATTR_BANDWIDTH_DOWN, BATADV_ATTR_BANDWIDTH_UP, }; static int gateways_callback(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct print_opts *opts = arg; struct bat_host *bat_host; struct genlmsghdr *ghdr; const char *primary_if; uint32_t bandwidth_down; uint32_t bandwidth_up; uint32_t throughput; uint8_t *router; uint8_t *orig; char c = ' '; uint8_t tq; if (!genlmsg_valid_hdr(nlh, 0)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } ghdr = nlmsg_data(nlh); if (ghdr->cmd != BATADV_CMD_GET_GATEWAYS) return NL_OK; if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } if (missing_mandatory_attrs(attrs, gateways_mandatory, ARRAY_SIZE(gateways_mandatory))) { fputs("Missing attributes from kernel\n", stderr); exit(1); } if (attrs[BATADV_ATTR_FLAG_BEST]) c = '*'; orig = nla_data(attrs[BATADV_ATTR_ORIG_ADDRESS]); router = nla_data(attrs[BATADV_ATTR_ROUTER]); primary_if = nla_get_string(attrs[BATADV_ATTR_HARD_IFNAME]); bandwidth_down = nla_get_u32(attrs[BATADV_ATTR_BANDWIDTH_DOWN]); bandwidth_up = nla_get_u32(attrs[BATADV_ATTR_BANDWIDTH_UP]); printf("%c ", c); bat_host = bat_hosts_find_by_mac((char *)orig); if (!(opts->read_opt & USE_BAT_HOSTS) || !bat_host) printf("%02x:%02x:%02x:%02x:%02x:%02x ", orig[0], orig[1], orig[2], orig[3], orig[4], orig[5]); else printf("%17s ", bat_host->name); if (attrs[BATADV_ATTR_THROUGHPUT]) { throughput = nla_get_u32(attrs[BATADV_ATTR_THROUGHPUT]); printf("(%9u.%1u) ", throughput / 10, throughput % 10); } else if (attrs[BATADV_ATTR_TQ]) { tq = nla_get_u8(attrs[BATADV_ATTR_TQ]); printf("(%3i) ", tq); } bat_host = bat_hosts_find_by_mac((char *)router); if (!(opts->read_opt & USE_BAT_HOSTS) || !bat_host) printf("%02x:%02x:%02x:%02x:%02x:%02x ", router[0], router[1], router[2], router[3], router[4], router[5]); else printf("%17s ", bat_host->name); printf("[%10s]: %u.%u/%u.%u MBit\n", primary_if, bandwidth_down / 10, bandwidth_down % 10, bandwidth_up / 10, bandwidth_up % 10); return NL_OK; } static int netlink_print_gateways(struct state *state, char *orig_iface, int read_opts, float orig_timeout, float watch_interval) { char *header = NULL; char *info_header; /* only parse routing algorithm name */ last_err = -EINVAL; info_header = netlink_get_info(state, BATADV_CMD_GET_ORIGINATORS, NULL); free(info_header); if (strlen(algo_name_buf) == 0) return last_err; if (!strcmp("BATMAN_IV", algo_name_buf)) header = " Router ( TQ) Next Hop [outgoingIf] Bandwidth\n"; if (!strcmp("BATMAN_V", algo_name_buf)) header = " Router ( throughput) Next Hop [outgoingIf] Bandwidth\n"; if (!header) return -EINVAL; return netlink_print_common(state, orig_iface, read_opts, orig_timeout, watch_interval, header, BATADV_CMD_GET_GATEWAYS, gateways_callback); } static struct debug_table_data batctl_debug_table_gateways = { .netlink_fn = netlink_print_gateways, }; COMMAND_NAMED(DEBUGTABLE, gateways, "gwl", handle_debug_table, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_debug_table_gateways, ""); batctl-2025.1/gateways_json.c000066400000000000000000000007711500012142700160720ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Sven Eckelmann #include #include #include #include #include #include #include "batman_adv.h" static int mcast_error_handler(struct sockaddr_nl *nla __maybe_unused, struct nlmsgerr *err, void *arg) { int *ret = arg; *ret = err->error; return NL_STOP; } static int mcast_ack_handler(struct nl_msg *msg __maybe_unused, void *arg) { int *ret = arg; *ret = 0; return NL_STOP; } struct mcast_handler_args { const char *group; int id; }; static int mcast_family_handler(struct nl_msg *msg, void *arg) { struct mcast_handler_args *grp = arg; struct nlattr *tb[CTRL_ATTR_MAX + 1]; struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); struct nlattr *mcgrp; int rem_mcgrp; nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL); if (!tb[CTRL_ATTR_MCAST_GROUPS]) return NL_SKIP; nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) { struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1]; nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX, nla_data(mcgrp), nla_len(mcgrp), NULL); if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] || !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]) continue; if (strncmp(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]), grp->group, nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]))) continue; grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]); break; } return NL_SKIP; } int nl_get_multicast_id(struct nl_sock *sock, const char *family, const char *group) { struct mcast_handler_args grp = { .group = group, .id = -ENOENT, }; struct nl_msg *msg; struct nl_cb *cb; int ctrlid; int ret; msg = nlmsg_alloc(); if (!msg) return -ENOMEM; cb = nl_cb_alloc(NL_CB_DEFAULT); if (!cb) { ret = -ENOMEM; goto out_fail_cb; } ctrlid = genl_ctrl_resolve(sock, "nlctrl"); genlmsg_put(msg, 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0); ret = -ENOBUFS; NLA_PUT_STRING(msg, CTRL_ATTR_FAMILY_NAME, family); ret = nl_send_auto_complete(sock, msg); if (ret < 0) goto out; ret = 1; nl_cb_err(cb, NL_CB_CUSTOM, mcast_error_handler, &ret); nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, mcast_ack_handler, &ret); nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, mcast_family_handler, &grp); while (ret > 0) nl_recvmsgs(sock, cb); if (ret == 0) ret = grp.id; nla_put_failure: out: nl_cb_put(cb); out_fail_cb: nlmsg_free(msg); return ret; } batctl-2025.1/genl.h000066400000000000000000000006371500012142700141500ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #ifndef _BATCTL_GENL_H #define _BATCTL_GENL_H #include #include int nl_get_multicast_id(struct nl_sock *sock, const char *family, const char *group); #endif /* _BATCTL_GENL_H */ batctl-2025.1/genl_json.c000066400000000000000000000337161500012142700152000ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Alexander Sarmanow * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include "genl_json.h" #include "main.h" #include #include #include #include #include #include #include #include #include #include #include #include "functions.h" #include "batadv_packet_compat.h" #include "batman_adv.h" #include "netlink.h" struct nla_policy_json { const char *name; void (*cb)(struct nlattr *attrs[], int idx); }; static void sanitize_string(const char *str) { while (*str) { if (*str == '"' || *str == '\\') { putchar('\\'); putchar(*str); } else if (!isprint(*str)) { printf("\\x%02x", *str); } else { putchar(*str); } str++; } } static void nljson_print_str(struct nlattr *attrs[], int idx) { const char *value; value = nla_get_string(attrs[idx]); putchar('"'); sanitize_string(value); putchar('"'); } static void nljson_print_flag(struct nlattr *attrs[] __maybe_unused, int idx __maybe_unused) { printf("true"); } static void nljson_print_bool(struct nlattr *attrs[], int idx) { printf("%s", nla_get_u8(attrs[idx]) ? "true" : "false"); } static void nljson_print_uint8(struct nlattr *attrs[], int idx) { printf("%" PRIu8, nla_get_u8(attrs[idx])); } static void nljson_print_uint16(struct nlattr *attrs[], int idx) { printf("%" PRIu16, nla_get_u16(attrs[idx])); } static void nljson_print_uint32(struct nlattr *attrs[], int idx) { printf("%" PRIu32, nla_get_u32(attrs[idx])); } static void nljson_print_uint64(struct nlattr *attrs[], int idx) { printf("%" PRIu64, nla_get_u64(attrs[idx])); } static void nljson_print_vlanid(struct nlattr *attrs[], int idx) { uint16_t vid = nla_get_u16(attrs[idx]); printf("%d", BATADV_PRINT_VID(vid)); } static void nljson_print_mac(struct nlattr *attrs[], int idx) { uint8_t *value = nla_data(attrs[idx]); printf("\"%02x:%02x:%02x:%02x:%02x:%02x\"", value[0], value[1], value[2], value[3], value[4], value[5]); } static void nljson_print_ttflags(struct nlattr *attrs[], int idx) { uint32_t val = nla_get_u32(attrs[idx]); putchar('{'); printf("\"del\": %s,", val & BATADV_TT_CLIENT_DEL ? "true" : "false"); printf("\"roam\": %s,", val & BATADV_TT_CLIENT_ROAM ? "true" : "false"); printf("\"wifi\": %s,", val & BATADV_TT_CLIENT_WIFI ? "true" : "false"); printf("\"isolated\": %s,", val & BATADV_TT_CLIENT_ISOLA ? "true" : "false"); printf("\"nopurge\": %s,", val & BATADV_TT_CLIENT_NOPURGE ? "true" : "false"); printf("\"new\": %s,", val & BATADV_TT_CLIENT_NEW ? "true" : "false"); printf("\"pending\": %s,", val & BATADV_TT_CLIENT_PENDING ? "true" : "false"); printf("\"temp\": %s,", val & BATADV_TT_CLIENT_TEMP ? "true" : "false"); printf("\"raw\": %" PRIu32, val); putchar('}'); } static void nljson_print_ipv4(struct nlattr *attrs[], int idx) { uint32_t val = nla_get_u32(attrs[idx]); struct in_addr in_addr; char *addr; in_addr.s_addr = val; addr = inet_ntoa(in_addr); putchar('"'); sanitize_string(addr); putchar('"'); } static void nljson_print_mcastflags(struct nlattr *attrs[], int idx) { uint32_t val = nla_get_u32(attrs[idx]); putchar('{'); printf("\"all_unsnoopables\": %s,", val & BATADV_MCAST_WANT_ALL_UNSNOOPABLES ? "true" : "false"); printf("\"want_all_ipv4\": %s,", val & BATADV_MCAST_WANT_ALL_IPV4 ? "true" : "false"); printf("\"want_all_ipv6\": %s,", val & BATADV_MCAST_WANT_ALL_IPV6 ? "true" : "false"); printf("\"want_no_rtr_ipv4\": %s,", val & BATADV_MCAST_WANT_NO_RTR4 ? "true" : "false"); printf("\"want_no_rtr_ipv6\": %s,", val & BATADV_MCAST_WANT_NO_RTR6 ? "true" : "false"); printf("\"have_mc_ptype_capa\": %s,", val & BATADV_MCAST_HAVE_MC_PTYPE_CAPA ? "true" : "false"); printf("\"raw\": %" PRIu32, val); putchar('}'); } static void nljson_print_mcastflags_priv(struct nlattr *attrs[], int idx) { uint32_t val = nla_get_u32(attrs[idx]); putchar('{'); printf("\"bridged\": %s,", val & BATADV_MCAST_FLAGS_BRIDGED ? "true" : "false"); printf("\"querier_ipv4_exists\": %s,", val & BATADV_MCAST_FLAGS_QUERIER_IPV4_EXISTS ? "true" : "false"); printf("\"querier_ipv6_exists\": %s,", val & BATADV_MCAST_FLAGS_QUERIER_IPV6_EXISTS ? "true" : "false"); printf("\"querier_ipv4_shadowing\": %s,", val & BATADV_MCAST_FLAGS_QUERIER_IPV4_SHADOWING ? "true" : "false"); printf("\"querier_ipv6_shadowing\": %s,", val & BATADV_MCAST_FLAGS_QUERIER_IPV6_SHADOWING ? "true" : "false"); printf("\"raw\": %" PRIu32, val); putchar('}'); } static void nljson_print_gwmode(struct nlattr *attrs[], int idx) { uint8_t val = nla_get_u8(attrs[idx]); switch (val) { case BATADV_GW_MODE_OFF: printf("\"off\""); break; case BATADV_GW_MODE_CLIENT: printf("\"client\""); break; case BATADV_GW_MODE_SERVER: printf("\"server\""); break; default: printf("\"unknown\""); break; } } static void nljson_print_loglevel(struct nlattr *attrs[], int idx) { uint32_t val = nla_get_u32(attrs[idx]); putchar('{'); printf("\"batman\": %s,", val & BIT(0) ? "true" : "false"); printf("\"routes\": %s,", val & BIT(1) ? "true" : "false"); printf("\"tt\": %s,", val & BIT(2) ? "true" : "false"); printf("\"bla\": %s,", val & BIT(3) ? "true" : "false"); printf("\"dat\": %s,", val & BIT(4) ? "true" : "false"); printf("\"nc\": %s,", val & BIT(5) ? "true" : "false"); printf("\"mcast\": %s,", val & BIT(6) ? "true" : "false"); printf("\"tp\": %s,", val & BIT(7) ? "true" : "false"); printf("\"raw\": %" PRIu32, val); putchar('}'); } /* WARNING: attributes must also be added to batadv_netlink_policy */ static const struct nla_policy_json batadv_genl_json[NUM_BATADV_ATTR] = { [BATADV_ATTR_VERSION] = { .name = "version", .cb = nljson_print_str, }, [BATADV_ATTR_ALGO_NAME] = { .name = "algo_name", .cb = nljson_print_str, }, [BATADV_ATTR_MESH_IFINDEX] = { .name = "mesh_ifindex", .cb = nljson_print_uint32, }, [BATADV_ATTR_MESH_IFNAME] = { .name = "mesh_ifname", .cb = nljson_print_str, }, [BATADV_ATTR_MESH_ADDRESS] = { .name = "mesh_address", .cb = nljson_print_mac, }, [BATADV_ATTR_HARD_IFINDEX] = { .name = "hard_ifindex", .cb = nljson_print_uint32, }, [BATADV_ATTR_HARD_IFNAME] = { .name = "hard_ifname", .cb = nljson_print_str, }, [BATADV_ATTR_HARD_ADDRESS] = { .name = "hard_address", .cb = nljson_print_mac, }, [BATADV_ATTR_ORIG_ADDRESS] = { .name = "orig_address", .cb = nljson_print_mac, }, [BATADV_ATTR_TPMETER_RESULT] = { .name = "tpmeter_result", .cb = nljson_print_uint8, }, [BATADV_ATTR_TPMETER_TEST_TIME] = { .name = "tpmeter_test_time", .cb = nljson_print_uint32, }, [BATADV_ATTR_TPMETER_BYTES] = { .name = "tpmeter_bytes", .cb = nljson_print_uint64, }, [BATADV_ATTR_TPMETER_COOKIE] = { .name = "tpmeter_cookie", .cb = nljson_print_uint32, }, [BATADV_ATTR_ACTIVE] = { .name = "active", .cb = nljson_print_flag, }, [BATADV_ATTR_TT_ADDRESS] = { .name = "tt_address", .cb = nljson_print_mac, }, [BATADV_ATTR_TT_TTVN] = { .name = "tt_ttvn", .cb = nljson_print_uint8, }, [BATADV_ATTR_TT_LAST_TTVN] = { .name = "tt_last_ttvn", .cb = nljson_print_uint8, }, [BATADV_ATTR_TT_CRC32] = { .name = "tt_crc32", .cb = nljson_print_uint32, }, [BATADV_ATTR_TT_VID] = { .name = "tt_vid", .cb = nljson_print_vlanid, }, [BATADV_ATTR_TT_FLAGS] = { .name = "tt_flags", .cb = nljson_print_ttflags, }, [BATADV_ATTR_FLAG_BEST] = { .name = "best", .cb = nljson_print_flag, }, [BATADV_ATTR_LAST_SEEN_MSECS] = { .name = "last_seen_msecs", .cb = nljson_print_uint32, }, [BATADV_ATTR_NEIGH_ADDRESS] = { .name = "neigh_address", .cb = nljson_print_mac, }, [BATADV_ATTR_TQ] = { .name = "tq", .cb = nljson_print_uint8, }, [BATADV_ATTR_THROUGHPUT] = { .name = "throughput", .cb = nljson_print_uint32, }, [BATADV_ATTR_BANDWIDTH_UP] = { .name = "bandwidth_up", .cb = nljson_print_uint32, }, [BATADV_ATTR_BANDWIDTH_DOWN] = { .name = "bandwidth_down", .cb = nljson_print_uint32, }, [BATADV_ATTR_ROUTER] = { .name = "router", .cb = nljson_print_mac, }, [BATADV_ATTR_BLA_OWN] = { .name = "bla_own", .cb = nljson_print_flag, }, [BATADV_ATTR_BLA_ADDRESS] = { .name = "bla_address", .cb = nljson_print_mac, }, [BATADV_ATTR_BLA_VID] = { .name = "bla_vid", .cb = nljson_print_vlanid, }, [BATADV_ATTR_BLA_BACKBONE] = { .name = "bla_backbone", .cb = nljson_print_mac, }, [BATADV_ATTR_BLA_CRC] = { .name = "bla_crc", .cb = nljson_print_uint16, }, [BATADV_ATTR_DAT_CACHE_IP4ADDRESS] = { .name = "dat_cache_ip4address", .cb = nljson_print_ipv4, }, [BATADV_ATTR_DAT_CACHE_HWADDRESS] = { .name = "dat_cache_hwaddress", .cb = nljson_print_mac, }, [BATADV_ATTR_DAT_CACHE_VID] = { .name = "dat_cache_vid", .cb = nljson_print_vlanid, }, [BATADV_ATTR_MCAST_FLAGS] = { .name = "mcast_flags", .cb = nljson_print_mcastflags, }, [BATADV_ATTR_MCAST_FLAGS_PRIV] = { .name = "mcast_flags_priv", .cb = nljson_print_mcastflags_priv, }, [BATADV_ATTR_VLANID] = { .name = "vlanid", .cb = nljson_print_uint16, }, [BATADV_ATTR_AGGREGATED_OGMS_ENABLED] = { .name = "aggregated_ogms_enabled", .cb = nljson_print_bool, }, [BATADV_ATTR_AP_ISOLATION_ENABLED] = { .name = "ap_isolation_enabled", .cb = nljson_print_bool, }, [BATADV_ATTR_ISOLATION_MARK] = { .name = "isolation_mark", .cb = nljson_print_uint32, }, [BATADV_ATTR_ISOLATION_MASK] = { .name = "isolation_mask", .cb = nljson_print_uint32, }, [BATADV_ATTR_BONDING_ENABLED] = { .name = "bonding_enabled", .cb = nljson_print_bool, }, [BATADV_ATTR_BRIDGE_LOOP_AVOIDANCE_ENABLED] = { .name = "bridge_loop_avoidance_enabled", .cb = nljson_print_bool, }, [BATADV_ATTR_DISTRIBUTED_ARP_TABLE_ENABLED] = { .name = "distributed_arp_table_enabled", .cb = nljson_print_bool, }, [BATADV_ATTR_FRAGMENTATION_ENABLED] = { .name = "fragmentation_enabled", .cb = nljson_print_bool, }, [BATADV_ATTR_GW_BANDWIDTH_DOWN] = { .name = "gw_bandwidth_down", .cb = nljson_print_uint32, }, [BATADV_ATTR_GW_BANDWIDTH_UP] = { .name = "gw_bandwidth_up", .cb = nljson_print_uint32, }, [BATADV_ATTR_GW_MODE] = { .name = "gw_mode", .cb = nljson_print_gwmode, }, [BATADV_ATTR_GW_SEL_CLASS] = { .name = "gw_sel_class", .cb = nljson_print_uint32, }, [BATADV_ATTR_HOP_PENALTY] = { .name = "hop_penalty", .cb = nljson_print_uint8, }, [BATADV_ATTR_LOG_LEVEL] = { .name = "log_level", .cb = nljson_print_loglevel, }, [BATADV_ATTR_MULTICAST_FORCEFLOOD_ENABLED] = { .name = "multicast_forceflood_enabled", .cb = nljson_print_bool, }, [BATADV_ATTR_NETWORK_CODING_ENABLED] = { .name = "network_coding_enabled", .cb = nljson_print_bool, }, [BATADV_ATTR_ORIG_INTERVAL] = { .name = "orig_interval", .cb = nljson_print_uint32, }, [BATADV_ATTR_ELP_INTERVAL] = { .name = "elp_interval", .cb = nljson_print_uint32, }, [BATADV_ATTR_THROUGHPUT_OVERRIDE] = { .name = "throughput_override", .cb = nljson_print_uint32, }, [BATADV_ATTR_MULTICAST_FANOUT] = { .name = "multicast_fanout", .cb = nljson_print_uint32, }, }; void netlink_print_json_entries(struct nlattr *attrs[], struct json_opts *json_opts) { bool first_valid_attr = true; int i; if (!json_opts->is_first) putchar(','); else json_opts->is_first = false; putchar('{'); for (i = 0; i < BATADV_ATTR_MAX + 1; i++) { if (!attrs[i]) continue; if (!batadv_genl_json[i].cb) continue; if (!first_valid_attr) putchar(','); else first_valid_attr = false; putchar('"'); sanitize_string(batadv_genl_json[i].name); putchar('"'); putchar(':'); batadv_genl_json[i].cb(attrs, i); } putchar('}'); } static void json_query_usage(struct state *state) { fprintf(stderr, "Usage: batctl [options] %s|%s [parameters]\n", state->cmd->name, state->cmd->abbr); fprintf(stderr, "parameters:\n"); fprintf(stderr, " \t -h print this help\n"); } static int netlink_print_query_json_cb(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct nlquery_opts *query_opts = arg; struct json_opts *json_opts; struct genlmsghdr *ghdr; json_opts = container_of(query_opts, struct json_opts, query_opts); if (!genlmsg_valid_hdr(nlh, 0)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } ghdr = nlmsg_data(nlh); if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } netlink_print_json_entries(attrs, json_opts); return NL_OK; } static int netlink_print_query_json_attributes(struct nl_msg *msg, void *arg) { struct state *state = arg; switch (state->selector) { case SP_NONE_OR_MESHIF: case SP_MESHIF: break; case SP_VLAN: nla_put_u16(msg, BATADV_ATTR_VLANID, state->vid); break; case SP_HARDIF: nla_put_u32(msg, BATADV_ATTR_HARD_IFINDEX, state->hif); break; } return 0; } static int netlink_print_query_json(struct state *state, struct json_query_data *json_query) { struct json_opts json_opts = { .is_first = true, .query_opts = { .err = 0, }, }; int ret; if (json_query->nlm_flags & NLM_F_DUMP) putchar('['); ret = netlink_query_common(state, state->mesh_ifindex, json_query->cmd, netlink_print_query_json_cb, netlink_print_query_json_attributes, json_query->nlm_flags, &json_opts.query_opts); if (json_query->nlm_flags & NLM_F_DUMP) puts("]"); else putchar('\n'); return ret; } int handle_json_query(struct state *state, int argc, char **argv) { struct json_query_data *json_query = state->cmd->arg; int optchar; int err; while ((optchar = getopt(argc, argv, "h")) != -1) { switch (optchar) { case 'h': json_query_usage(state); return EXIT_SUCCESS; } } err = netlink_print_query_json(state, json_query); return err; } batctl-2025.1/genl_json.h000066400000000000000000000012061500012142700151720ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) B.A.T.M.A.N. contributors: * * Alexander Sarmanow * * License-Filename: LICENSES/preferred/GPL-2.0 */ #ifndef _BATCTL_GENLJSON_H #define _BATCTL_GENLJSON_H #include #include "batman_adv.h" #include "netlink.h" struct json_opts { uint8_t is_first:1; struct nlquery_opts query_opts; }; struct json_query_data { int nlm_flags; enum batadv_nl_commands cmd; }; void netlink_print_json_entries(struct nlattr *attrs[], struct json_opts *json_opts); int handle_json_query(struct state *state, int argc, char **argv); #endif /* _BATCTL_GENLJSON_H */ batctl-2025.1/gw_mode.c000066400000000000000000000155061500012142700146400ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include #include "batman_adv.h" #include "functions.h" #include "main.h" #include "netlink.h" #include "sys.h" #define SYS_GW_MODE "gw_mode" #define SYS_GW_SEL "gw_sel_class" #define SYS_GW_BW "gw_bandwidth" static struct gw_data { uint8_t bandwidth_down_found:1; uint8_t bandwidth_up_found:1; uint8_t sel_class_found:1; uint8_t mode; uint32_t bandwidth_down; uint32_t bandwidth_up; uint32_t sel_class; } gw_globals; static void gw_mode_usage(void) { fprintf(stderr, "Usage: batctl [options] gw_mode [mode] [sel_class|bandwidth]\n"); fprintf(stderr, "options:\n"); fprintf(stderr, " \t -h print this help\n"); } static bool is_throughput_select_class(struct state *state) { char algoname[32]; int ret; ret = get_algoname(state, state->mesh_ifindex, algoname, sizeof(algoname)); /* no algo name -> assume that it is a pre-B.A.T.M.A.N. V version */ if (ret < 0) return false; if (strcmp(algoname, "BATMAN_V") == 0) return true; return false; } static int parse_gw_limit(char *buff) { char *slash_ptr; bool ret; slash_ptr = strchr(buff, '/'); if (slash_ptr) *slash_ptr = 0; ret = parse_throughput(buff, "download gateway speed", &gw_globals.bandwidth_down); if (!ret) return -EINVAL; gw_globals.bandwidth_down_found = 1; /* we also got some upload info */ if (slash_ptr) { ret = parse_throughput(slash_ptr + 1, "upload gateway speed", &gw_globals.bandwidth_up); if (!ret) return -EINVAL; gw_globals.bandwidth_up_found = 1; } return 0; } static int parse_gw(struct state *state, int argc, char *argv[]) { char buff[256]; char *endptr; int ret; if (argc != 2 && argc != 3) { fprintf(stderr, "Error - incorrect number of arguments (expected 1/2)\n"); return -EINVAL; } if (strcmp(argv[1], "client") == 0) { gw_globals.mode = BATADV_GW_MODE_CLIENT; } else if (strcmp(argv[1], "server") == 0) { gw_globals.mode = BATADV_GW_MODE_SERVER; } else if (strcmp(argv[1], "off") == 0) { gw_globals.mode = BATADV_GW_MODE_OFF; } else { fprintf(stderr, "Error - the supplied argument is invalid: %s\n", argv[1]); fprintf(stderr, "The following values are allowed:\n"); fprintf(stderr, " * off\n"); fprintf(stderr, " * client\n"); fprintf(stderr, " * server\n"); return -EINVAL; } if (argc <= 2) return 0; strncpy(buff, argv[2], sizeof(buff)); buff[sizeof(buff) - 1] = '\0'; switch (gw_globals.mode) { case BATADV_GW_MODE_OFF: fprintf(stderr, "Error - unexpected argument for mode \"off\": %s\n", argv[2]); return -EINVAL; case BATADV_GW_MODE_CLIENT: if (is_throughput_select_class(state)) { if (!parse_throughput(buff, "sel_class", &gw_globals.sel_class)) return -EINVAL; } else { gw_globals.sel_class = strtoul(buff, &endptr, 0); if (!endptr || *endptr != '\0') { fprintf(stderr, "Error - unexpected argument for mode \"client\": %s\n", buff); return -EINVAL; } } gw_globals.sel_class_found = 1; break; case BATADV_GW_MODE_SERVER: ret = parse_gw_limit(buff); if (ret < 0) return ret; break; } return 0; } static int print_gw(struct nl_msg *msg, void *arg) { static const int mandatory[] = { BATADV_ATTR_GW_MODE, }; static const int mandatory_client[] = { BATADV_ATTR_ALGO_NAME, BATADV_ATTR_GW_SEL_CLASS, }; static const int mandatory_server[] = { BATADV_ATTR_GW_BANDWIDTH_DOWN, BATADV_ATTR_GW_BANDWIDTH_UP, }; struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct genlmsghdr *ghdr; int *result = arg; const char *algo; uint8_t gw_mode; uint32_t down; uint32_t val; uint32_t up; if (!genlmsg_valid_hdr(nlh, 0)) return NL_OK; ghdr = nlmsg_data(nlh); if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { return NL_OK; } /* ignore entry when attributes are missing */ if (missing_mandatory_attrs(attrs, mandatory, ARRAY_SIZE(mandatory))) return NL_OK; gw_mode = nla_get_u8(attrs[BATADV_ATTR_GW_MODE]); switch (gw_mode) { case BATADV_GW_MODE_OFF: printf("off\n"); break; case BATADV_GW_MODE_CLIENT: if (missing_mandatory_attrs(attrs, mandatory_client, ARRAY_SIZE(mandatory_client))) return NL_OK; algo = nla_data(attrs[BATADV_ATTR_ALGO_NAME]); val = nla_get_u32(attrs[BATADV_ATTR_GW_SEL_CLASS]); if (strcmp(algo, "BATMAN_V") == 0) printf("client (selection class: %u.%01u MBit)\n", val / 10, val % 10); else printf("client (selection class: %u)\n", val); break; case BATADV_GW_MODE_SERVER: if (missing_mandatory_attrs(attrs, mandatory_server, ARRAY_SIZE(mandatory_server))) return NL_OK; down = nla_get_u32(attrs[BATADV_ATTR_GW_BANDWIDTH_DOWN]); up = nla_get_u32(attrs[BATADV_ATTR_GW_BANDWIDTH_UP]); printf("server (announced bw: %u.%01u/%u.%01u MBit)\n", down / 10, down % 10, up / 10, up % 10); break; default: printf("unknown\n"); break; } *result = 0; return NL_STOP; } static int get_gw(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_GET_MESH, NULL, print_gw); } static int set_attrs_gw(struct nl_msg *msg, void *arg __maybe_unused) { nla_put_u8(msg, BATADV_ATTR_GW_MODE, gw_globals.mode); if (gw_globals.bandwidth_down_found) nla_put_u32(msg, BATADV_ATTR_GW_BANDWIDTH_DOWN, gw_globals.bandwidth_down); if (gw_globals.bandwidth_up_found) nla_put_u32(msg, BATADV_ATTR_GW_BANDWIDTH_UP, gw_globals.bandwidth_up); if (gw_globals.sel_class_found) nla_put_u32(msg, BATADV_ATTR_GW_SEL_CLASS, gw_globals.sel_class); return 0; } static int set_gw(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_SET_MESH, set_attrs_gw, NULL); } static int gw_read_setting(struct state *state) { int res; res = get_gw(state); if (res < 0) return EXIT_FAILURE; else return EXIT_SUCCESS; } static int gw_write_setting(struct state *state) { int res = EXIT_FAILURE; res = set_gw(state); if (res < 0) return EXIT_FAILURE; else return EXIT_SUCCESS; } static int gw_mode(struct state *state, int argc, char **argv) { int res = EXIT_FAILURE; int optchar; while ((optchar = getopt(argc, argv, "h")) != -1) { switch (optchar) { case 'h': gw_mode_usage(); return EXIT_SUCCESS; default: gw_mode_usage(); return EXIT_FAILURE; } } if (argc == 1) return gw_read_setting(state); res = parse_gw(state, argc, argv); if (res < 0) return EXIT_FAILURE; return gw_write_setting(state); } COMMAND(SUBCOMMAND_MIF, gw_mode, "gw", COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, NULL, "[mode] \tdisplay or modify the gateway mode"); batctl-2025.1/hardif_json.c000066400000000000000000000007471500012142700155060ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Sven Eckelmann #include /* clears the hash */ void hash_init(struct hashtable_t *hash) { int i; hash->elements = 0; for (i = 0; i < hash->size; i++) hash->table[i] = NULL; } /* remove the hash structure. if hashdata_free_cb != NULL, * this function will be called to remove the elements inside of the hash. * if you don't remove the elements, memory might be leaked. */ void hash_delete(struct hashtable_t *hash, hashdata_free_cb free_cb) { struct element_t *last_bucket; struct element_t *bucket; int i; for (i = 0; i < hash->size; i++) { bucket = hash->table[i]; while (bucket) { if (free_cb) free_cb(bucket->data); last_bucket = bucket; bucket = bucket->next; free(last_bucket); } } hash_destroy(hash); } /* adds data to the hashtable and reuse bucket. * returns 0 on success, -1 on error */ static int hash_add_bucket(struct hashtable_t *hash, void *data, struct element_t *bucket, int check_duplicate) { struct element_t *prev_bucket = NULL; struct element_t *bucket_it; int index; index = hash->choose(data, hash->size); bucket_it = hash->table[index]; while (bucket_it) { if (check_duplicate && hash->compare(bucket_it->data, data)) return -1; prev_bucket = bucket_it; bucket_it = bucket_it->next; } /* init the new bucket */ bucket->data = data; bucket->next = NULL; /* and link it */ if (!prev_bucket) hash->table[index] = bucket; else prev_bucket->next = bucket; hash->elements++; return 0; } /* free only the hashtable and the hash itself. */ void hash_destroy(struct hashtable_t *hash) { free(hash->table); free(hash); } /* free hash_it_t pointer when stopping hash_iterate early */ void hash_iterate_free(struct hash_it_t *iter_in) { free(iter_in); } /* iterate though the hash. first element is selected with iter_in NULL. * use the returned iterator to access the elements until hash_it_t returns * NULL. */ struct hash_it_t *hash_iterate(struct hashtable_t *hash, struct hash_it_t *iter_in) { struct hash_it_t *iter; if (!iter_in) { iter = malloc(sizeof(*iter)); if (!iter) return NULL; iter->index = -1; iter->bucket = NULL; iter->prev_bucket = NULL; } else { iter = iter_in; } /* sanity checks first (if our bucket got deleted in the last * iteration): */ if (iter->bucket) { if (iter->first_bucket) { /* we're on the first element and it got removed after * the last iteration. */ if ((*iter->first_bucket) != iter->bucket) { /* there are still other elements in the list */ if ((*iter->first_bucket) != NULL) { iter->prev_bucket = NULL; iter->bucket = (*iter->first_bucket); iter->first_bucket = &hash->table[iter->index]; return iter; } iter->bucket = NULL; } } else if (iter->prev_bucket) { /* we're not on the first element, and the bucket got * removed after the last iteration. The last bucket's * next pointer is not pointing to our actual bucket * anymore. Select the next. */ if (iter->prev_bucket->next != iter->bucket) iter->bucket = iter->prev_bucket; } } /* now as we are sane, select the next one if there is some */ if (iter->bucket) { if (iter->bucket->next) { iter->prev_bucket = iter->bucket; iter->bucket = iter->bucket->next; iter->first_bucket = NULL; return iter; } } /* if not returned yet, we've reached the last one on the index and * have to search forward */ iter->index++; /* go through the entries of the hash table */ while (iter->index < hash->size) { if (!hash->table[iter->index]) { iter->index++; continue; } iter->prev_bucket = NULL; iter->bucket = hash->table[iter->index]; iter->first_bucket = &hash->table[iter->index]; return iter; /* if this table entry is not null, return it */ } /* nothing to iterate over anymore */ hash_iterate_free(iter); return NULL; } /* allocates and clears the hash */ struct hashtable_t *hash_new(int size, hashdata_compare_cb compare, hashdata_choose_cb choose) { struct hashtable_t *hash; hash = malloc(sizeof(*hash)); if (!hash) return NULL; hash->size = size; hash->table = calloc(size, sizeof(struct element_t *)); if (!hash->table) { free(hash); return NULL; } hash_init(hash); hash->compare = compare; hash->choose = choose; return hash; } /* adds data to the hashtable. returns 0 on success, -1 on error */ int hash_add(struct hashtable_t *hash, void *data) { struct element_t *bucket; int ret; /* found the tail of the list, add new element */ bucket = malloc(sizeof(*bucket)); if (!bucket) return -1; ret = hash_add_bucket(hash, data, bucket, 1); if (ret < 0) free(bucket); return ret; } /* finds data, based on the key in keydata. returns the found data on success, * or NULL on error */ void *hash_find(struct hashtable_t *hash, void *keydata) { struct element_t *bucket; int index; index = hash->choose(keydata, hash->size); bucket = hash->table[index]; while (bucket) { if (hash->compare(bucket->data, keydata)) return bucket->data; bucket = bucket->next; } return NULL; } /* remove bucket (this might be used in hash_iterate() if you already found * the bucket you want to delete and don't need the overhead to find it again * with hash_remove(). But usually, you don't want to use this function, as it * fiddles with hash-internals. */ void *hash_remove_bucket(struct hashtable_t *hash, struct hash_it_t *hash_it_t) { void *data_save; /* save the pointer to the data */ data_save = hash_it_t->bucket->data; if (hash_it_t->prev_bucket) hash_it_t->prev_bucket->next = hash_it_t->bucket->next; else if (hash_it_t->first_bucket) (*hash_it_t->first_bucket) = hash_it_t->bucket->next; free(hash_it_t->bucket); hash->elements--; return data_save; } /* removes data from hash, if found. returns pointer do data on success, * so you can remove the used structure yourself, or NULL on error . * data could be the structure you use with just the key filled, * we just need the key for comparing. */ void *hash_remove(struct hashtable_t *hash, void *data) { struct hash_it_t hash_it_t; hash_it_t.index = hash->choose(data, hash->size); hash_it_t.bucket = hash->table[hash_it_t.index]; hash_it_t.prev_bucket = NULL; while (hash_it_t.bucket) { if (hash->compare(hash_it_t.bucket->data, data)) { int bucket_same; bucket_same = (hash_it_t.bucket == hash->table[hash_it_t.index]); hash_it_t.first_bucket = (bucket_same ? &hash->table[hash_it_t.index] : NULL); return hash_remove_bucket(hash, &hash_it_t); } hash_it_t.prev_bucket = hash_it_t.bucket; hash_it_t.bucket = hash_it_t.bucket->next; } return NULL; } /* resize the hash, returns the pointer to the new hash or NULL on error. * removes the old hash on success. */ struct hashtable_t *hash_resize(struct hashtable_t *hash, int size) { struct hashtable_t *new_hash; struct element_t *bucket; int i; /* initialize a new hash with the new size */ new_hash = hash_new(size, hash->compare, hash->choose); if (!new_hash) return NULL; /* copy the elements */ for (i = 0; i < hash->size; i++) { while (hash->table[i]) { bucket = hash->table[i]; hash->table[i] = bucket->next; hash_add_bucket(new_hash, bucket->data, bucket, 0); } } /* remove hash and eventual overflow buckets but not the * content itself. */ hash_delete(hash, NULL); return new_hash; } batctl-2025.1/hash.h000066400000000000000000000064211500012142700141430ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) B.A.T.M.A.N. contributors: * * Simon Wunderlich, Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #ifndef _BATMAN_HASH_H #define _BATMAN_HASH_H typedef int (*hashdata_compare_cb)(void *, void *); typedef int (*hashdata_choose_cb)(void *, int); typedef void (*hashdata_free_cb)(void *); struct element_t { void *data; /* pointer to the data */ struct element_t *next; /* overflow bucket pointer */ }; struct hash_it_t { int index; struct element_t *bucket; struct element_t *prev_bucket; struct element_t **first_bucket; }; struct hashtable_t { struct element_t **table; /* the hashtable itself, with the * buckets */ int elements; /* number of elements registered */ int size; /* size of hashtable */ hashdata_compare_cb compare; /* callback to a compare function. * should compare 2 element datas for * their keys, return 0 if same and not * 0 if not same */ hashdata_choose_cb choose; /* the hashfunction, should return an * index based on the key in the data * of the first argument and the size * the second */ }; /* clears the hash */ void hash_init(struct hashtable_t *hash); /* allocates and clears the hash */ struct hashtable_t *hash_new(int size, hashdata_compare_cb compare, hashdata_choose_cb choose); /* remove bucket (this might be used in hash_iterate() if you already found * the bucket you want to delete and don't need the overhead to find it again * with hash_remove(). But usually, you don't want to use this function, as it * fiddles with hash-internals. */ void *hash_remove_bucket(struct hashtable_t *hash, struct hash_it_t *hash_it_t); /* remove the hash structure. if hashdata_free_cb != NULL, * this function will be called to remove the elements inside of the hash. * if you don't remove the elements, memory might be leaked. */ void hash_delete(struct hashtable_t *hash, hashdata_free_cb free_cb); /* free only the hashtable and the hash itself. */ void hash_destroy(struct hashtable_t *hash); /* adds data to the hashtable. returns 0 on success, -1 on error */ int hash_add(struct hashtable_t *hash, void *data); /* removes data from hash, if found. returns pointer do data on success, * so you can remove the used structure yourself, or NULL on error . * data could be the structure you use with just the key filled, * we just need the key for comparing. */ void *hash_remove(struct hashtable_t *hash, void *data); /* finds data, based on the key in keydata. returns the found data on success, * or NULL on error */ void *hash_find(struct hashtable_t *hash, void *keydata); /* resize the hash, returns the pointer to the new hash or NULL on error. * removes the old hash on success */ struct hashtable_t *hash_resize(struct hashtable_t *hash, int size); /* print the hash table for debugging */ void hash_debug(struct hashtable_t *hash); /* iterate though the hash. first element is selected with iter_in NULL. * use the returned iterator to access the elements until hash_it_t * returns NULL. */ struct hash_it_t *hash_iterate(struct hashtable_t *hash, struct hash_it_t *iter_in); /* free hash_it_t pointer when stopping hash_iterate early */ void hash_iterate_free(struct hash_it_t *iter_in); #endif batctl-2025.1/hop_penalty.c000066400000000000000000000071071500012142700155370ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include "main.h" #include "sys.h" static struct hop_penalty_data { uint8_t hop_penalty; } hop_penalty; static int parse_hop_penalty(struct state *state, int argc, char *argv[]) { struct settings_data *settings = state->cmd->arg; struct hop_penalty_data *data = settings->data; char *endptr; if (argc != 2) { fprintf(stderr, "Error - incorrect number of arguments (expected 1)\n"); return -EINVAL; } data->hop_penalty = strtoul(argv[1], &endptr, 0); if (!endptr || *endptr != '\0') { fprintf(stderr, "Error - the supplied argument is invalid: %s\n", argv[1]); return -EINVAL; } return 0; } static int print_hop_penalty(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct genlmsghdr *ghdr; int *result = arg; if (!genlmsg_valid_hdr(nlh, 0)) return NL_OK; ghdr = nlmsg_data(nlh); if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { return NL_OK; } if (!attrs[BATADV_ATTR_HOP_PENALTY]) return NL_OK; printf("%u\n", nla_get_u8(attrs[BATADV_ATTR_HOP_PENALTY])); *result = 0; return NL_STOP; } static int get_hop_penalty(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_GET_MESH, NULL, print_hop_penalty); } static int get_attrs_hop_penalty_if(struct nl_msg *msg, void *arg) { struct state *state = arg; nla_put_u32(msg, BATADV_ATTR_HARD_IFINDEX, state->hif); return 0; } static int get_hop_penalty_if(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_GET_HARDIF, get_attrs_hop_penalty_if, print_hop_penalty); } static int set_attrs_hop_penalty(struct nl_msg *msg, void *arg) { struct state *state = arg; struct settings_data *settings = state->cmd->arg; struct hop_penalty_data *data = settings->data; nla_put_u8(msg, BATADV_ATTR_HOP_PENALTY, data->hop_penalty); return 0; } static int set_hop_penalty(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_SET_MESH, set_attrs_hop_penalty, NULL); } static int set_attrs_hop_penalty_if(struct nl_msg *msg, void *arg) { struct state *state = arg; struct settings_data *settings = state->cmd->arg; struct hop_penalty_data *data = settings->data; nla_put_u32(msg, BATADV_ATTR_HARD_IFINDEX, state->hif); nla_put_u8(msg, BATADV_ATTR_HOP_PENALTY, data->hop_penalty); return 0; } static int set_hop_penalty_if(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_SET_HARDIF, set_attrs_hop_penalty_if, NULL); } static struct settings_data batctl_settings_hop_penalty = { .data = &hop_penalty, .parse = parse_hop_penalty, .netlink_get = get_hop_penalty, .netlink_set = set_hop_penalty, }; static struct settings_data batctl_settings_hop_penalty_if = { .data = &hop_penalty, .parse = parse_hop_penalty, .netlink_get = get_hop_penalty_if, .netlink_set = set_hop_penalty_if, }; COMMAND_NAMED(SUBCOMMAND_MIF, hop_penalty, "hp", handle_sys_setting, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_settings_hop_penalty, "[penalty] \tdisplay or modify hop_penalty setting"); COMMAND_NAMED(SUBCOMMAND_HIF, hop_penalty, "hp", handle_sys_setting, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_settings_hop_penalty_if, "[penalty] \tdisplay or modify hop_penalty setting"); batctl-2025.1/icmp_helper.c000066400000000000000000000277751500012142700155210ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner , Simon Wunderlich * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include "icmp_helper.h" #include "main.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "batadv_packet_compat.h" #include "debug.h" #include "functions.h" #include "list.h" #include "netlink.h" #ifndef ETH_P_BATMAN #define ETH_P_BATMAN 0x4305 #endif /* ETH_P_BATMAN */ static LIST_HEAD(interface_list); static size_t direct_reply_len; static uint8_t uid; static uint8_t primary_mac[ETH_ALEN]; static uint8_t icmp_buffer[BATADV_ICMP_MAX_PACKET_SIZE]; #define BATADV_ICMP_MIN_PACKET_SIZE sizeof(struct batadv_icmp_packet) #define BADADV_ICMP_ETH_OFFSET(member) \ (ETH_HLEN + offsetof(struct batadv_icmp_packet, member)) static struct icmp_interface *icmp_interface_find(const char *ifname) { struct icmp_interface *found = NULL; struct icmp_interface *iface; list_for_each_entry(iface, &interface_list, list) { if (strcmp(iface->name, ifname) == 0) { found = iface; break; } } return found; } static bool icmp_interfaces_is_my_mac(uint8_t dst[ETH_ALEN]) { struct icmp_interface *iface; list_for_each_entry(iface, &interface_list, list) { if (memcmp(iface->mac, dst, ETH_ALEN) == 0) return true; } return false; } void icmp_interface_destroy(struct icmp_interface *iface) { close(iface->sock); list_del(&iface->list); free(iface); } static int icmp_interface_filter(int sock, int uid) { struct sock_fprog filter; struct sock_filter accept_icmp[] = { /* load ethernet proto */ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_header, ether_type)), /* jump to ret 0 when it is != 0x4305 */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETH_P_BATMAN, 0, 14), /* load pktlen */ BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* jump to ret 0 when it is < 34 */ BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, ETH_HLEN + BATADV_ICMP_MIN_PACKET_SIZE, 0, 12), /* jump to ret 0 when it is > 130 */ BPF_JUMP(BPF_JMP + BPF_JGT + BPF_K, ETH_HLEN + BATADV_ICMP_MAX_PACKET_SIZE, 11, 0), /* load batman-adv type */ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, BADADV_ICMP_ETH_OFFSET(packet_type)), /* jump to ret 0 when it is != BATADV_ICMP */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BATADV_ICMP, 0, 9), /* load batman-adv version */ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, BADADV_ICMP_ETH_OFFSET(version)), /* jump to ret 0 when it is != 15 */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BATADV_COMPAT_VERSION, 0, 7), /* load batman-adv icmp msg_type */ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, BADADV_ICMP_ETH_OFFSET(msg_type)), /* accept BATADV_ECHO_REPLY or go to next check */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BATADV_ECHO_REPLY, 2, 0), /* accept BATADV_DESTINATION_UNREACHABLE or go to next check */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BATADV_DESTINATION_UNREACHABLE, 1, 0), /* accept BATADV_TTL_EXCEEDED or go to ret 0 */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BATADV_TTL_EXCEEDED, 0, 3), /* load batman-adv icmp uid */ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, BADADV_ICMP_ETH_OFFSET(uid)), /* jump to ret 0 when it is not our uid */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, uid, 0, 1), /* accept 130 bytes */ BPF_STMT(BPF_RET + BPF_K, ETH_HLEN + BATADV_ICMP_MAX_PACKET_SIZE), /* ret 0 -> reject packet */ BPF_STMT(BPF_RET + BPF_K, 0), }; memset(&filter, 0, sizeof(filter)); filter.len = sizeof(accept_icmp) / sizeof(*accept_icmp); filter.filter = accept_icmp; if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) return -1; return 0; } static int icmp_interface_add(const char *ifname, const uint8_t mac[ETH_ALEN]) { struct icmp_interface *iface; struct sockaddr_ll sll; struct ifreq req; int ret; iface = malloc(sizeof(*iface)); if (!iface) return -ENOMEM; iface->mark = 1; memcpy(iface->mac, mac, ETH_ALEN); strncpy(iface->name, ifname, IFNAMSIZ); iface->name[sizeof(iface->name) - 1] = '\0'; iface->sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (iface->sock < 0) { perror("Error - can't create raw socket"); ret = -errno; goto free_iface; } memset(&req, 0, sizeof(struct ifreq)); strncpy(req.ifr_name, ifname, IFNAMSIZ); req.ifr_name[sizeof(req.ifr_name) - 1] = '\0'; ret = ioctl(iface->sock, SIOCGIFINDEX, &req); if (ret < 0) { perror("Error - can't create raw socket (SIOCGIFINDEX)"); ret = -errno; goto close_sock; } memset(&sll, 0, sizeof(sll)); sll.sll_family = AF_PACKET; sll.sll_protocol = htons(ETH_P_ALL); sll.sll_pkttype = PACKET_HOST; sll.sll_ifindex = req.ifr_ifindex; ret = bind(iface->sock, (struct sockaddr *)&sll, sizeof(struct sockaddr_ll)); if (ret < 0) { perror("Error - can't bind raw socket"); ret = -errno; goto close_sock; } ret = icmp_interface_filter(iface->sock, uid); if (ret < 0) { fprintf(stderr, "Error - can't add filter to raw socket: %s\n", strerror(-ret)); goto close_sock; } list_add(&iface->list, &interface_list); return 0; close_sock: close(iface->sock); free_iface: free(iface); return ret; } int icmp_interfaces_init(void) { get_random_bytes(&uid, 1); return 0; } static struct nla_policy link_policy[IFLA_MAX + 1] = { [IFLA_IFNAME] = { .type = NLA_STRING, .maxlen = IFNAMSIZ }, [IFLA_MASTER] = { .type = NLA_U32 }, [IFLA_ADDRESS] = { .type = NLA_UNSPEC, .minlen = ETH_ALEN, .maxlen = ETH_ALEN, }, }; struct icmp_interface_update_arg { int ifindex; }; static int icmp_interface_update_parse(struct nl_msg *msg, void *arg) { struct icmp_interface_update_arg *update_arg = arg; struct nlattr *attrs[IFLA_MAX + 1]; struct icmp_interface *iface; struct ifinfomsg *ifm; char *ifname; uint8_t *mac; int master; int ret; ifm = nlmsg_data(nlmsg_hdr(msg)); ret = nlmsg_parse(nlmsg_hdr(msg), sizeof(*ifm), attrs, IFLA_MAX, link_policy); if (ret < 0) goto err; if (!attrs[IFLA_IFNAME]) goto err; if (!attrs[IFLA_MASTER]) goto err; if (!attrs[IFLA_ADDRESS]) goto err; ifname = nla_get_string(attrs[IFLA_IFNAME]); master = nla_get_u32(attrs[IFLA_MASTER]); mac = nla_data(attrs[IFLA_ADDRESS]); /* required on older kernels which don't prefilter the results */ if (master != update_arg->ifindex) goto err; /* update or add interface */ iface = icmp_interface_find(ifname); if (!iface) { icmp_interface_add(ifname, mac); goto err; } /* update */ iface->mark = 1; memcpy(iface->mac, mac, ETH_ALEN); err: return NL_OK; } static void icmp_interface_unmark(void) { struct icmp_interface *iface; list_for_each_entry(iface, &interface_list, list) iface->mark = 0; } static void icmp_interface_sweep(void) { struct icmp_interface *iface, *safe; list_for_each_entry_safe(iface, safe, &interface_list, list) { if (iface->mark) continue; icmp_interface_destroy(iface); } } static int icmp_interface_update(struct state *state) { struct icmp_interface_update_arg update_arg; update_arg.ifindex = state->mesh_ifindex; /* unmark current interface - will be marked again by query */ icmp_interface_unmark(); query_rtnl_link(update_arg.ifindex, icmp_interface_update_parse, &update_arg); /* remove old interfaces */ icmp_interface_sweep(); get_primarymac_netlink(state, primary_mac); return 0; } static int icmp_interface_send(struct batadv_icmp_header *icmp_packet, size_t packet_len, struct icmp_interface *iface, uint8_t nexthop[ETH_ALEN]) { struct ether_header header; struct iovec vector[2]; header.ether_type = htons(ETH_P_BATMAN); memcpy(header.ether_shost, iface->mac, ETH_ALEN); memcpy(header.ether_dhost, nexthop, ETH_ALEN); vector[0].iov_base = &header; vector[0].iov_len = sizeof(struct ether_header); vector[1].iov_base = icmp_packet; vector[1].iov_len = packet_len; return (int)writev(iface->sock, vector, 2); } int icmp_interface_write(struct state *state, struct batadv_icmp_header *icmp_packet, size_t len) { struct batadv_icmp_packet_rr *icmp_packet_rr; struct icmp_interface *iface; uint8_t nexthop[ETH_ALEN]; char ifname[IF_NAMESIZE]; struct ether_addr mac; size_t packet_len; int ret; if (len < sizeof(*icmp_packet)) return -EINVAL; if (len >= BATADV_ICMP_MAX_PACKET_SIZE) packet_len = BATADV_ICMP_MAX_PACKET_SIZE; else packet_len = len; if (icmp_packet->packet_type != BATADV_ICMP) return -EINVAL; if (icmp_packet->msg_type != BATADV_ECHO_REQUEST) return -EINVAL; icmp_interface_update(state); if (list_empty(&interface_list)) return -EFAULT; /* find best neighbor */ memcpy(&mac, icmp_packet->dst, ETH_ALEN); ret = get_nexthop_netlink(state, &mac, nexthop, ifname); if (ret < 0) goto dst_unreachable; iface = icmp_interface_find(ifname); if (!iface) goto dst_unreachable; direct_reply_len = 0; icmp_packet->uid = uid; memcpy(icmp_packet->orig, primary_mac, ETH_ALEN); /* start RR packet */ icmp_packet_rr = (struct batadv_icmp_packet_rr *)icmp_packet; if (packet_len == sizeof(*icmp_packet_rr)) memcpy(icmp_packet_rr->rr[0], iface->mac, ETH_ALEN); return icmp_interface_send(icmp_packet, packet_len, iface, nexthop); dst_unreachable: memcpy(icmp_buffer, icmp_packet, packet_len); icmp_packet = (struct batadv_icmp_header *)icmp_buffer; icmp_packet->msg_type = BATADV_DESTINATION_UNREACHABLE; direct_reply_len = packet_len; return 0; } static int icmp_interface_preselect(fd_set *read_sockets) { struct icmp_interface *iface; int max = 0; FD_ZERO(read_sockets); list_for_each_entry(iface, &interface_list, list) { FD_SET(iface->sock, read_sockets); if (max <= iface->sock) max = iface->sock + 1; } return max; } static ssize_t icmp_interface_get_read_sock(fd_set *read_sockets, struct icmp_interface **piface) { struct icmp_interface *iface; int sock = -1; list_for_each_entry(iface, &interface_list, list) { if (!FD_ISSET(iface->sock, read_sockets)) continue; sock = iface->sock; *piface = iface; break; } return sock; } ssize_t icmp_interface_read(struct batadv_icmp_header *icmp_packet, size_t len, struct timeval *tv) { struct batadv_icmp_packet_rr *icmp_packet_rr; struct icmp_interface *iface; struct ether_header header; struct iovec vector[2]; fd_set read_sockets; size_t packet_len; ssize_t read_len; int read_sock; int max_sock; int res; if (len < sizeof(*icmp_packet)) return -EINVAL; if (len >= BATADV_ICMP_MAX_PACKET_SIZE) packet_len = BATADV_ICMP_MAX_PACKET_SIZE; else packet_len = len; if (direct_reply_len > 0) { memcpy(icmp_packet, icmp_buffer, packet_len); direct_reply_len = 0; return (ssize_t)packet_len; } retry: max_sock = icmp_interface_preselect(&read_sockets); res = select(max_sock, &read_sockets, NULL, NULL, tv); /* timeout, or < 0 error */ if (res <= 0) return res; read_sock = icmp_interface_get_read_sock(&read_sockets, &iface); if (read_sock < 0) return read_sock; vector[0].iov_base = &header; vector[0].iov_len = sizeof(struct ether_header); vector[1].iov_base = icmp_packet; vector[1].iov_len = packet_len; read_len = readv(read_sock, vector, 2); if (read_len < 0) return read_len; if (read_len < ETH_HLEN) goto retry; read_len -= ETH_HLEN; if (read_len < (ssize_t)sizeof(*icmp_packet)) goto retry; if (!icmp_interfaces_is_my_mac(icmp_packet->dst)) goto retry; /* end RR packet */ icmp_packet_rr = (struct batadv_icmp_packet_rr *)icmp_packet; if (read_len == sizeof(*icmp_packet_rr) && icmp_packet_rr->rr_cur < BATADV_RR_LEN) { memcpy(icmp_packet_rr->rr[icmp_packet_rr->rr_cur], iface->mac, ETH_ALEN); icmp_packet_rr->rr_cur++; } return read_len; } void icmp_interfaces_clean(void) { struct icmp_interface *iface; struct icmp_interface *safe; list_for_each_entry_safe(iface, safe, &interface_list, list) icmp_interface_destroy(iface); } batctl-2025.1/icmp_helper.h000066400000000000000000000016531500012142700155110ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) B.A.T.M.A.N. contributors: * * Andreas Langer , Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #ifndef _BATCTL_ICMP_HELPER_H #define _BATCTL_ICMP_HELPER_H #include "main.h" #include #include #include #include #include #include #include #include "list.h" struct timeval; struct batadv_icmp_header; struct icmp_interface { char name[IFNAMSIZ]; uint8_t mac[ETH_ALEN]; int sock; int mark; struct list_head list; }; int icmp_interfaces_init(void); int icmp_interface_write(struct state *state, struct batadv_icmp_header *icmp_packet, size_t len); void icmp_interfaces_clean(void); ssize_t icmp_interface_read(struct batadv_icmp_header *icmp_packet, size_t len, struct timeval *tv); #endif batctl-2025.1/interface.c000066400000000000000000000305321500012142700151530ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "main.h" #include "sys.h" #include "functions.h" #define IFACE_STATUS_LEN 256 static void interface_usage(void) { fprintf(stderr, "Usage: batctl [options] interface [parameters] [add|del iface(s)]\n"); fprintf(stderr, " batctl [options] interface [parameters] create [routing_algo|ra RA_NAME]\n"); fprintf(stderr, " batctl [options] interface [parameters] destroy\n"); fprintf(stderr, "parameters:\n"); fprintf(stderr, " \t -M disable automatic creation of batman-adv interface\n"); fprintf(stderr, " \t -h print this help\n"); } static int get_iface_status_netlink_parse(struct nl_msg *msg, void *arg) { struct nlattr *attrs[NUM_BATADV_ATTR]; struct nlmsghdr *nlh = nlmsg_hdr(msg); char *iface_status = arg; struct genlmsghdr *ghdr; if (!genlmsg_valid_hdr(nlh, 0)) return NL_OK; ghdr = nlmsg_data(nlh); if (ghdr->cmd != BATADV_CMD_GET_HARDIF) return NL_OK; if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) return NL_OK; if (attrs[BATADV_ATTR_ACTIVE]) strncpy(iface_status, "active\n", IFACE_STATUS_LEN); else strncpy(iface_status, "inactive\n", IFACE_STATUS_LEN); iface_status[IFACE_STATUS_LEN - 1] = '\0'; return NL_OK; } static char *get_iface_status_netlink(struct state *state, unsigned int hardif, char *iface_status) { char *ret_status = NULL; struct nl_msg *msg; int ret; iface_status[0] = '\0'; nl_cb_set(state->cb, NL_CB_VALID, NL_CB_CUSTOM, get_iface_status_netlink_parse, iface_status); msg = nlmsg_alloc(); if (!msg) return NULL; genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, state->batadv_family, 0, 0, BATADV_CMD_GET_HARDIF, 1); nla_put_u32(msg, BATADV_ATTR_MESH_IFINDEX, state->mesh_ifindex); nla_put_u32(msg, BATADV_ATTR_HARD_IFINDEX, hardif); ret = nl_send_auto_complete(state->sock, msg); if (ret < 0) goto err_free_msg; ret = nl_recvmsgs(state->sock, state->cb); if (ret < 0) goto err_free_msg; nl_wait_for_ack(state->sock); if (strlen(iface_status) > 0) ret_status = iface_status; err_free_msg: nlmsg_free(msg); return ret_status; } static struct nla_policy link_policy[IFLA_MAX + 1] = { [IFLA_IFNAME] = { .type = NLA_STRING, .maxlen = IFNAMSIZ }, [IFLA_MASTER] = { .type = NLA_U32 }, }; static int print_interfaces_rtnl_parse(struct nl_msg *msg, void *arg) { char iface_status[IFACE_STATUS_LEN]; struct nlattr *attrs[IFLA_MAX + 1]; struct state *state = arg; struct ifinfomsg *ifm; unsigned int master; const char *status; char *ifname; int ret; ifm = nlmsg_data(nlmsg_hdr(msg)); ret = nlmsg_parse(nlmsg_hdr(msg), sizeof(*ifm), attrs, IFLA_MAX, link_policy); if (ret < 0) goto err; if (!attrs[IFLA_IFNAME]) goto err; if (!attrs[IFLA_MASTER]) goto err; ifname = nla_get_string(attrs[IFLA_IFNAME]); master = nla_get_u32(attrs[IFLA_MASTER]); /* required on older kernels which don't prefilter the results */ if (master != state->mesh_ifindex) goto err; status = get_iface_status_netlink(state, ifm->ifi_index, iface_status); if (!status) status = "\n"; printf("%s: %s", ifname, status); err: return NL_OK; } static int print_interfaces(struct state *state) { int ret; if (!file_exists(module_ver_path)) { fprintf(stderr, "Error - batman-adv module has not been loaded\n"); return EXIT_FAILURE; } /* duplicated code here from the main() because interface doesn't always * need COMMAND_FLAG_MESH_IFACE and COMMAND_FLAG_NETLINK */ if (check_mesh_iface(state)) return EXIT_FAILURE; ret = netlink_create(state); if (ret < 0) return EXIT_FAILURE; query_rtnl_link(state->mesh_ifindex, print_interfaces_rtnl_parse, state); netlink_destroy(state); return EXIT_SUCCESS; } struct count_interfaces_rtnl_arg { int ifindex; unsigned int count; }; static int count_interfaces_rtnl_parse(struct nl_msg *msg, void *arg) { struct count_interfaces_rtnl_arg *count_arg = arg; struct nlattr *attrs[IFLA_MAX + 1]; struct ifinfomsg *ifm; int ret; int master; ifm = nlmsg_data(nlmsg_hdr(msg)); ret = nlmsg_parse(nlmsg_hdr(msg), sizeof(*ifm), attrs, IFLA_MAX, link_policy); if (ret < 0) goto err; if (!attrs[IFLA_IFNAME]) goto err; if (!attrs[IFLA_MASTER]) goto err; master = nla_get_u32(attrs[IFLA_MASTER]); /* required on older kernels which don't prefilter the results */ if (master != count_arg->ifindex) goto err; count_arg->count++; err: return NL_OK; } static unsigned int count_interfaces(char *mesh_iface) { struct count_interfaces_rtnl_arg count_arg; count_arg.count = 0; count_arg.ifindex = if_nametoindex(mesh_iface); if (!count_arg.ifindex) return 0; query_rtnl_link(count_arg.ifindex, count_interfaces_rtnl_parse, &count_arg); return count_arg.count; } struct interface_create_params { const char *routing_algo; }; static int interface_parse_create_params(int argc, char **argv, struct interface_create_params *create_params) { int pos = 1; while (pos < argc) { if (strcmp(argv[pos], "routing_algo") == 0 || strcmp(argv[pos], "ra") == 0) { pos++; if (pos >= argc) { fprintf(stderr, "Error - missing parameter for 'routing_algo'\n"); return -EINVAL; } create_params->routing_algo = argv[pos]; pos++; } else { fprintf(stderr, "Error - unknown parameter '%s'\n", argv[pos]); return -EINVAL; } } return 0; } static int create_interface(const char *mesh_iface, const struct interface_create_params *create_param) { struct ifinfomsg rt_hdr = { .ifi_family = IFLA_UNSPEC, }; struct nlattr *linkinfo; struct nlattr *linkdata; struct nl_msg *msg; int err = 0; int ret; msg = nlmsg_alloc_simple(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK); if (!msg) return -ENOMEM; ret = nlmsg_append(msg, &rt_hdr, sizeof(rt_hdr), NLMSG_ALIGNTO); if (ret < 0) { err = -ENOMEM; goto err_free_msg; } ret = nla_put_string(msg, IFLA_IFNAME, mesh_iface); if (ret < 0) { err = -ENOMEM; goto err_free_msg; } linkinfo = nla_nest_start(msg, IFLA_LINKINFO); if (!linkinfo) { err = -ENOMEM; goto err_free_msg; } ret = nla_put_string(msg, IFLA_INFO_KIND, "batadv"); if (ret < 0) { err = -ENOMEM; goto err_free_msg; } linkdata = nla_nest_start(msg, IFLA_INFO_DATA); if (!linkdata) { err = -ENOMEM; goto err_free_msg; } if (create_param->routing_algo) { ret = nla_put_string(msg, IFLA_BATADV_ALGO_NAME, create_param->routing_algo); if (ret < 0) { err = -ENOMEM; goto err_free_msg; } } nla_nest_end(msg, linkdata); nla_nest_end(msg, linkinfo); err = netlink_simple_request(msg); err_free_msg: nlmsg_free(msg); return err; } static int destroy_interface(const char *mesh_iface) { struct ifinfomsg rt_hdr = { .ifi_family = IFLA_UNSPEC, }; struct nl_msg *msg; int err = 0; int ret; msg = nlmsg_alloc_simple(RTM_DELLINK, NLM_F_REQUEST | NLM_F_ACK); if (!msg) return -ENOMEM; ret = nlmsg_append(msg, &rt_hdr, sizeof(rt_hdr), NLMSG_ALIGNTO); if (ret < 0) { err = -ENOMEM; goto err_free_msg; } ret = nla_put_string(msg, IFLA_IFNAME, mesh_iface); if (ret < 0) { err = -ENOMEM; goto err_free_msg; } err = netlink_simple_request(msg); err_free_msg: nlmsg_free(msg); return err; } static int set_master_interface(const char *iface, unsigned int ifmaster) { struct ifinfomsg rt_hdr = { .ifi_family = IFLA_UNSPEC, }; struct nl_msg *msg; int err = 0; int ret; msg = nlmsg_alloc_simple(RTM_SETLINK, NLM_F_REQUEST | NLM_F_ACK); if (!msg) return -ENOMEM; ret = nlmsg_append(msg, &rt_hdr, sizeof(rt_hdr), NLMSG_ALIGNTO); if (ret < 0) { err = -ENOMEM; goto err_free_msg; } ret = nla_put_string(msg, IFLA_IFNAME, iface); if (ret < 0) { err = -ENOMEM; goto err_free_msg; } ret = nla_put_u32(msg, IFLA_MASTER, ifmaster); if (ret < 0) { err = -ENOMEM; goto err_free_msg; } err = netlink_simple_request(msg); err_free_msg: nlmsg_free(msg); return err; } static int interface(struct state *state, int argc, char **argv) { struct interface_create_params create_params = {}; bool manual_mode = false; unsigned int ifmaster; unsigned int ifindex; unsigned int pre_cnt; const char *long_op; unsigned int cnt; char **rest_argv; int rest_argc; int optchar; int ret; int i; while ((optchar = getopt(argc, argv, "hM")) != -1) { switch (optchar) { case 'h': interface_usage(); return EXIT_SUCCESS; case 'M': manual_mode = true; break; default: interface_usage(); return EXIT_FAILURE; } } rest_argc = argc - optind; rest_argv = &argv[optind]; if (rest_argc == 0) return print_interfaces(state); if ((strcmp(rest_argv[0], "add") != 0) && (strcmp(rest_argv[0], "a") != 0) && (strcmp(rest_argv[0], "del") != 0) && (strcmp(rest_argv[0], "d") != 0) && (strcmp(rest_argv[0], "create") != 0) && (strcmp(rest_argv[0], "c") != 0) && (strcmp(rest_argv[0], "destroy") != 0) && (strcmp(rest_argv[0], "D") != 0)) { fprintf(stderr, "Error - unknown argument specified: %s\n", rest_argv[0]); interface_usage(); goto err; } if (strcmp(rest_argv[0], "destroy") == 0) rest_argv[0][0] = 'D'; switch (rest_argv[0][0]) { case 'a': case 'd': if (rest_argc == 1) { fprintf(stderr, "Error - missing interface name(s) after '%s'\n", rest_argv[0]); interface_usage(); goto err; } break; case 'D': if (rest_argc != 1) { fprintf(stderr, "Error - extra parameter after '%s'\n", rest_argv[0]); interface_usage(); goto err; } break; case 'c': ret = interface_parse_create_params(rest_argc, rest_argv, &create_params); if (ret) { interface_usage(); goto err; } default: break; } switch (rest_argv[0][0]) { case 'c': ret = create_interface(state->mesh_iface, &create_params); if (ret < 0) { fprintf(stderr, "Error - failed to add create batman-adv interface: %s\n", strerror(-ret)); goto err; } return EXIT_SUCCESS; case 'D': ret = destroy_interface(state->mesh_iface); if (ret < 0) { fprintf(stderr, "Error - failed to destroy batman-adv interface: %s\n", strerror(-ret)); goto err; } return EXIT_SUCCESS; default: break; } /* get index of batman-adv interface - or try to create it */ ifmaster = if_nametoindex(state->mesh_iface); if (!manual_mode && !ifmaster && rest_argv[0][0] == 'a') { ret = create_interface(state->mesh_iface, &create_params); if (ret < 0 && ret != -EEXIST) { fprintf(stderr, "Error - failed to create batman-adv interface: %s\n", strerror(-ret)); goto err; } ifmaster = if_nametoindex(state->mesh_iface); } if (!ifmaster) { ret = -ENODEV; fprintf(stderr, "Error - failed to find batman-adv interface: %s\n", strerror(-ret)); goto err; } /* make sure that batman-adv is loaded or was loaded by create_interface */ if (!file_exists(module_ver_path)) { fprintf(stderr, "Error - batman-adv module has not been loaded\n"); goto err; } pre_cnt = count_interfaces(state->mesh_iface); for (i = 1; i < rest_argc; i++) { ifindex = if_nametoindex(rest_argv[i]); if (!ifindex) { fprintf(stderr, "Error - interface does not exist: %s\n", rest_argv[i]); continue; } if (rest_argv[0][0] == 'a') ifindex = ifmaster; else ifindex = 0; ret = set_master_interface(rest_argv[i], ifindex); if (ret < 0) { if (rest_argv[0][0] == 'a') long_op = "add"; else long_op = "delete"; fprintf(stderr, "Error - failed to %s interface %s: %s\n", long_op, rest_argv[i], strerror(-ret)); goto err; } } /* check if there is no interface left and then destroy mesh_iface */ if (!manual_mode && rest_argv[0][0] == 'd') { cnt = count_interfaces(state->mesh_iface); if (cnt == 0 && pre_cnt > 0) fprintf(stderr, "Warning: %s has no interfaces and can be destroyed with: batctl meshif %s interface destroy\n", state->mesh_iface, state->mesh_iface); } return EXIT_SUCCESS; err: return EXIT_FAILURE; } COMMAND(SUBCOMMAND_MIF, interface, "if", 0, NULL, "[add|del iface(s)]\tdisplay or modify the interface settings"); batctl-2025.1/isolation_mark.c000066400000000000000000000067531500012142700162360ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Antonio Quartulli * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include "main.h" #include "sys.h" static struct isolation_mark_data { uint32_t isolation_mark; uint32_t isolation_mask; } isolation_mark; static int parse_isolation_mark(struct state *state, int argc, char *argv[]) { struct settings_data *settings = state->cmd->arg; struct isolation_mark_data *data = settings->data; char *mask_ptr; char buff[256]; uint32_t mark; uint32_t mask; char *endptr; if (argc != 2) { fprintf(stderr, "Error - incorrect number of arguments (expected 1)\n"); return -EINVAL; } strncpy(buff, argv[1], sizeof(buff)); buff[sizeof(buff) - 1] = '\0'; /* parse the mask if it has been specified, otherwise assume the mask is * the biggest possible */ mask = 0xFFFFFFFF; mask_ptr = strchr(buff, '/'); if (mask_ptr) { *mask_ptr = '\0'; mask_ptr++; /* the mask must be entered in hex base as it is going to be a * bitmask and not a prefix length */ mask = strtoul(mask_ptr, &endptr, 16); if (!endptr || *endptr != '\0') goto inval_format; } /* the mark can be entered in any base */ mark = strtoul(buff, &endptr, 0); if (!endptr || *endptr != '\0') goto inval_format; data->isolation_mask = mask; /* erase bits not covered by the mask */ data->isolation_mark = mark & mask; return 0; inval_format: fprintf(stderr, "Error - incorrect number of arguments (expected 1)\n"); fprintf(stderr, "The following formats for mark(/mask) are allowed:\n"); fprintf(stderr, " * 0x12345678\n"); fprintf(stderr, " * 0x12345678/0xabcdef09\n"); return -EINVAL; } static int print_isolation_mark(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct genlmsghdr *ghdr; int *result = arg; if (!genlmsg_valid_hdr(nlh, 0)) return NL_OK; ghdr = nlmsg_data(nlh); if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { return NL_OK; } if (!attrs[BATADV_ATTR_ISOLATION_MARK] || !attrs[BATADV_ATTR_ISOLATION_MASK]) return NL_OK; printf("0x%08x/0x%08x\n", nla_get_u32(attrs[BATADV_ATTR_ISOLATION_MARK]), nla_get_u32(attrs[BATADV_ATTR_ISOLATION_MASK])); *result = 0; return NL_STOP; } static int get_isolation_mark(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_GET_MESH, NULL, print_isolation_mark); } static int set_attrs_isolation_mark(struct nl_msg *msg, void *arg) { struct state *state = arg; struct settings_data *settings = state->cmd->arg; struct isolation_mark_data *data = settings->data; nla_put_u32(msg, BATADV_ATTR_ISOLATION_MARK, data->isolation_mark); nla_put_u32(msg, BATADV_ATTR_ISOLATION_MASK, data->isolation_mask); return 0; } static int set_isolation_mark(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_SET_MESH, set_attrs_isolation_mark, NULL); } static struct settings_data batctl_settings_isolation_mark = { .data = &isolation_mark, .parse = parse_isolation_mark, .netlink_get = get_isolation_mark, .netlink_set = set_isolation_mark, }; COMMAND_NAMED(SUBCOMMAND_MIF, isolation_mark, "mark", handle_sys_setting, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_settings_isolation_mark, "[mark] \tdisplay or modify isolation_mark setting"); batctl-2025.1/list.h000066400000000000000000000630511500012142700141750ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Minimal Linux-like double-linked list helper functions * * SPDX-FileCopyrightText: Sven Eckelmann */ #ifndef __LINUX_LIKE_LIST_H__ #define __LINUX_LIKE_LIST_H__ #ifdef __cplusplus extern "C" { #endif #include #if defined(__GNUC__) #define LIST_TYPEOF_USE 1 #endif #if defined(_MSC_VER) #define inline __inline #endif /** * container_of() - Calculate address of object that contains address ptr * @ptr: pointer to member variable * @type: type of the structure containing ptr * @member: name of the member variable in struct @type * * Return: @type pointer of object containing ptr */ #ifndef container_of #ifdef LIST_TYPEOF_USE #define container_of(ptr, type, member) __extension__ ({ \ const __typeof__(((type *)0)->member) *__pmember = (ptr); \ (type *)((char *)__pmember - offsetof(type, member)); }) #else #define container_of(ptr, type, member) \ ((type *)((char *)(ptr) - offsetof(type, member))) #endif #endif /** * struct list_head - Head and node of a double-linked list * @prev: pointer to the previous node in the list * @next: pointer to the next node in the list * * The simple double-linked list consists of a head and nodes attached to * this head. Both node and head share the same struct type. The list_* * functions and macros can be used to access and modify this data structure. * * The @prev pointer of the list head points to the last list node of the * list and @next points to the first list node of the list. For an empty list, * both member variables point to the head. * * The list nodes are usually embedded in a container structure which holds the * actual data. Such an container object is called entry. The helper list_entry * can be used to calculate the object address from the address of the node. */ struct list_head { struct list_head *prev; struct list_head *next; }; /** * LIST_HEAD - Declare list head and initialize it * @head: name of the new object */ #define LIST_HEAD(head) \ struct list_head head = { &(head), &(head) } /** * INIT_LIST_HEAD() - Initialize empty list head * @head: pointer to list head * * This can also be used to initialize a unlinked list node. * * A node is usually linked inside a list, will be added to a list in * the near future or the entry containing the node will be free'd soon. * * But an unlinked node may be given to a function which uses list_del(_init) * before it ends up in a previously mentioned state. The list_del(_init) on an * initialized node is well defined and safe. But the result of a * list_del(_init) on an uninitialized node is undefined (unrelated memory is * modified, crashes, ...). */ static inline void INIT_LIST_HEAD(struct list_head *head) { head->next = head; head->prev = head; } /** * list_add() - Add a list node to the beginning of the list * @node: pointer to the new node * @head: pointer to the head of the list */ static inline void list_add(struct list_head *node, struct list_head *head) { struct list_head *next = head->next; next->prev = node; node->next = next; node->prev = head; head->next = node; } /** * list_add_tail() - Add a list node to the end of the list * @node: pointer to the new node * @head: pointer to the head of the list */ static inline void list_add_tail(struct list_head *node, struct list_head *head) { struct list_head *prev = head->prev; prev->next = node; node->next = head; node->prev = prev; head->prev = node; } /** * list_add_before() - Add a list node before another node to the list * @new_node: pointer to the new node * @node: pointer to the reference node in the list * * WARNING this functionality is not available in the Linux list implementation */ #define list_add_before(new_node, node) \ list_add_tail(new_node, node) /** * list_add_behind() - Add a list node behind another node to the list * @new_node: pointer to the new node * @node: pointer to the reference node in the list * * WARNING this functionality is not available in the Linux list implementation */ #define list_add_behind(new_node, node) \ list_add(new_node, node) /** * list_del() - Remove a list node from the list * @node: pointer to the node * * The node is only removed from the list. Neither the memory of the removed * node nor the memory of the entry containing the node is free'd. The node * has to be handled like an uninitialized node. Accessing the next or prev * pointer of the node is not safe. * * Unlinked, initialized nodes are also uninitialized after list_del. * * LIST_POISONING can be enabled during build-time to provoke an invalid memory * access when the memory behind the next/prev pointer is used after a list_del. * This only works on systems which prohibit access to the predefined memory * addresses. */ static inline void list_del(struct list_head *node) { struct list_head *next = node->next; struct list_head *prev = node->prev; next->prev = prev; prev->next = next; #ifdef LIST_POISONING node->prev = (struct list_head *)(0x00100100); node->next = (struct list_head *)(0x00200200); #endif } /** * list_del_init() - Remove a list node from the list and reinitialize it * @node: pointer to the node * * The removed node will not end up in an uninitialized state like when using * list_del. Instead the node is initialized again to the unlinked state. */ static inline void list_del_init(struct list_head *node) { list_del(node); INIT_LIST_HEAD(node); } /** * list_empty() - Check if list head has no nodes attached * @head: pointer to the head of the list * * Return: 0 - list is not empty !0 - list is empty */ static inline int list_empty(const struct list_head *head) { return (head->next == head); } /** * list_is_singular() - Check if list head has exactly one node attached * @head: pointer to the head of the list * * Return: 0 - list is not singular !0 -list has exactly one entry */ static inline int list_is_singular(const struct list_head *head) { return (!list_empty(head) && head->prev == head->next); } /** * list_splice() - Add list nodes from a list to beginning of another list * @list: pointer to the head of the list with the node entries * @head: pointer to the head of the list * * All nodes from @list are added to the beginning of the list of @head. * It is similar to list_add but for multiple nodes. The @list head is not * modified and has to be initialized to be used as a valid list head/node * again. */ static inline void list_splice(struct list_head *list, struct list_head *head) { struct list_head *head_first = head->next; struct list_head *list_first = list->next; struct list_head *list_last = list->prev; if (list_empty(list)) return; head->next = list_first; list_first->prev = head; list_last->next = head_first; head_first->prev = list_last; } /** * list_splice_tail() - Add list nodes from a list to end of another list * @list: pointer to the head of the list with the node entries * @head: pointer to the head of the list * * All nodes from @list are added to the end of the list of @head. * It is similar to list_add_tail but for multiple nodes. The @list head is not * modified and has to be initialized to be used as a valid list head/node * again. */ static inline void list_splice_tail(struct list_head *list, struct list_head *head) { struct list_head *head_last = head->prev; struct list_head *list_first = list->next; struct list_head *list_last = list->prev; if (list_empty(list)) return; head->prev = list_last; list_last->next = head; list_first->prev = head_last; head_last->next = list_first; } /** * list_splice_init() - Move list nodes from a list to beginning of another list * @list: pointer to the head of the list with the node entries * @head: pointer to the head of the list * * All nodes from @list are added to the beginning of the list of @head. * It is similar to list_add but for multiple nodes. * * The @list head will not end up in an uninitialized state like when using * list_splice. Instead the @list is initialized again to the an empty * list/unlinked state. */ static inline void list_splice_init(struct list_head *list, struct list_head *head) { list_splice(list, head); INIT_LIST_HEAD(list); } /** * list_splice_tail_init() - Move list nodes from a list to end of another list * @list: pointer to the head of the list with the node entries * @head: pointer to the head of the list * * All nodes from @list are added to the end of the list of @head. * It is similar to list_add_tail but for multiple nodes. * * The @list head will not end up in an uninitialized state like when using * list_splice. Instead the @list is initialized again to the an empty * list/unlinked state. */ static inline void list_splice_tail_init(struct list_head *list, struct list_head *head) { list_splice_tail(list, head); INIT_LIST_HEAD(list); } /** * list_cut_position() - Move beginning of a list to another list * @head_to: pointer to the head of the list which receives nodes * @head_from: pointer to the head of the list * @node: pointer to the node in which defines the cutting point * * All entries from the beginning of the list @head_from to (including) the * @node is moved to @head_to. * * @head_to is replaced when @head_from is not empty. @node must be a real * list node from @head_from or the behavior is undefined. */ static inline void list_cut_position(struct list_head *head_to, struct list_head *head_from, struct list_head *node) { struct list_head *head_from_first = head_from->next; if (list_empty(head_from)) return; if (head_from == node) { INIT_LIST_HEAD(head_to); return; } head_from->next = node->next; head_from->next->prev = head_from; head_to->prev = node; node->next = head_to; head_to->next = head_from_first; head_to->next->prev = head_to; } /** * list_move() - Move a list node to the beginning of the list * @node: pointer to the node * @head: pointer to the head of the list * * The @node is removed from its old position/node and add to the beginning of * @head */ static inline void list_move(struct list_head *node, struct list_head *head) { list_del(node); list_add(node, head); } /** * list_move_tail() - Move a list node to the end of the list * @node: pointer to the node * @head: pointer to the head of the list * * The @node is removed from its old position/node and add to the end of @head */ static inline void list_move_tail(struct list_head *node, struct list_head *head) { list_del(node); list_add_tail(node, head); } /** * list_entry() - Calculate address of entry that contains list node * @node: pointer to list node * @type: type of the entry containing the list node * @member: name of the list_head member variable in struct @type * * Return: @type pointer of entry containing node */ #define list_entry(node, type, member) container_of(node, type, member) /** * list_first_entry() - get first entry of the list * @head: pointer to the head of the list * @type: type of the entry containing the list node * @member: name of the list_head member variable in struct @type * * Return: @type pointer of first entry in list */ #define list_first_entry(head, type, member) \ list_entry((head)->next, type, member) /** * list_last_entry() - get last entry of the list * @head: pointer to the head of the list * @type: type of the entry containing the list node * @member: name of the list_head member variable in struct @type * * Return: @type pointer of last entry in list */ #define list_last_entry(head, type, member) \ list_entry((head)->prev, type, member) /** * list_for_each - iterate over list nodes * @node: list_head pointer used as iterator * @head: pointer to the head of the list * * The nodes and the head of the list must be kept unmodified while * iterating through it. Any modifications to the list will cause undefined * behavior. */ #define list_for_each(node, head) \ for (node = (head)->next; \ node != (head); \ node = node->next) /** * list_for_each_entry_t - iterate over list entries * @entry: @type pointer used as iterator * @head: pointer to the head of the list * @type: type of the entries containing the list nodes * @member: name of the list_head member variable in struct @type * * The nodes and the head of the list must be kept unmodified while * iterating through it. Any modifications to the list will cause undefined * behavior. * * WARNING this functionality is not available in the Linux list implementation */ #define list_for_each_entry_t(entry, head, type, member) \ for (entry = list_entry((head)->next, type, member); \ &entry->member != (head); \ entry = list_entry(entry->member.next, type, member)) /** * list_for_each_entry - iterate over list entries * @entry: pointer used as iterator * @head: pointer to the head of the list * @member: name of the list_head member variable in struct type of @entry * * The nodes and the head of the list must be kept unmodified while * iterating through it. Any modifications to the list will cause undefined * behavior. */ #ifdef LIST_TYPEOF_USE #define list_for_each_entry(entry, head, member) \ list_for_each_entry_t(entry, head, __typeof__(*entry), member) #endif /** * list_for_each_safe - iterate over list nodes and allow deletes * @node: list_head pointer used as iterator * @safe: list_head pointer used to store info for next entry in list * @head: pointer to the head of the list * * The current node (iterator) is allowed to be removed from the list. Any * other modifications to the list will cause undefined behavior. */ #define list_for_each_safe(node, safe, head) \ for (node = (head)->next, safe = node->next; \ node != (head); \ node = safe, safe = node->next) /** * list_for_each_entry_safe_t - iterate over list entries and allow deletes * @entry: @type pointer used as iterator * @safe: @type pointer used to store info for next entry in list * @head: pointer to the head of the list * @type: type of the entries containing the list nodes * @member: name of the list_head member variable in struct @type * * The current node (iterator) is allowed to be removed from the list. Any * other modifications to the list will cause undefined behavior. * * WARNING this functionality is not available in the Linux list implementation */ #define list_for_each_entry_safe_t(entry, safe, head, type, member) \ for (entry = list_entry((head)->next, type, member), \ safe = list_entry(entry->member.next, type, member); \ &entry->member != (head); \ entry = safe, \ safe = list_entry(safe->member.next, type, member)) /** * list_for_each_entry_safe - iterate over list entries and allow deletes * @entry: pointer used as iterator * @safe: @type pointer used to store info for next entry in list * @head: pointer to the head of the list * @member: name of the list_head member variable in struct type of @entry * * The current node (iterator) is allowed to be removed from the list. Any * other modifications to the list will cause undefined behavior. */ #ifdef LIST_TYPEOF_USE #define list_for_each_entry_safe(entry, safe, head, member) \ list_for_each_entry_safe_t(entry, safe, head, __typeof__(*entry), \ member) #endif /** * struct hlist_node - Node of a double-linked list with single pointer head * @next: pointer to the next node in the list * @pprev: pointer to @next of the previous node in the hlist * * The double-linked list with single pointer head consists of a head and nodes * attached to this head. The hlist_* functions and macros can be used to access * and modify this data structure. * * The @pprev pointer is used to find the previous node (or head) in the list * when doing hlist_del operations * * The hlist nodes are usually embedded in a container structure which holds the * actual data. Such an container object is called entry. The helper hlist_entry * can be used to calculate the object address from the address of the node. */ struct hlist_node { struct hlist_node *next; struct hlist_node **pprev; }; /** * struct hlist_head - Head of a double-linked list with single pointer head * @first: pointer to the first node in the hlist * * The hlist doesn't have a pointer to the last node. This makes it harder to * access or modify the tail of the list. But the single pointer to the first * entry makes it well suited for implementation of hash tables because it * cuts the size cost of the head pointers by half compared to the list_head. */ struct hlist_head { struct hlist_node *first; }; /** * HLIST_HEAD - Declare hlist head and initialize it * @head: name of the new object */ #define HLIST_HEAD(head) \ struct hlist_head head = { NULL } /** * INIT_HLIST_HEAD() - Initialize empty hlist head * @head: pointer to hlist head */ static inline void INIT_HLIST_HEAD(struct hlist_head *head) { head->first = NULL; } /** * INIT_HLIST_NODE() - Initialize unhashed hlist node * @node: pointer to hlist node * * A hlist_node is usually linked inside a hlist, will be added to a hlist in * the near future or the entry containing the node will be free'd soon. * * But an unlinked node may be given to a function which uses hlist_del(_init) * before it ends up in a previously mentioned state. The hlist_del(_init) on an * initialized node is well defined and safe. But the result of a * hlist_del(_init) on an uninitialized node is undefined (unrelated memory is * modified, crashes, ...). */ static inline void INIT_HLIST_NODE(struct hlist_node *node) { node->next = NULL; node->pprev = NULL; } /** * hlist_add_head() - Add a hlist node to the beginning of the hlist * @node: pointer to the new node * @head: pointer to the head of the hlist */ static inline void hlist_add_head(struct hlist_node *node, struct hlist_head *head) { struct hlist_node *first = head->first; head->first = node; node->next = first; node->pprev = &head->first; if (first) first->pprev = &node->next; } /** * hlist_add_before() - Add a hlist node before another node to the hlist * @new_node: pointer to the new node * @node: pointer to the reference node in the hlist */ static inline void hlist_add_before(struct hlist_node *new_node, struct hlist_node *node) { struct hlist_node **pprev = node->pprev; *pprev = new_node; new_node->next = node; new_node->pprev = pprev; node->pprev = &new_node->next; } /** * hlist_add_behind() - Add a hlist node behind another node to the hlist * @new_node: pointer to the new node * @node: pointer to the reference node in the hlist */ static inline void hlist_add_behind(struct hlist_node *new_node, struct hlist_node *node) { struct hlist_node *next = node->next; node->next = new_node; new_node->pprev = &node->next; new_node->next = next; if (next) next->pprev = &new_node->next; } /** * hlist_del() - Remove a hlist node from the hlist * @node: pointer to the node * * The node is only removed from the hlist. Neither the memory of the removed * node nor the memory of the entry containing the node is free'd. The node * has to be handled like an uninitialized node. Accessing the next or pprev * pointer of the node is not safe. * * Unlinked, initialized nodes are also uninitialized after hlist_del. * * LIST_POISONING can be enabled during build-time to provoke an invalid memory * access when the memory behind the next/prev pointer is used after an * hlist_del. This only works on systems which prohibit access to the predefined * memory addresses. */ static inline void hlist_del(struct hlist_node *node) { struct hlist_node *next = node->next; struct hlist_node **pprev = node->pprev; if (pprev) *pprev = next; if (next) next->pprev = pprev; #ifdef LIST_POISONING node->pprev = (struct hlist_node **)(0x00100100); node->next = (struct hlist_node *)(0x00200200); #endif } /** * hlist_del_init() - Remove a hlist node from the hlist and reinitialize it * @node: pointer to the node * * The removed node will not end up in an uninitialized state like when using * hlist_del. Instead the node is initialized again to the unlinked state. */ static inline void hlist_del_init(struct hlist_node *node) { hlist_del(node); INIT_HLIST_NODE(node); } /** * hlist_empty() - Check if hlist head has no nodes attached * @head: pointer to the head of the hlist * * Return: 0 - hlist is not empty !0 - hlist is empty */ static inline int hlist_empty(const struct hlist_head *head) { return !head->first; } /** * hlist_move_list() - Move hlist nodes from a hlist head new hlist head * @list: pointer to the head of the hlist with the node entries * @head: pointer to the head of the hlist * * All nodes from @list are added to the beginning of the list of @head. * @head can be uninitialized or an empty, initialized hlist. All entries of * a non-empty hlist @head would be lost after this operation. * * The @list head will not end up in an uninitialized state. Instead the @list * is initialized again to an empty hlist. */ static inline void hlist_move_list(struct hlist_head *list, struct hlist_head *head) { head->first = list->first; if (head->first) head->first->pprev = &head->first; INIT_HLIST_HEAD(list); } /** * hlist_entry() - Calculate address of entry that contains hlist node * @node: pointer to hlist node * @type: type of the entry containing the hlist node * @member: name of the hlist_node member variable in struct @type * * Return: @type pointer of entry containing node */ #define hlist_entry(node, type, member) container_of(node, type, member) /** * hlist_entry_safe() - Calculate address of entry that contains hlist node * @node: pointer to hlist node or (struct hlist_node *)NULL * @type: type of the entry containing the hlist node * @member: name of the hlist_node member variable in struct @type * * Return: @type pointer of entry containing node or NULL */ #ifdef LIST_TYPEOF_USE #define hlist_entry_safe(node, type, member) __extension__ ({ \ __typeof__(node) __node = (node); \ !__node ? NULL : hlist_entry(__node, type, member); }) #else #define hlist_entry_safe(node, type, member) \ (node) ? hlist_entry(node, type, member) : NULL #endif /** * hlist_for_each - iterate over hlist nodes * @node: hlist_node pointer used as iterator * @head: pointer to the head of the hlist * * The nodes and the head of the hlist must be kept unmodified while * iterating through it. Any modifications to the hlist will cause undefined * behavior. */ #define hlist_for_each(node, head) \ for (node = (head)->first; \ node; \ node = node->next) /** * hlist_for_each_entry_t - iterate over hlist entries * @entry: @type pointer used as iterator * @head: pointer to the head of the hlist * @type: type of the entries containing the hlist nodes * @member: name of the hlist_node member variable in struct @type * * The nodes and the head of the hlist must be kept unmodified while * iterating through it. Any modifications to the hlist will cause undefined * behavior. * * WARNING this functionality is not available in the Linux list implementation */ #define hlist_for_each_entry_t(entry, head, type, member) \ for (entry = hlist_entry_safe((head)->first, type, member); \ entry; \ entry = hlist_entry_safe(entry->member.next, type, member)) /** * hlist_for_each_entry - iterate over hlist entries * @entry: pointer used as iterator * @head: pointer to the head of the hlist * @member: name of the hlist_node member variable in struct type of @entry * * The nodes and the head of the hlist must be kept unmodified while * iterating through it. Any modifications to the hlist will cause undefined * behavior. */ #ifdef LIST_TYPEOF_USE #define hlist_for_each_entry(entry, head, member) \ hlist_for_each_entry_t(entry, head, __typeof__(*entry), member) #endif /** * hlist_for_each_safe - iterate over hlist nodes and allow deletes * @node: hlist_node pointer used as iterator * @safe: hlist_node pointer used to store info for next entry in hlist * @head: pointer to the head of the hlist * * The current node (iterator) is allowed to be removed from the hlist. Any * other modifications to the hlist will cause undefined behavior. */ #define hlist_for_each_safe(node, safe, head) \ for (node = (head)->first; \ node && ((safe = node->next) || 1); \ node = safe) /** * hlist_for_each_entry_safe_t - iterate over hlist entries and allow deletes * @entry: @type pointer used as iterator * @safe: hlist_node pointer used to store info for next entry in hlist * @head: pointer to the head of the hlist * @type: type of the entries containing the hlist nodes * @member: name of the hlist_node member variable in struct @type * * The current node (iterator) is allowed to be removed from the hlist. Any * other modifications to the hlist will cause undefined behavior. * * WARNING this functionality is not available in the Linux list implementation */ #define hlist_for_each_entry_safe_t(entry, safe, head, type, member) \ for (entry = hlist_entry_safe((head)->first, type, member); \ entry && ((safe = entry->member.next) || 1); \ entry = hlist_entry_safe(safe, type, member)) /** * hlist_for_each_entry_safe - iterate over hlist entries and allow deletes * @entry: pointer used as iterator * @safe: hlist_node pointer used to store info for next entry in hlist * @head: pointer to the head of the hlist * @member: name of the hlist_node member variable in struct type of @entry * * The current node (iterator) is allowed to be removed from the hlist. Any * other modifications to the hlist will cause undefined behavior. */ #ifdef LIST_TYPEOF_USE #define hlist_for_each_entry_safe(entry, safe, head, member) \ hlist_for_each_entry_safe_t(entry, safe, head, __typeof__(*entry),\ member) #endif #ifdef __cplusplus } #endif #endif /* __LINUX_LIKE_LIST_H__ */ batctl-2025.1/loglevel.c000066400000000000000000000131551500012142700150260ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include "functions.h" #include "main.h" #include "sys.h" #define SYS_LOG_LEVEL "log_level" static struct log_level_data { uint32_t log_level; } log_level_globals; static void log_level_usage(void) { fprintf(stderr, "Usage: batctl [options] loglevel [parameters] [level[ level[ level]]...]\n"); fprintf(stderr, "parameters:\n"); fprintf(stderr, " \t -h print this help\n"); fprintf(stderr, "levels:\n"); fprintf(stderr, " \t none Debug logging is disabled\n"); fprintf(stderr, " \t all Print messages from all below\n"); fprintf(stderr, " \t batman Messages related to routing / flooding / broadcasting\n"); fprintf(stderr, " \t routes Messages related to route added / changed / deleted\n"); fprintf(stderr, " \t tt Messages related to translation table operations\n"); fprintf(stderr, " \t bla Messages related to bridge loop avoidance\n"); fprintf(stderr, " \t dat Messages related to arp snooping and distributed arp table\n"); fprintf(stderr, " \t nc Messages related to network coding\n"); fprintf(stderr, " \t mcast Messages related to multicast\n"); fprintf(stderr, " \t tp Messages related to throughput meter\n"); } static int extract_log_level(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct genlmsghdr *ghdr; int *result = arg; if (!genlmsg_valid_hdr(nlh, 0)) return NL_OK; ghdr = nlmsg_data(nlh); if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { return NL_OK; } if (!attrs[BATADV_ATTR_LOG_LEVEL]) return NL_OK; log_level_globals.log_level = nla_get_u32(attrs[BATADV_ATTR_LOG_LEVEL]); *result = 0; return NL_STOP; } static int get_log_level(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_GET_MESH, NULL, extract_log_level); } static int set_attrs_log_level(struct nl_msg *msg, void *arg __maybe_unused) { nla_put_u32(msg, BATADV_ATTR_LOG_LEVEL, log_level_globals.log_level); return 0; } static int set_log_level(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_SET_MESH, set_attrs_log_level, NULL); } static int log_level_read_setting(struct state *state) { int res; res = get_log_level(state); if (res < 0) return EXIT_FAILURE; else return EXIT_SUCCESS; } static int log_level_write_setting(struct state *state) { int res; res = set_log_level(state); if (res < 0) return EXIT_FAILURE; else return EXIT_SUCCESS; } static int loglevel(struct state *state, int argc, char **argv) { int res = EXIT_FAILURE; int optchar; int i; log_level_globals.log_level = 0; while ((optchar = getopt(argc, argv, "h")) != -1) { switch (optchar) { case 'h': log_level_usage(); return EXIT_SUCCESS; default: log_level_usage(); return EXIT_FAILURE; } } if (argc != 1) { for (i = 1; i < argc; i++) { if (strcmp(argv[i], "none") == 0) { log_level_globals.log_level = 0; break; } else if (strcmp(argv[i], "all") == 0) { log_level_globals.log_level = 255; break; } else if (strcmp(argv[i], "batman") == 0) { log_level_globals.log_level |= BIT(0); } else if (strcmp(argv[i], "routes") == 0) { log_level_globals.log_level |= BIT(1); } else if (strcmp(argv[i], "tt") == 0) { log_level_globals.log_level |= BIT(2); } else if (strcmp(argv[i], "bla") == 0) { log_level_globals.log_level |= BIT(3); } else if (strcmp(argv[i], "dat") == 0) { log_level_globals.log_level |= BIT(4); } else if (strcmp(argv[i], "nc") == 0) { log_level_globals.log_level |= BIT(5); } else if (strcmp(argv[i], "mcast") == 0) { log_level_globals.log_level |= BIT(6); } else if (strcmp(argv[i], "tp") == 0) { log_level_globals.log_level |= BIT(7); } else { log_level_usage(); return EXIT_FAILURE; } } return log_level_write_setting(state); } res = log_level_read_setting(state); if (res != EXIT_SUCCESS) return res; printf("[%c] %s (%s)\n", (!log_level_globals.log_level) ? 'x' : ' ', "all debug output disabled", "none"); printf("[%c] %s (%s)\n", (log_level_globals.log_level & BIT(0)) ? 'x' : ' ', "messages related to routing / flooding / broadcasting", "batman"); printf("[%c] %s (%s)\n", (log_level_globals.log_level & BIT(1)) ? 'x' : ' ', "messages related to route added / changed / deleted", "routes"); printf("[%c] %s (%s)\n", (log_level_globals.log_level & BIT(2)) ? 'x' : ' ', "messages related to translation table operations", "tt"); printf("[%c] %s (%s)\n", (log_level_globals.log_level & BIT(3)) ? 'x' : ' ', "messages related to bridge loop avoidance", "bla"); printf("[%c] %s (%s)\n", (log_level_globals.log_level & BIT(4)) ? 'x' : ' ', "messages related to arp snooping and distributed arp table", "dat"); printf("[%c] %s (%s)\n", (log_level_globals.log_level & BIT(5)) ? 'x' : ' ', "messages related to network coding", "nc"); printf("[%c] %s (%s)\n", (log_level_globals.log_level & BIT(6)) ? 'x' : ' ', "messages related to multicast", "mcast"); printf("[%c] %s (%s)\n", (log_level_globals.log_level & BIT(7)) ? 'x' : ' ', "messages related to throughput meter", "tp"); return res; } COMMAND(SUBCOMMAND_MIF, loglevel, "ll", COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, NULL, "[level] \tdisplay or modify the log level"); batctl-2025.1/main.c000066400000000000000000000221001500012142700141270ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Andreas Langer , Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include "main.h" #include "sys.h" #include "debug.h" #include "functions.h" #include "netlink.h" char mesh_dfl_iface[] = "bat0"; char module_ver_path[] = "/sys/module/batman_adv/version"; extern const struct command *__start___command[]; extern const struct command *__stop___command[]; static void print_usage(void) { struct { const char *label; uint32_t types; } type[] = { { .label = "commands:\n", .types = BIT(SUBCOMMAND) | BIT(SUBCOMMAND_MIF) | BIT(SUBCOMMAND_VID) | BIT(SUBCOMMAND_HIF), }, { .label = "debug tables: \tdisplay the corresponding debug table\n", .types = BIT(DEBUGTABLE), }, { .label = "JSON queries: \tdisplay results of netlink query as JSON\n", .types = BIT(JSON_MIF) | BIT(JSON_VID) | BIT(JSON_HIF), }, }; static const char * const default_prefixes[] = { "", NULL, }; static const char * const meshif_prefixes[] = { "meshif ", NULL, }; static const char * const vlan_prefixes[] = { "vlan ", "meshif vid ", NULL, }; static const char * const hardif_prefixes[] = { "hardif ", NULL, }; const struct command **p; const char * const *prefixes; const char * const *prefix; char buf[64]; size_t i; fprintf(stderr, "Usage: batctl [options] command|debug table|debug json [parameters]\n"); fprintf(stderr, "options:\n"); fprintf(stderr, " \t-h print this help (or 'batctl -h' for the parameter help)\n"); fprintf(stderr, " \t-v print version\n"); for (i = 0; i < sizeof(type) / sizeof(*type); i++) { fprintf(stderr, "\n"); fprintf(stderr, "%s", type[i].label); for (p = __start___command; p < __stop___command; p++) { const struct command *cmd = *p; if (!(BIT(cmd->type) & type[i].types)) continue; if (!cmd->usage) continue; switch (cmd->type) { case DEBUGTABLE: case JSON_MIF: case SUBCOMMAND_MIF: prefixes = meshif_prefixes; break; case JSON_VID: case SUBCOMMAND_VID: prefixes = vlan_prefixes; break; case JSON_HIF: case SUBCOMMAND_HIF: prefixes = hardif_prefixes; break; default: prefixes = default_prefixes; break; } for (prefix = &prefixes[0]; *prefix; prefix++) { if (strcmp(cmd->name, cmd->abbr) == 0) snprintf(buf, sizeof(buf), "%s%s", *prefix, cmd->name); else snprintf(buf, sizeof(buf), "%s%s|%s", *prefix, cmd->name, cmd->abbr); fprintf(stderr, " \t%-43s%s\n", buf, cmd->usage); } } } } static void version(void) { int ret; printf("batctl %s [batman-adv: ", SOURCE_VERSION); ret = read_file(module_ver_path, USE_READ_BUFF | SILENCE_ERRORS); if ((line_ptr) && (line_ptr[strlen(line_ptr) - 1] == '\n')) line_ptr[strlen(line_ptr) - 1] = '\0'; if (ret == EXIT_SUCCESS) printf("%s]\n", line_ptr); else printf("module not loaded]\n"); free(line_ptr); exit(EXIT_SUCCESS); } static const struct command *find_command_by_types(uint32_t types, const char *name) { const struct command **p; for (p = __start___command; p < __stop___command; p++) { const struct command *cmd = *p; if (!(BIT(cmd->type) & types)) continue; if (strcmp(cmd->name, name) == 0) return cmd; if (strcmp(cmd->abbr, name) == 0) return cmd; } return NULL; } static const struct command *find_command(struct state *state, const char *name) { uint32_t types = 0; switch (state->selector) { case SP_NONE_OR_MESHIF: types = BIT(SUBCOMMAND); /* fall through */ case SP_MESHIF: types |= BIT(SUBCOMMAND_MIF) | BIT(DEBUGTABLE) | BIT(JSON_MIF); break; case SP_VLAN: types = BIT(JSON_VID) | BIT(SUBCOMMAND_VID); break; case SP_HARDIF: types = BIT(JSON_HIF) | BIT(SUBCOMMAND_HIF); break; default: return NULL; } return find_command_by_types(types, name); } static int detect_selector_prefix(int argc, char *argv[], enum selector_prefix *selector) { /* not enough remaining arguments to detect anything */ if (argc < 2) return -EINVAL; /* only detect selector prefix which identifies meshif */ if (strcmp(argv[0], "meshif") == 0) { *selector = SP_MESHIF; return 2; } else if (strcmp(argv[0], "vlan") == 0) { *selector = SP_VLAN; return 2; } else if (strcmp(argv[0], "hardif") == 0) { *selector = SP_HARDIF; return 2; } return 0; } static int guess_selector_prefix(int argc, char *argv[], enum selector_prefix *selector) { int ret; /* check if there is a direct hit with full prefix */ ret = detect_selector_prefix(argc, argv, selector); if (ret > 0) return ret; /* not enough remaining arguments to detect anything */ if (argc < 1) return -EINVAL; /* don't try to parse subcommand names as network interface */ if (find_command_by_types(0xffffffff, argv[0])) return -EEXIST; /* check if it is a netdev - and if it exists, try to guess what kind */ ret = guess_netdev_type(argv[0], selector); if (ret < 0) return ret; return 1; } static int parse_meshif_args(struct state *state, int argc, char *argv[]) { enum selector_prefix selector; int parsed_args; char *dev_arg; int ret; parsed_args = guess_selector_prefix(argc, argv, &selector); if (parsed_args < 1) goto fallback_meshif_vlan; dev_arg = argv[parsed_args - 1]; switch (selector) { case SP_MESHIF: snprintf(state->mesh_iface, sizeof(state->mesh_iface), "%s", dev_arg); state->selector = SP_MESHIF; return parsed_args; case SP_VLAN: ret = translate_vlan_iface(state, dev_arg); if (ret < 0) { fprintf(stderr, "Error - invalid vlan device %s: %s\n", dev_arg, strerror(-ret)); return ret; } return parsed_args; case SP_HARDIF: ret = translate_hard_iface(state, dev_arg); if (ret < 0) { fprintf(stderr, "Error - invalid hardif %s: %s\n", dev_arg, strerror(-ret)); return ret; } snprintf(state->hard_iface, sizeof(state->hard_iface), "%s", dev_arg); return parsed_args; case SP_NONE_OR_MESHIF: /* not allowed - see detect_selector_prefix */ break; } fallback_meshif_vlan: /* parse vlan as part of -m parameter or mesh_dfl_iface */ translate_mesh_iface_vlan(state, state->arg_iface); return 0; } static int parse_dev_args(struct state *state, int argc, char *argv[]) { int dev_arguments; int ret; /* try to parse selector prefix which can be used to identify meshif */ dev_arguments = parse_meshif_args(state, argc, argv); if (dev_arguments < 0) return dev_arguments; /* try to parse secondary prefix selectors which cannot be used to * identify the meshif */ argv += dev_arguments; argc -= dev_arguments; switch (state->selector) { case SP_NONE_OR_MESHIF: case SP_MESHIF: /* continue below */ break; default: return dev_arguments; } /* enough room for additional selectors? */ if (argc < 2) return dev_arguments; if (strcmp(argv[0], "vid") == 0) { ret = translate_vid(state, argv[1]); if (ret < 0) return ret; return dev_arguments + 2; } return dev_arguments; } int main(int argc, char **argv) { const struct command *cmd; struct state state = { .arg_iface = mesh_dfl_iface, .selector = SP_NONE_OR_MESHIF, .cmd = NULL, }; int dev_arguments; int opt; int ret; while ((opt = getopt(argc, argv, "+hm:v")) != -1) { switch (opt) { case 'h': print_usage(); exit(EXIT_SUCCESS); break; case 'm': if (state.arg_iface != mesh_dfl_iface) { fprintf(stderr, "Error - multiple mesh interfaces specified\n"); goto err; } fprintf(stderr, "Warning - option -m was deprecated and will be removed in the future\n"); state.arg_iface = argv[2]; break; case 'v': version(); break; default: goto err; } } if (optind >= argc) { fprintf(stderr, "Error - no command specified\n"); goto err; } argv += optind; argc -= optind; optind = 0; /* parse arguments to identify vlan, ... */ dev_arguments = parse_dev_args(&state, argc, argv); if (dev_arguments < 0) goto err; argv += dev_arguments; argc -= dev_arguments; if (argc == 0) { fprintf(stderr, "Error - no command specified\n"); goto err; } cmd = find_command(&state, argv[0]); if (!cmd) { fprintf(stderr, "Error - no valid command or debug table/JSON specified: %s\n", argv[0]); goto err; } state.cmd = cmd; if (cmd->flags & COMMAND_FLAG_MESH_IFACE && check_mesh_iface(&state) < 0) { fprintf(stderr, "Error - interface %s is not present or not a batman-adv interface\n", state.mesh_iface); exit(EXIT_FAILURE); } if (cmd->flags & COMMAND_FLAG_NETLINK) { ret = netlink_create(&state); if (ret < 0) { fprintf(stderr, "Error - failed to connect to batadv\n"); exit(EXIT_FAILURE); } } ret = cmd->handler(&state, argc, argv); if (cmd->flags & COMMAND_FLAG_NETLINK) netlink_destroy(&state); return ret; err: print_usage(); exit(EXIT_FAILURE); } batctl-2025.1/main.h000066400000000000000000000051331500012142700141430ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) B.A.T.M.A.N. contributors: * * Andreas Langer , Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #ifndef _BATCTL_MAIN_H #define _BATCTL_MAIN_H #include #include #include #include #include #ifndef SOURCE_VERSION #define SOURCE_VERSION "2025.1" #endif #define EXIT_NOSUCCESS 2 #if BYTE_ORDER == BIG_ENDIAN #define __BIG_ENDIAN_BITFIELD #elif BYTE_ORDER == LITTLE_ENDIAN #define __LITTLE_ENDIAN_BITFIELD #else #error "unknown endianness" #endif #define __maybe_unused __attribute__((unused)) #define BIT(nr) (1UL << (nr)) /* linux kernel compat */ extern char module_ver_path[]; #ifndef VLAN_VID_MASK #define VLAN_VID_MASK 0xfff #endif #define BATADV_PRINT_VID(vid) ((vid) & (1UL << 15) ? \ (int)((vid) & 0xfff) : -1) #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) #ifndef container_of #define container_of(ptr, type, member) __extension__ ({ \ const __typeof__(((type *)0)->member) * __pmember = (ptr); \ (type *)((char *)__pmember - offsetof(type, member)); }) #endif enum command_flags { COMMAND_FLAG_MESH_IFACE = BIT(0), COMMAND_FLAG_NETLINK = BIT(1), COMMAND_FLAG_INVERSE = BIT(2), }; enum selector_prefix { SP_NONE_OR_MESHIF, SP_MESHIF, SP_VLAN, SP_HARDIF, }; enum command_type { SUBCOMMAND, SUBCOMMAND_MIF, SUBCOMMAND_VID, SUBCOMMAND_HIF, DEBUGTABLE, JSON_MIF, JSON_VID, JSON_HIF, }; struct state { char *arg_iface; enum selector_prefix selector; char mesh_iface[IF_NAMESIZE]; unsigned int mesh_ifindex; char hard_iface[IF_NAMESIZE]; union { unsigned int hif; int vid; }; const struct command *cmd; struct nl_sock *sock; struct nl_cb *cb; int batadv_family; }; struct command { enum command_type type; const char *name; const char *abbr; int (*handler)(struct state *state, int argc, char **argv); uint32_t flags; void *arg; const char *usage; }; #define COMMAND_NAMED(_type, _name, _abbr, _handler, _flags, _arg, _usage) \ static const struct command command_ ## _name ## _ ## _type = { \ .type = (_type), \ .name = (#_name), \ .abbr = _abbr, \ .handler = (_handler), \ .flags = (_flags), \ .arg = (_arg), \ .usage = (_usage), \ }; \ static const struct command *__command_ ## _name ## _ ## _type \ __attribute__((__used__,__section__ ("__command"))) = &command_ ## _name ## _ ## _type #define COMMAND(_type, _handler, _abbr, _flags, _arg, _usage) \ COMMAND_NAMED(_type, _handler, _abbr, _handler, _flags, _arg, _usage) #endif batctl-2025.1/man/000077500000000000000000000000001500012142700136175ustar00rootroot00000000000000batctl-2025.1/man/batctl.8000066400000000000000000000607331500012142700151720ustar00rootroot00000000000000.\" SPDX-License-Identifier: GPL-2.0 .\" License-Filename: LICENSES/preferred/GPL-2.0 .\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH "BATCTL" "8" "July 17, 2015" "Linux" "B.A.T.M.A.N. Advanced Control Tool" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .\" -------------------------------------------------------------------------- .\" Process this file with .\" groff -man batctl.8 -Tutf8 .\" Retrieve format warnings with .\" man --warnings batctl.8 > /dev/null .\" -------------------------------------------------------------------------- .ad l .SH NAME batctl \- B.A.T.M.A.N. advanced control and management tool .SH SYNOPSIS .B batctl [\fIoptions\fP]\ \fIcommand\fP|\fIdebug\ table\fP|\fIdebug\ JSON\fP\ [\fIparameters\fP] .br .SH DESCRIPTION batctl offers a convenient way to configure the batman\-adv kernel module as well as displaying debug information such as originator tables and translation tables. In combination with a bat\-hosts file batctl allows the use of host names instead of MAC addresses. .PP B.A.T.M.A.N. advanced operates on layer 2. Thus all hosts participating in the virtual switched network are transparently connected together for all protocols above layer 2. Therefore the common diagnosis tools do not work as expected. To overcome these problems batctl contains the commands \fBping\fP, \fBtraceroute\fP, \fBtcpdump\fP which provide similar functionality to the normal \fBping\fP(1), \fBtraceroute\fP(1), \fBtcpdump\fP(1) commands, but modified to layer 2 behaviour or using the B.A.T.M.A.N. advanced protocol. For similar reasons, \fBthroughputmeter\fP, a command to test network performances, is also included. .SH OPTIONS .TP \fB\-m\fP specify mesh interface (default 'bat0') .TP \fB\-h\fP print general batctl help .TP \fB-v\fP print batctl version and batman-adv version (if the module is loaded) .SH COMMANDS .TP \fBbisect_iv\fP [\fB\-l MAC\fP][\fB\-t\fP \fIMAC\fP][\fB\-r\fP \fIMAC\fP][\fB\-s\fP \fImin\fP [\- \fImax\fP]][\fB\-o\fP \fIMAC\fP][\fB\-n\fP] \fIlogfile1\fP ... Analyses the B.A.T.M.A.N. IV logfiles to build a small internal database of all sent sequence numbers and routing table changes. This database can then be analyzed in a number of different ways. With "\-l" the database can be used to search for routing loops. Use "\-t" to trace OGMs of a host throughout the network. Use "\-r" to display routing tables of the nodes. The option "\-s" can be used to limit the output to a range of sequence numbers, between min and max, or to one specific sequence number, min. Furthermore using "\-o" you can filter the output to a specified originator. If "\-n" is given batctl will not replace the MAC addresses with bat\-host names in the output. .RE .TP \fBevent\fP|\fBe\fP [\fB\-t\fP|\fB\-r\fP] batctl will monitor for events from the netlink kernel interface of batman-adv. The local timestamp of the event will be printed when parameter \fB\-t\fP is specified. Parameter \fB\-r\fP will do the same but with relative timestamps. .TP [\fBmeshif\fP \fInetdev\fP] \fBinterface\fP|\fBif\fP .TQ [\fBmeshif\fP \fInetdev\fP] \fBinterface\fP|\fBif\fP [\fB-M\fP] \fBadd\fP|\fBdel\fP \fIiface\fP ... If no parameter is given or the first parameter is neither "add" nor "del" the current interface settings are displayed. In order to add or delete interfaces specify "add" or "del" as first argument and append the interface names you wish to add or delete. Multiple interfaces can be specified. The "\-M" option tells batctl to not automatically create the batman-adv interface on "add". It can also be used to suppress the warning about the manual destruction when "del" removed all interfaces which belonged to it. .TP [\fBmeshif\fP \fInetdev\fP] \fBinterface\fP|\fBif\fP \fBcreate\fP [\fBrouting_algo\fP|\fBra\fP \fIRA_NAME\fP] A batman-adv interface without attached interfaces can be created using "create". The parameter routing_algo can be used to overwrite the (default) routing algorithm. .TP [\fBmeshif\fP \fInetdev\fP] \fBinterface\fP|\fBif\fP \fBdestroy\fP Remove all attached interfaces and destroy the batman-adv interface. .TP [\fBmeshif\fP \fInetdev\fP] \fBping\fP|\fBp\fP [\fB\-c\fP \fIcount\fP][\fB\-i\fP \fIinterval\fP][\fB\-t\fP \fItime\fP][\fB\-R\fP][\fB\-T\fP] \fIMAC_address\fP|\fIbat\-host_name\fP|\fIhost_name\fP|\fIIP_address\fP Layer 2 ping of a MAC address or bat\-host name. batctl will try to find the bat\-host name if the given parameter was not a MAC address. It can also try to guess the MAC address using an IPv4/IPv6 address or a hostname when the IPv4/IPv6 address was configured on top of the batman-adv interface of the destination device and both source and destination devices are in the same IP subnet. The "\-c" option tells batctl how man pings should be sent before the program exits. Without the "\-c" option batctl will continue pinging without end. Use CTRL + C to stop it. With "\-i" and "\-t" you can set the default interval between pings and the timeout time for replies, both in seconds. When run with "\-R", the route taken by the ping messages will be recorded. With "\-T" you can disable the automatic translation of a client MAC address to the originator address which is responsible for this client. .TP \fBrouting_algo\fP|\fBra\fP [\fIalgorithm\fP] If no parameter is given the current routing algorithm configuration as well as supported routing algorithms are displayed. Otherwise the parameter is used to select the routing algorithm for the following batX interface to be created. .TP [\fBmeshif\fP \fInetdev\fP] \fBstatistics\fP|\fBs\fP Retrieve traffic counters from batman-adv kernel module. The output may vary depending on which features have been compiled into the kernel module. .br Each module subsystem has its own counters which are indicated by their prefixes: .TS tab (@); r lx. mgmt@T{ mesh protocol counters T} tt@T{ translation table counters T} .TE All counters without a prefix concern payload (pure user data) traffic. .TP \fBtcpdump\fP|\fBtd\fP [\fB\-c\fP][\fB\-n\fP][\fB\-p\fP \fIfilter\fP][\fB\-x\fP \fIfilter\fP] \fBinterface ...\fP batctl will display all packets that are seen on the given interface(s). A variety of options to filter the output are available: To only print packets that match the compatibility number of batctl specify the "\-c" (compat filter) option. If "\-n" is given batctl will not replace the MAC addresses with bat\-host names in the output. To filter the shown packet types you can either use "\-p" (dump only specified packet types) or "\-x" (dump all packet types except specified). The following packet types are available: .TS tab (@); r lx. 1@T{ batman ogm packets T} 2@T{ batman icmp packets T} 4@T{ batman unicast packets T} 8@T{ batman broadcast packets T} 16@T{ batman unicast tvlv packets T} 32@T{ batman fragmented packets T} 64@T{ batman tt / roaming packets T} 128@T{ non batman packets T} .TE Example: batctl td \-p 129 \-> only display batman ogm packets and non batman packets .TP [\fBmeshif\fP \fInetdev\fP] \fBthroughputmeter\fP|\fBtp\fP \fIMAC\fP This command starts a throughput test entirely controlled by batman module in kernel space: the computational resources needed to align memory and copy data between user and kernel space that are required by other user space tools may represent a bottleneck on some low profile device. The test consist of the transfer of 14 MB of data between the two nodes. The protocol used to transfer the data is somehow similar to TCP, but simpler: some TCP features are still missing, thus protocol performances could be worst. Since a fixed amount of data is transferred the experiment duration depends on the network conditions. The experiment can be interrupted with CTRL + C. At the end of a successful experiment the throughput in KBytes per second is returned, together with the experiment duration in millisecond and the amount of bytes transferred. If too many packets are lost or the specified MAC address is not reachable, a message notifying the error is returned instead of the result. .TP [\fBmeshif\fP \fInetdev\fP] \fBtraceroute\fP|\fBtr\fP [\fB\-n\fP][\fB\-T\fP] \fIMAC_address\fP|\fIbat\-host_name\fP|\fIhost_name\fP|\fIIP_address\fP Layer 2 traceroute to a MAC address or bat\-host name. batctl will try to find the bat\-host name if the given parameter was not a MAC address. It can also try to guess the MAC address using an IPv4/IPv6 address or a hostname when the IPv4/IPv6 address was configured on top of the batman-adv interface of the destination device and both source and destination devices are in the same IP subnet. batctl will send 3 packets to each host and display the response time. If "\-n" is given batctl will not replace the MAC addresses with bat\-host names in the output. With "\-T" you can disable the automatic translation of a client MAC address to the originator address which is responsible for this client. .TP [\fBmeshif\fP \fInetdev\fP] \fBtranslate\fP|\fBt\fP \fIMAC_address\fP|\fIbat\-host_name\fP|\fIhost_name\fP|\fIIP_address\fP Translates a destination (hostname, IP, MAC, bat_host-name) to the originator mac address responsible for it. .SH SETTINGS .TP [\fBmeshif\fP \fInetdev\fP] \fBaggregation\fP|\fBag\fP [\fI0\fP|\fI1\fP] If no parameter is given the current aggregation setting is displayed. Otherwise the parameter is used to enable or disable OGM packet aggregation. .TP [\fBmeshif\fP \fInetdev\fP] \fBap_isolation\fP|\fBap\fP [\fI0\fP|\fI1\fP] If no parameter is given the current ap isolation setting is displayed. Otherwise the parameter is used to enable or disable ap isolation. .TP [\fBmeshif\fP \fInetdev\fP] \fBap_isolation\fP|\fBap\fP [\fI0\fP|\fI1\fP] .TQ [\fBmeshif\fP \fInetdev\fP] \fBvid \fP \fBap_isolation\fP|\fBap\fP [\fI0\fP|\fI1\fP] .TQ \fBvlan\fP \fIvdev\fP \fBap_isolation\fP|\fBap\fP [\fI0\fP|\fI1\fP] If no parameter is given the current ap isolation setting for the specified VLAN is displayed. Otherwise the parameter is used to enable or disable ap isolation for the specified VLAN. .TP [\fBmeshif\fP \fInetdev\fP] \fBbonding\fP|\fBb\fP [\fI0\fP|\fI1\fP] If no parameter is given the current bonding mode setting is displayed. Otherwise the parameter is used to enable or disable the bonding mode. .TP [\fBmeshif\fP \fInetdev\fP] \fBbridge_loop_avoidance\fP|\fBbl\fP [\fI0\fP|\fI1\fP] If no parameter is given the current bridge loop avoidance setting is displayed. Otherwise the parameter is used to enable or disable the bridge loop avoidance. Bridge loop avoidance support has to be enabled when compiling the module otherwise this option won't be available. .TP [\fBmeshif\fP \fInetdev\fP] \fBdistributed_arp_table\fP|\fBdat\fP [\fI0\fP|\fI1\fP] If no parameter is given the current distributed arp table setting is displayed. Otherwise the parameter is used to enable or disable the distributed arp table. .TP \fBhardif\fP \fIhardif\fP \fBelp_interval\fP|\fBet\fP [\fIinterval\fP] If no parameter is given the current ELP interval setting of the hard interface is displayed otherwise the parameter is used to set the ELP interval. The interval is in units of milliseconds. .TP [\fBmeshif\fP \fInetdev\fP] \fBfragmentation\fP|\fBf\fP [\fI0\fP|\fI1\fP] If no parameter is given the current fragmentation mode setting is displayed. Otherwise the parameter is used to enable or disable fragmentation. .TP [\fBmeshif\fP \fInetdev\fP] \fBgw_mode|gw\fP [\fBoff\fP|\fBclient\fP|\fBserver\fP] [\fIsel_class\fP|\fIbandwidth\fP] If no parameter is given the current gateway mode is displayed otherwise the parameter is used to set the gateway mode. The second (optional) argument specifies the selection class (if 'client' was the first argument) or the gateway bandwidth (if 'server' was the first argument). If the node is a server this parameter is used to inform other nodes in the network about this node's internet connection bandwidth. Just enter any number (optionally followed by "kbit" or "mbit") and the batman-adv module will propagate the entered value in the mesh. Use "/" to separate the down\(hy and upload rates. You can omit the upload rate and the module will assume an upload of download / 5. .RS 17 default: 10000 \-> 10.0/2.0 MBit .RE .RS 16 examples: 5000 \-> 5.0/1.0 MBit .RE .RS 26 5000kbit 5mbit 5mbit/1024 5mbit/1024kbit 5mbit/1mbit .RE .RS 7 If the node is a gateway client the parameter will decide which criteria to consider when the batman-adv module has to choose between different internet connections announced by the aforementioned servers. .RE .RS 7 B.A.T.M.A.N. IV: .RE .RS 17 default: 20 \-> late switch (TQ 20) .RE .RS 16 examples: 1 -> fast connection .RS 16 consider the gateway's advertised throughput as well as the link quality towards the gateway and stick with the selection until the gateway disappears .RE .RE .RS 25 2 \-> stable connection .RS 7 chooses the gateway with the best link quality and sticks with it (ignore the advertised throughput) .RE 3 \-> fast switch connection .RS 7 chooses the gateway with the best link quality but switches to another gateway as soon as a better one is found .RE XX \-> late switch connection .RS 7 chooses the gateway with the best link quality but switches to another gateway as soon as a better one is found which is at least XX TQ better than the currently selected gateway (XX has to be a number between 3 and 256). .RE .RE .RS 7 B.A.T.M.A.N. V: .RE .RS 17 default: 5000 \-> late switch (5000 kbit/s throughput) .br example: 1500 \-> fast switch connection .RS 17 switches to another gateway as soon as a better one is found which is at least 1500 kbit/s faster throughput than the currently selected gateway. Throughput is determined by evaluating which is lower: the advertised throughput by the gateway or the maximum bandwidth across the entire path. .RE .RE .br .TP [\fBmeshif\fP \fInetdev\fP] \fBhop_penalty\fP|\fBhp\fP [\fIpenalty\fP] If no parameter is given the current hop penalty setting is displayed. Otherwise the parameter is used to set the hop penalty. The penalty is can be 0-255 (255 sets originator message's TQ to zero when forwarded by this hop). .TP [\fBhardif\fP \fIhardif\fP] \fBhop_penalty\fP|\fBhp\fP [\fIpenalty\fP] If no parameter is given the current hop penalty setting of the hard interface is displayed. Otherwise the parameter is used to set the hop penalty. The penalty can be 0-255 (255 sets originator message's TQ to zero when forwarded over this interface). .TP [\fBmeshif\fP \fInetdev\fP] \fBisolation_mark\fP|\fBmark\fP [\fIvalue\fP[/\fImask\fP]] If no parameter is given the current isolation mark value is displayed. Otherwise the parameter is used to set or unset the isolation mark used by the Extended Isolation feature. .br The input is supposed to be of the form $value/$mask, where $value can be any 32bit long integer (expressed in decimal or hex base) and $mask is a generic bitmask (expressed in hex base) that selects the bits to take into consideration from $value. It is also possible to enter the input using only $value and in this case the full bitmask is used by default. .br .br Example 1: 0x00000001/0xffffffff .br Example 2: 0x00040000/0xffff0000 .br Example 3: 16 or 0x0F .br .TP [\fBmeshif\fP \fInetdev\fP] \fBloglevel\fP|\fBll\fP [\fIlevel\fP ...] If no parameter is given the current log level settings are displayed otherwise the parameter(s) is/are used to set the log level. Level 'none' disables all verbose logging. Level 'batman' enables messages related to routing / flooding / broadcasting. Level 'routes' enables messages related to routes being added / changed / deleted. Level 'tt' enables messages related to translation table operations. Level 'bla' enables messages related to the bridge loop avoidance. Level 'dat' enables messages related to ARP snooping and the Distributed Arp Table. Level 'nc' enables messages related to network coding. Level 'mcast' enables messages related to multicast optimizations. Level 'tp' enables messages related to throughput meter. Level 'all' enables all messages. The messages are sent to the kernels trace buffers. Use \fBtrace-cmd stream -e batadv:batadv_dbg\fP to receive the system wide log messages. .TP [\fBmeshif\fP \fInetdev\fP] \fBmulticast_fanout\fP|\fBmo\fP [\fIfanout\fP] If no parameter is given the current multicast fanout setting is displayed. Otherwise the parameter is used to set the multicast fanout. The multicast fanout defines the maximum number of packet copies that may be generated for a multicast-to-unicast conversion. Once this limit is exceeded distribution will fall back to broadcast. .TP [\fBmeshif\fP \fInetdev\fP] \fBmulticast_forceflood\fP|\fBmff\fP [\fI0\fP|\fI1\fP] If no parameter is given the current multicast forceflood setting is displayed. Otherwise the parameter is used to enable or disable multicast forceflood. This setting defines whether multicast optimizations should be replaced by simple broadcast-like flooding of multicast packets. If set to non-zero then all nodes in the mesh are going to use classic flooding for any multicast packet with no optimizations. .TP [\fBmeshif\fP \fInetdev\fP] \fBnetwork_coding\fP|\fBnc\fP [\fI0\fP|\fI1\fP] If no parameter is given the current network coding mode setting is displayed. Otherwise the parameter is used to enable or disable network coding. .TP [\fBmeshif\fP \fInetdev\fP] \fBorig_interval\fP|\fBit\fP [\fIinterval\fP] If no parameter is given the current originator interval setting is displayed otherwise the parameter is used to set the originator interval. The interval is in units of milliseconds. .TP \fBhardif\fP \fIhardif\fP \fBthroughput_override|to\fP [\fIbandwidth\fP] If no parameter is given the current througput override is displayed otherwise the parameter is used to set the throughput override for the specified hard interface. Just enter any number (optionally followed by "kbit" or "mbit"). .SH DEBUG TABLES The batman-adv kernel module comes with a variety of debug tables containing various information about the state of the mesh seen by each individual node. All of the debug tables support the following options: .TP \fB-w\fP refresh the list every second or add a number to let it refresh at a custom interval in seconds (with optional decimal places) .TP \fB-n\fP do not replace the MAC addresses with bat\-host names in the output .TP \fB-H\fP do not show the header of the debug table .PP The originator table also supports the "\-t" filter option to remove all originators from the output that have not been seen for the specified amount of seconds (with optional decimal places). It furthermore supports the "\-i" parameter to specify an interface for which the originator table should be printed. If this parameter is not supplied, the default originator table is printed. The local and global translation tables also support the "\-u" and "\-m" option to only display unicast or multicast translation table announcements respectively. .TP [\fBmeshif\fP \fInetdev\fP] \fBbackbonetable\fP|\fBbbt\fP [\fB-n\fP] [\fB-H\fP] [\fB-w\fP \fIinterval\fP] (compile time option) .TP [\fBmeshif\fP \fInetdev\fP] \fBclaimtable\fP|\fBcl\fP [\fB-n\fP] [\fB-H\fP] [\fB-w\fP \fIinterval\fP] .TP [\fBmeshif\fP \fInetdev\fP] \fBdat_cache\fP|\fBdc\fP [\fB-n\fP] [\fB-H\fP] [\fB-w\fP \fIinterval\fP] (compile time option) .TP [\fBmeshif\fP \fInetdev\fP] \fBgateways\fP|\fBgwl\fP [\fB-n\fP] [\fB-H\fP] [\fB-w\fP \fIinterval\fP] .TP [\fBmeshif\fP \fInetdev\fP] \fBmcast_flags\fP|\fBmf\fP [\fB-n\fP] [\fB-H\fP] [\fB-w\fP \fIinterval\fP] (compile time option) .TP [\fBmeshif\fP \fInetdev\fP] \fBneighbors\fP|\fBn\fP [\fB-n\fP] [\fB-H\fP] [\fB-w\fP \fIinterval\fP] .TP [\fBmeshif\fP \fInetdev\fP] \fBoriginators\fP|\fBo\fP [\fB-n\fP] [\fB-H\fP] [\fB-w\fP \fIinterval\fP] [\fB-t\fP \fItimeout_interval\fP] [\fB-i\fP \fIinterface\fP] .TP [\fBmeshif\fP \fInetdev\fP] \fBtransglobal\fP|\fBtg\fP [\fB-n\fP] [\fB-H\fP] [\fB-w\fP \fIinterval\fP] [\fB-u\fP] [\fB-m\fP] (compile time option) .TP [\fBmeshif\fP \fInetdev\fP] \fBtranslocal\fP|\fBtl\fP [\fB-n\fP] [\fB-H\fP] [\fB-w\fP \fIinterval\fP] [\fB-u\fP] [\fB-m\fP] .SH JSON QUERIES The generic netlink family provided by the batman-adv kernel module can be queried (read-only) by batctl and automatically translated to JSON. This can be used to monitor the state of the system without the need of parsing the freeform debug tables or the native netlink messages. .TP [\fBmeshif\fP \fInetdev\fP] \fBbla_backbone_json\fP|\fBbbj\fP .TP [\fBmeshif\fP \fInetdev\fP] \fBbla_claim_json\fP|\fBclj\fP .TP [\fBmeshif\fP \fInetdev\fP] \fBdat_cache_json\fP|\fBdcj\fP .TP [\fBmeshif\fP \fInetdev\fP] \fBgateways_json\fP|\fBgwj\fP .TP \fBhardif\fP \fIhardif\fP \fBhardif_json\fP|\fBhj\fP .TP [\fBmeshif\fP \fInetdev\fP] \fBhardifs_json\fP|\fBhj\fP .TP [\fBmeshif\fP \fInetdev\fP] \fBmcast_flags_json\fP|\fBmfj\fP .TP [\fBmeshif\fP \fInetdev\fP] \fBmesh_json\fP|\fBmj\fP .TP [\fBmeshif\fP \fInetdev\fP] \fBneighbors_json\fP|\fBnj\fP .TP [\fBmeshif\fP \fInetdev\fP] \fBoriginators_json\fP|\fBoj\fP .TP [\fBmeshif\fP \fInetdev\fP] \fBtranstable_global_json\fP|\fBtgj\fP .TP [\fBmeshif\fP \fInetdev\fP] \fBtranstable_local_json\fP|\fBtlj\fP .TP [\fBmeshif\fP \fInetdev\fP] \fBvid \fP \fBvlan_json\fP|\fBvj\fP .TQ \fBvlan\fP \fIvdev\fP \fBvlan_json\fP|\fBvj\fP .SH EXAMPLES The setup of a batadv interface usually consists of creation of the the main interface, attaching of the (lower) hard-interface, adjusting of settings and bringup of the interface: .PP .in +4n .EX # create batadv (mesh) interface bat0 with routing algorithm B.A.T.M.A.N. IV .RB "$" " batctl meshif bat0 interface create routing_algo BATMAN_IV" # add the (already up and running) mesh0 interface as lower (hard) interface to bat0 .RB "$" " batctl meshif bat0 interface -M add mesh0" # change some settings to better match the requirements of the user .RB "$" " batctl meshif bat0 orig_interval 5000" .RB "$" " batctl meshif bat0 distributed_arp_table disable" .RB "..." # set the batadv (mesh) interface up before it is possible to use it .RB "$" " ip link set up dev bat0" .EE .in This only makes sure that the layer 2 functionality of bat0 is started up. It is the responsibility of the user to make sure that the bat0 device itself gets attached to a bridge, configured with an IP address (manually/DHCP client/...) or integrated in other parts of the system before it gets used. .PP Also the attached (lower) hard-interfaces attached to the batadv interface must be configured by the user to support transportation of ethernet unicast and broadcast packets between its linked peers. The most common reason for a not working batman-adv mesh are incorrect configurations of the hard-interfaces, hardware, firmware or driver bugs which prevent that some of the packet types are correctly exchanged. .PP The current status of interface can be checked using the debug tables. It is often relevant to check from which direct neighbors discovery packets were received. The next step is to check the (preferred) routes to originators. These will only be established when the metric has detected bidirectional connections between neighbors and might have forwarded discovery packets from not directly reachable nodes/originators. .PP .in +4n .EX # get list of neighbors from which the current node was able to receive discovery packets .RB "$" " batctl meshif bat0 neighbors" # get (preferred) routes the routing algorithm found .RB "$" " batctl meshif bat0 originators" .EE .in .PP If the bat0 interface should no longer used by the system, it can be destroyed again: .PP .in +4n .EX # destroy the interface and let the system remove its state .RB "$" " batctl meshif bat0 interface destroy" .EE .in .SH FILES .TP \fBbat-hosts\fP This file is similar to the /etc/hosts file. You can write one MAC address and one host name per line. batctl will search for bat-hosts in /etc, your home directory and the current directory. The found data is used to match MAC address to your provided host name or replace MAC addresses in debug output and logs. Host names are much easier to remember than MAC addresses. .SH SEE ALSO .BR bridge (8), .BR dmesg (1), .BR ip (8), .BR ip-link (8), .BR ping (8), .BR tcpdump (8), .BR traceroute (1), .BR trace-cmd (1) .SH AUTHOR batctl was written by Andreas Langer and Marek Lindner . .PP This manual page was written by Simon Wunderlich , Marek Lindner and Andrew Lunn batctl-2025.1/mcast_flags.c000066400000000000000000000113771500012142700155040ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include #include #include #include #include "batadv_packet_compat.h" #include "batman_adv.h" #include "bat-hosts.h" #include "debug.h" #include "functions.h" #include "main.h" #include "netlink.h" static const int mcast_flags_mandatory[] = { BATADV_ATTR_ORIG_ADDRESS, }; static int mcast_flags_callback(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct print_opts *opts = arg; struct bat_host *bat_host; struct genlmsghdr *ghdr; uint32_t flags; uint8_t *addr; if (!genlmsg_valid_hdr(nlh, 0)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } ghdr = nlmsg_data(nlh); if (ghdr->cmd != BATADV_CMD_GET_MCAST_FLAGS) return NL_OK; if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } if (missing_mandatory_attrs(attrs, mcast_flags_mandatory, ARRAY_SIZE(mcast_flags_mandatory))) { fputs("Missing attributes from kernel\n", stderr); exit(1); } addr = nla_data(attrs[BATADV_ATTR_ORIG_ADDRESS]); if (opts->read_opt & MULTICAST_ONLY && !(addr[0] & 0x01)) return NL_OK; if (opts->read_opt & UNICAST_ONLY && (addr[0] & 0x01)) return NL_OK; bat_host = bat_hosts_find_by_mac((char *)addr); if (!(opts->read_opt & USE_BAT_HOSTS) || !bat_host) printf("%02x:%02x:%02x:%02x:%02x:%02x ", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); else printf("%17s ", bat_host->name); if (attrs[BATADV_ATTR_MCAST_FLAGS]) { flags = nla_get_u32(attrs[BATADV_ATTR_MCAST_FLAGS]); printf("[%c%c%c%s%s%c]\n", flags & BATADV_MCAST_WANT_ALL_UNSNOOPABLES ? 'U' : '.', flags & BATADV_MCAST_WANT_ALL_IPV4 ? '4' : '.', flags & BATADV_MCAST_WANT_ALL_IPV6 ? '6' : '.', !(flags & BATADV_MCAST_WANT_NO_RTR4) ? "R4" : ". ", !(flags & BATADV_MCAST_WANT_NO_RTR6) ? "R6" : ". ", !(flags & BATADV_MCAST_HAVE_MC_PTYPE_CAPA) ? 'P' : '.'); } else { printf("-\n"); } return NL_OK; } static int netlink_print_mcast_flags(struct state *state, char *orig_iface, int read_opts, float orig_timeout, float watch_interval) { char *info_header; char shadowing4; char shadowing6; char querier4; char querier6; char *header; bool bridged; int ret; /* only parse own multicast flags */ info_header = netlink_get_info(state, BATADV_CMD_GET_MCAST_FLAGS, NULL); free(info_header); if (mcast_flags == -EOPNOTSUPP || mcast_flags_priv == -EOPNOTSUPP) return -EOPNOTSUPP; bridged = mcast_flags_priv & BATADV_MCAST_FLAGS_BRIDGED; if (bridged) { querier4 = (mcast_flags_priv & BATADV_MCAST_FLAGS_QUERIER_IPV4_EXISTS) ? '.' : '4'; querier6 = (mcast_flags_priv & BATADV_MCAST_FLAGS_QUERIER_IPV6_EXISTS) ? '.' : '6'; shadowing4 = (mcast_flags_priv & BATADV_MCAST_FLAGS_QUERIER_IPV4_SHADOWING) ? '4' : '.'; shadowing6 = (mcast_flags_priv & BATADV_MCAST_FLAGS_QUERIER_IPV6_SHADOWING) ? '6' : '.'; } else { querier4 = '?'; querier6 = '?'; shadowing4 = '?'; shadowing6 = '?'; } ret = asprintf(&header, "Multicast flags (own flags: [%c%c%c%s%s%c])\n" "* Bridged [U]\t\t\t\t%c\n" "* No IGMP/MLD Querier [4/6]:\t\t%c/%c\n" "* Shadowing IGMP/MLD Querier [4/6]:\t%c/%c\n" "-------------------------------------------\n" " %-10s %s\n", (mcast_flags & BATADV_MCAST_WANT_ALL_UNSNOOPABLES) ? 'U' : '.', (mcast_flags & BATADV_MCAST_WANT_ALL_IPV4) ? '4' : '.', (mcast_flags & BATADV_MCAST_WANT_ALL_IPV6) ? '6' : '.', !(mcast_flags & BATADV_MCAST_WANT_NO_RTR4) ? "R4" : ". ", !(mcast_flags & BATADV_MCAST_WANT_NO_RTR6) ? "R6" : ". ", !(mcast_flags & BATADV_MCAST_HAVE_MC_PTYPE_CAPA) ? 'P' : '.', bridged ? 'U' : '.', querier4, querier6, shadowing4, shadowing6, "Originator", "Flags"); if (ret < 0) return ret; ret = netlink_print_common(state, orig_iface, read_opts, orig_timeout, watch_interval, header, BATADV_CMD_GET_MCAST_FLAGS, mcast_flags_callback); free(header); return ret; } static struct debug_table_data batctl_debug_table_mcast_flags = { .netlink_fn = netlink_print_mcast_flags, }; COMMAND_NAMED(DEBUGTABLE, mcast_flags, "mf", handle_debug_table, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_debug_table_mcast_flags, ""); batctl-2025.1/mcast_flags_json.c000066400000000000000000000010051500012142700165200ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Sven Eckelmann * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include "main.h" #include "sys.h" static struct multicast_fanout_data { uint32_t multicast_fanout; } multicast_fanout; static int parse_multicast_fanout(struct state *state, int argc, char *argv[]) { struct settings_data *settings = state->cmd->arg; struct multicast_fanout_data *data = settings->data; char *endptr; if (argc != 2) { fprintf(stderr, "Error - incorrect number of arguments (expected 1)\n"); return -EINVAL; } data->multicast_fanout = strtoul(argv[1], &endptr, 0); if (!endptr || *endptr != '\0') { fprintf(stderr, "Error - the supplied argument is invalid: %s\n", argv[1]); return -EINVAL; } return 0; } static int print_multicast_fanout(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct genlmsghdr *ghdr; int *result = arg; if (!genlmsg_valid_hdr(nlh, 0)) return NL_OK; ghdr = nlmsg_data(nlh); if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { return NL_OK; } if (!attrs[BATADV_ATTR_MULTICAST_FANOUT]) return NL_OK; printf("%u\n", nla_get_u32(attrs[BATADV_ATTR_MULTICAST_FANOUT])); *result = 0; return NL_STOP; } static int get_multicast_fanout(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_GET_MESH, NULL, print_multicast_fanout); } static int set_attrs_multicast_fanout(struct nl_msg *msg, void *arg) { struct state *state = arg; struct settings_data *settings = state->cmd->arg; struct multicast_fanout_data *data = settings->data; nla_put_u32(msg, BATADV_ATTR_MULTICAST_FANOUT, data->multicast_fanout); return 0; } static int set_multicast_fanout(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_SET_MESH, set_attrs_multicast_fanout, NULL); } static struct settings_data batctl_settings_multicast_fanout = { .data = &multicast_fanout, .parse = parse_multicast_fanout, .netlink_get = get_multicast_fanout, .netlink_set = set_multicast_fanout, }; COMMAND_NAMED(SUBCOMMAND_MIF, multicast_fanout, "mo", handle_sys_setting, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_settings_multicast_fanout, "[fanout] \tdisplay or modify multicast_fanout setting"); batctl-2025.1/multicast_forceflood.c000066400000000000000000000041511500012142700174200ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Linus Lüssing * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include "main.h" #include #include #include #include "batman_adv.h" #include "netlink.h" #include "sys.h" static struct simple_boolean_data multicast_forceflood; static int print_multicast_forceflood(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct genlmsghdr *ghdr; int *result = arg; if (!genlmsg_valid_hdr(nlh, 0)) return NL_OK; ghdr = nlmsg_data(nlh); if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { return NL_OK; } if (!attrs[BATADV_ATTR_MULTICAST_FORCEFLOOD_ENABLED]) return NL_OK; printf("%s\n", nla_get_u8(attrs[BATADV_ATTR_MULTICAST_FORCEFLOOD_ENABLED]) ? "enabled" : "disabled"); *result = 0; return NL_STOP; } static int get_multicast_forceflood(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_GET_MESH, NULL, print_multicast_forceflood); } static int set_attrs_multicast_forceflood(struct nl_msg *msg, void *arg) { struct state *state = arg; struct settings_data *settings = state->cmd->arg; struct simple_boolean_data *data = settings->data; nla_put_u8(msg, BATADV_ATTR_MULTICAST_FORCEFLOOD_ENABLED, data->val); return 0; } static int set_multicast_forceflood(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_SET_MESH, set_attrs_multicast_forceflood, NULL); } static struct settings_data batctl_settings_multicast_forceflood = { .data = &multicast_forceflood, .parse = parse_simple_boolean, .netlink_get = get_multicast_forceflood, .netlink_set = set_multicast_forceflood, }; COMMAND_NAMED(SUBCOMMAND_MIF, multicast_forceflood, "mff", handle_sys_setting, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK | COMMAND_FLAG_INVERSE, &batctl_settings_multicast_forceflood, "[0|1] \tdisplay or modify multicast_forceflood setting"); batctl-2025.1/multicast_mode.c000066400000000000000000000036761500012142700162350ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Linus Lüssing * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include "main.h" #include #include #include #include "batman_adv.h" #include "netlink.h" #include "sys.h" static struct simple_boolean_data multicast_mode; static int print_multicast_mode(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct genlmsghdr *ghdr; int *result = arg; if (!genlmsg_valid_hdr(nlh, 0)) return NL_OK; ghdr = nlmsg_data(nlh); if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { return NL_OK; } if (!attrs[BATADV_ATTR_MULTICAST_FORCEFLOOD_ENABLED]) return NL_OK; printf("%s\n", !nla_get_u8(attrs[BATADV_ATTR_MULTICAST_FORCEFLOOD_ENABLED]) ? "enabled" : "disabled"); *result = 0; return NL_STOP; } static int get_multicast_mode(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_GET_MESH, NULL, print_multicast_mode); } static int set_attrs_multicast_mode(struct nl_msg *msg, void *arg) { struct state *state = arg; struct settings_data *settings = state->cmd->arg; struct simple_boolean_data *data = settings->data; nla_put_u8(msg, BATADV_ATTR_MULTICAST_FORCEFLOOD_ENABLED, !data->val); return 0; } static int set_multicast_mode(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_SET_MESH, set_attrs_multicast_mode, NULL); } static struct settings_data batctl_settings_multicast_mode = { .data = &multicast_mode, .parse = parse_simple_boolean, .netlink_get = get_multicast_mode, .netlink_set = set_multicast_mode, }; COMMAND_NAMED(SUBCOMMAND_MIF, multicast_mode, "mm", handle_sys_setting, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_settings_multicast_mode, NULL); batctl-2025.1/neighbors.c000066400000000000000000000101721500012142700151710ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Andrew Lunn * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include #include #include #include "batman_adv.h" #include "bat-hosts.h" #include "debug.h" #include "functions.h" #include "main.h" #include "netlink.h" static const int neighbors_mandatory[] = { BATADV_ATTR_NEIGH_ADDRESS, BATADV_ATTR_HARD_IFINDEX, BATADV_ATTR_LAST_SEEN_MSECS, }; static int neighbors_callback(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); int last_seen_msecs, last_seen_secs; struct print_opts *opts = arg; unsigned int throughput_mbits; unsigned int throughput_kbits; char ifname_buf[IF_NAMESIZE]; struct bat_host *bat_host; struct genlmsghdr *ghdr; uint32_t ifindex; uint8_t *neigh; char *ifname; if (!genlmsg_valid_hdr(nlh, 0)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } ghdr = nlmsg_data(nlh); if (ghdr->cmd != BATADV_CMD_GET_NEIGHBORS) return NL_OK; if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } if (missing_mandatory_attrs(attrs, neighbors_mandatory, ARRAY_SIZE(neighbors_mandatory))) { fputs("Missing attributes from kernel\n", stderr); exit(1); } neigh = nla_data(attrs[BATADV_ATTR_NEIGH_ADDRESS]); bat_host = bat_hosts_find_by_mac((char *)neigh); if (attrs[BATADV_ATTR_HARD_IFNAME]) { ifname = nla_get_string(attrs[BATADV_ATTR_HARD_IFNAME]); } else { /* compatibility for Linux < 5.14/batman-adv < 2021.2 */ ifindex = nla_get_u32(attrs[BATADV_ATTR_HARD_IFINDEX]); if (!if_indextoname(ifindex, ifname_buf)) ifname_buf[0] = '\0'; ifname = ifname_buf; } last_seen_msecs = nla_get_u32(attrs[BATADV_ATTR_LAST_SEEN_MSECS]); last_seen_secs = last_seen_msecs / 1000; last_seen_msecs = last_seen_msecs % 1000; if (attrs[BATADV_ATTR_THROUGHPUT]) { throughput_kbits = nla_get_u32(attrs[BATADV_ATTR_THROUGHPUT]); throughput_mbits = throughput_kbits / 1000; throughput_kbits = throughput_kbits % 1000; if (!(opts->read_opt & USE_BAT_HOSTS) || !bat_host) printf("%02x:%02x:%02x:%02x:%02x:%02x ", neigh[0], neigh[1], neigh[2], neigh[3], neigh[4], neigh[5]); else printf("%17s ", bat_host->name); printf("%4i.%03is (%9u.%1u) [%10s]\n", last_seen_secs, last_seen_msecs, throughput_mbits, throughput_kbits / 100, ifname); } else { printf(" %10s ", ifname); if (!(opts->read_opt & USE_BAT_HOSTS) || !bat_host) printf("%02x:%02x:%02x:%02x:%02x:%02x ", neigh[0], neigh[1], neigh[2], neigh[3], neigh[4], neigh[5]); else printf("%17s ", bat_host->name); printf("%4i.%03is\n", last_seen_secs, last_seen_msecs); } return NL_OK; } static int netlink_print_neighbors(struct state *state, char *orig_iface, int read_opts, float orig_timeout, float watch_interval) { char *header = NULL; char *info_header; /* only parse routing algorithm name */ last_err = -EINVAL; info_header = netlink_get_info(state, BATADV_CMD_GET_ORIGINATORS, NULL); free(info_header); if (strlen(algo_name_buf) == 0) return last_err; if (!strcmp("BATMAN_IV", algo_name_buf)) header = "IF Neighbor last-seen\n"; if (!strcmp("BATMAN_V", algo_name_buf)) header = " Neighbor last-seen speed IF\n"; if (!header) return -EINVAL; return netlink_print_common(state, orig_iface, read_opts, orig_timeout, watch_interval, header, BATADV_CMD_GET_NEIGHBORS, neighbors_callback); } static struct debug_table_data batctl_debug_table_neighbors = { .netlink_fn = netlink_print_neighbors, }; COMMAND_NAMED(DEBUGTABLE, neighbors, "n", handle_debug_table, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_debug_table_neighbors, ""); batctl-2025.1/neighbors_json.c000066400000000000000000000010021500012142700162120ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Alexander Sarmanow * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include "main.h" #include "genl_json.h" static struct json_query_data batctl_json_query_neighbors = { .nlm_flags = NLM_F_DUMP, .cmd = BATADV_CMD_GET_NEIGHBORS, }; COMMAND_NAMED(JSON_MIF, neighbors_json, "nj", handle_json_query, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_json_query_neighbors, ""); batctl-2025.1/netlink.c000066400000000000000000000536161500012142700146670ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner , Andrew Lunn * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include "netlink.h" #include "main.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "bat-hosts.h" #include "batman_adv.h" #include "netlink.h" #include "functions.h" #include "main.h" /* WARNING: attributes must also be added to batadv_genl_json */ struct nla_policy batadv_netlink_policy[NUM_BATADV_ATTR] = { [BATADV_ATTR_VERSION] = { .type = NLA_STRING, }, [BATADV_ATTR_ALGO_NAME] = { .type = NLA_STRING, }, [BATADV_ATTR_MESH_IFINDEX] = { .type = NLA_U32, }, [BATADV_ATTR_MESH_IFNAME] = { .type = NLA_STRING, .maxlen = IFNAMSIZ, }, [BATADV_ATTR_MESH_ADDRESS] = { .type = NLA_UNSPEC, .minlen = ETH_ALEN, .maxlen = ETH_ALEN, }, [BATADV_ATTR_HARD_IFINDEX] = { .type = NLA_U32, }, [BATADV_ATTR_HARD_IFNAME] = { .type = NLA_STRING, .maxlen = IFNAMSIZ, }, [BATADV_ATTR_HARD_ADDRESS] = { .type = NLA_UNSPEC, .minlen = ETH_ALEN, .maxlen = ETH_ALEN, }, [BATADV_ATTR_ORIG_ADDRESS] = { .type = NLA_UNSPEC, .minlen = ETH_ALEN, .maxlen = ETH_ALEN, }, [BATADV_ATTR_TPMETER_RESULT] = { .type = NLA_U8, }, [BATADV_ATTR_TPMETER_TEST_TIME] = { .type = NLA_U32, }, [BATADV_ATTR_TPMETER_BYTES] = { .type = NLA_U64, }, [BATADV_ATTR_TPMETER_COOKIE] = { .type = NLA_U32, }, [BATADV_ATTR_PAD] = { .type = NLA_UNSPEC, }, [BATADV_ATTR_ACTIVE] = { .type = NLA_FLAG, }, [BATADV_ATTR_TT_ADDRESS] = { .type = NLA_UNSPEC, .minlen = ETH_ALEN, .maxlen = ETH_ALEN, }, [BATADV_ATTR_TT_TTVN] = { .type = NLA_U8, }, [BATADV_ATTR_TT_LAST_TTVN] = { .type = NLA_U8, }, [BATADV_ATTR_TT_CRC32] = { .type = NLA_U32, }, [BATADV_ATTR_TT_VID] = { .type = NLA_U16, }, [BATADV_ATTR_TT_FLAGS] = { .type = NLA_U32, }, [BATADV_ATTR_FLAG_BEST] = { .type = NLA_FLAG, }, [BATADV_ATTR_LAST_SEEN_MSECS] = { .type = NLA_U32, }, [BATADV_ATTR_NEIGH_ADDRESS] = { .type = NLA_UNSPEC, .minlen = ETH_ALEN, .maxlen = ETH_ALEN, }, [BATADV_ATTR_TQ] = { .type = NLA_U8, }, [BATADV_ATTR_THROUGHPUT] = { .type = NLA_U32, }, [BATADV_ATTR_BANDWIDTH_UP] = { .type = NLA_U32, }, [BATADV_ATTR_BANDWIDTH_DOWN] = { .type = NLA_U32, }, [BATADV_ATTR_ROUTER] = { .type = NLA_UNSPEC, .minlen = ETH_ALEN, .maxlen = ETH_ALEN, }, [BATADV_ATTR_BLA_OWN] = { .type = NLA_FLAG, }, [BATADV_ATTR_BLA_ADDRESS] = { .type = NLA_UNSPEC, .minlen = ETH_ALEN, .maxlen = ETH_ALEN, }, [BATADV_ATTR_BLA_VID] = { .type = NLA_U16, }, [BATADV_ATTR_BLA_BACKBONE] = { .type = NLA_UNSPEC, .minlen = ETH_ALEN, .maxlen = ETH_ALEN, }, [BATADV_ATTR_BLA_CRC] = { .type = NLA_U16, }, [BATADV_ATTR_DAT_CACHE_IP4ADDRESS] = { .type = NLA_U32, }, [BATADV_ATTR_DAT_CACHE_HWADDRESS] = { .type = NLA_UNSPEC, .minlen = ETH_ALEN, .maxlen = ETH_ALEN, }, [BATADV_ATTR_DAT_CACHE_VID] = { .type = NLA_U16, }, [BATADV_ATTR_MCAST_FLAGS] = { .type = NLA_U32, }, [BATADV_ATTR_MCAST_FLAGS_PRIV] = { .type = NLA_U32, }, [BATADV_ATTR_VLANID] = { .type = NLA_U16, }, [BATADV_ATTR_AGGREGATED_OGMS_ENABLED] = { .type = NLA_U8, }, [BATADV_ATTR_AP_ISOLATION_ENABLED] = { .type = NLA_U8, }, [BATADV_ATTR_ISOLATION_MARK] = { .type = NLA_U32, }, [BATADV_ATTR_ISOLATION_MASK] = { .type = NLA_U32, }, [BATADV_ATTR_BONDING_ENABLED] = { .type = NLA_U8, }, [BATADV_ATTR_BRIDGE_LOOP_AVOIDANCE_ENABLED] = { .type = NLA_U8, }, [BATADV_ATTR_DISTRIBUTED_ARP_TABLE_ENABLED] = { .type = NLA_U8, }, [BATADV_ATTR_FRAGMENTATION_ENABLED] = { .type = NLA_U8, }, [BATADV_ATTR_GW_BANDWIDTH_DOWN] = { .type = NLA_U32, }, [BATADV_ATTR_GW_BANDWIDTH_UP] = { .type = NLA_U32, }, [BATADV_ATTR_GW_MODE] = { .type = NLA_U8, }, [BATADV_ATTR_GW_SEL_CLASS] = { .type = NLA_U32, }, [BATADV_ATTR_HOP_PENALTY] = { .type = NLA_U8, }, [BATADV_ATTR_LOG_LEVEL] = { .type = NLA_U32, }, [BATADV_ATTR_MULTICAST_FORCEFLOOD_ENABLED] = { .type = NLA_U8, }, [BATADV_ATTR_NETWORK_CODING_ENABLED] = { .type = NLA_U8, }, [BATADV_ATTR_ORIG_INTERVAL] = { .type = NLA_U32, }, [BATADV_ATTR_ELP_INTERVAL] = { .type = NLA_U32, }, [BATADV_ATTR_THROUGHPUT_OVERRIDE] = { .type = NLA_U32, }, [BATADV_ATTR_MULTICAST_FANOUT] = { .type = NLA_U32, }, }; int netlink_create(struct state *state) { int ret; state->sock = NULL; state->cb = NULL; state->batadv_family = 0; state->sock = nl_socket_alloc(); if (!state->sock) return -ENOMEM; ret = genl_connect(state->sock); if (ret < 0) goto err_free_sock; state->batadv_family = genl_ctrl_resolve(state->sock, BATADV_NL_NAME); if (state->batadv_family < 0) { ret = -EOPNOTSUPP; goto err_free_sock; } state->cb = nl_cb_alloc(NL_CB_DEFAULT); if (!state->cb) { ret = -ENOMEM; goto err_free_family; } return 0; err_free_family: state->batadv_family = 0; err_free_sock: nl_socket_free(state->sock); state->sock = NULL; return ret; } void netlink_destroy(struct state *state) { if (state->cb) { nl_cb_put(state->cb); state->cb = NULL; } if (state->sock) { nl_socket_free(state->sock); state->sock = NULL; } } int last_err; char algo_name_buf[256] = ""; int64_t mcast_flags = -EOPNOTSUPP; int64_t mcast_flags_priv = -EOPNOTSUPP; int missing_mandatory_attrs(struct nlattr *attrs[], const int mandatory[], int num) { int i; for (i = 0; i < num; i++) if (!attrs[mandatory[i]]) return -EINVAL; return 0; } int netlink_print_error(struct sockaddr_nl *nla __maybe_unused, struct nlmsgerr *nlerr, void *arg __maybe_unused) { if (nlerr->error != -EOPNOTSUPP) fprintf(stderr, "Error received: %s\n", strerror(-nlerr->error)); last_err = nlerr->error; return NL_STOP; } int netlink_stop_callback(struct nl_msg *msg, void *arg __maybe_unused) { struct nlmsghdr *nlh = nlmsg_hdr(msg); int *error = nlmsg_data(nlh); if (*error) fprintf(stderr, "Error received: %s\n", strerror(-*error)); return NL_STOP; } static const int info_mandatory[] = { BATADV_ATTR_MESH_IFINDEX, BATADV_ATTR_MESH_IFNAME, }; static const int info_hard_mandatory[] = { BATADV_ATTR_VERSION, BATADV_ATTR_ALGO_NAME, BATADV_ATTR_HARD_IFNAME, BATADV_ATTR_HARD_ADDRESS, }; static int info_callback(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct print_opts *opts = arg; const uint8_t *primary_mac; uint16_t bla_group_id = 0; const char *extra_header; char *extra_info = NULL; struct genlmsghdr *ghdr; const uint8_t *mesh_mac; const char *primary_if; const char *algo_name; const char *mesh_name; const char *version; uint8_t ttvn = 0; int ret; if (!genlmsg_valid_hdr(nlh, 0)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } ghdr = nlmsg_data(nlh); if (ghdr->cmd != BATADV_CMD_GET_MESH_INFO) return NL_OK; if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } if (missing_mandatory_attrs(attrs, info_mandatory, ARRAY_SIZE(info_mandatory))) { fputs("Missing attributes from kernel\n", stderr); exit(1); } mesh_name = nla_get_string(attrs[BATADV_ATTR_MESH_IFNAME]); mesh_mac = nla_data(attrs[BATADV_ATTR_MESH_ADDRESS]); if (attrs[BATADV_ATTR_HARD_IFNAME]) { if (missing_mandatory_attrs(attrs, info_hard_mandatory, ARRAY_SIZE(info_hard_mandatory))) { fputs("Missing attributes from kernel\n", stderr); exit(1); } version = nla_get_string(attrs[BATADV_ATTR_VERSION]); algo_name = nla_get_string(attrs[BATADV_ATTR_ALGO_NAME]); primary_if = nla_get_string(attrs[BATADV_ATTR_HARD_IFNAME]); primary_mac = nla_data(attrs[BATADV_ATTR_HARD_ADDRESS]); snprintf(algo_name_buf, sizeof(algo_name_buf), "%s", algo_name); if (attrs[BATADV_ATTR_TT_TTVN]) ttvn = nla_get_u8(attrs[BATADV_ATTR_TT_TTVN]); if (attrs[BATADV_ATTR_BLA_CRC]) bla_group_id = nla_get_u16(attrs[BATADV_ATTR_BLA_CRC]); if (attrs[BATADV_ATTR_MCAST_FLAGS]) mcast_flags = nla_get_u32(attrs[BATADV_ATTR_MCAST_FLAGS]); else mcast_flags = -EOPNOTSUPP; if (attrs[BATADV_ATTR_MCAST_FLAGS_PRIV]) mcast_flags_priv = nla_get_u32(attrs[BATADV_ATTR_MCAST_FLAGS_PRIV]); else mcast_flags_priv = -EOPNOTSUPP; switch (opts->nl_cmd) { case BATADV_CMD_GET_TRANSTABLE_LOCAL: ret = asprintf(&extra_info, ", TTVN: %u", ttvn); if (ret < 0) extra_info = NULL; break; case BATADV_CMD_GET_BLA_BACKBONE: case BATADV_CMD_GET_BLA_CLAIM: ret = asprintf(&extra_info, ", group id: 0x%04x", bla_group_id); if (ret < 0) extra_info = NULL; break; default: extra_info = strdup(""); break; } if (opts->static_header) extra_header = opts->static_header; else extra_header = ""; ret = asprintf(&opts->remaining_header, "[B.A.T.M.A.N. adv %s, MainIF/MAC: %s/%02x:%02x:%02x:%02x:%02x:%02x (%s/%02x:%02x:%02x:%02x:%02x:%02x %s)%s]\n%s", version, primary_if, primary_mac[0], primary_mac[1], primary_mac[2], primary_mac[3], primary_mac[4], primary_mac[5], mesh_name, mesh_mac[0], mesh_mac[1], mesh_mac[2], mesh_mac[3], mesh_mac[4], mesh_mac[5], algo_name, extra_info, extra_header); if (ret < 0) opts->remaining_header = NULL; if (extra_info) free(extra_info); } else { ret = asprintf(&opts->remaining_header, "BATMAN mesh %s disabled\n", mesh_name); if (ret < 0) opts->remaining_header = NULL; } return NL_OK; } char *netlink_get_info(struct state *state, uint8_t nl_cmd, const char *header) { struct print_opts opts = { .read_opt = 0, .nl_cmd = nl_cmd, .remaining_header = NULL, .static_header = header, }; struct nl_msg *msg; struct nl_cb *cb; int ret; msg = nlmsg_alloc(); if (!msg) return NULL; genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, state->batadv_family, 0, 0, BATADV_CMD_GET_MESH_INFO, 1); nla_put_u32(msg, BATADV_ATTR_MESH_IFINDEX, state->mesh_ifindex); nl_send_auto_complete(state->sock, msg); nlmsg_free(msg); cb = nl_cb_alloc(NL_CB_DEFAULT); if (!cb) return NULL; nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, info_callback, &opts); nl_cb_err(cb, NL_CB_CUSTOM, netlink_print_error, NULL); ret = nl_recvmsgs(state->sock, cb); if (ret < 0) return opts.remaining_header; nl_wait_for_ack(state->sock); return opts.remaining_header; } void netlink_print_remaining_header(struct print_opts *opts) { if (!opts->remaining_header) return; fputs(opts->remaining_header, stdout); free(opts->remaining_header); opts->remaining_header = NULL; } int netlink_print_common_cb(struct nl_msg *msg, void *arg) { struct print_opts *opts = arg; netlink_print_remaining_header(opts); return opts->callback(msg, arg); } int netlink_print_common(struct state *state, char *orig_iface, int read_opt, float orig_timeout, float watch_interval, const char *header, uint8_t nl_cmd, nl_recvmsg_msg_cb_t callback) { struct print_opts opts = { .read_opt = read_opt, .orig_timeout = orig_timeout, .watch_interval = watch_interval, .remaining_header = NULL, .callback = callback, }; int hardifindex = 0; struct nl_msg *msg; if (!state->sock) { last_err = -EOPNOTSUPP; return last_err; } if (orig_iface) { hardifindex = if_nametoindex(orig_iface); if (!hardifindex) { fprintf(stderr, "Interface %s is unknown\n", orig_iface); last_err = -ENODEV; return last_err; } } bat_hosts_init(read_opt); nl_cb_set(state->cb, NL_CB_VALID, NL_CB_CUSTOM, netlink_print_common_cb, &opts); nl_cb_set(state->cb, NL_CB_FINISH, NL_CB_CUSTOM, netlink_stop_callback, NULL); nl_cb_err(state->cb, NL_CB_CUSTOM, netlink_print_error, NULL); do { if (read_opt & CLR_CONT_READ) /* clear screen, set cursor back to 0,0 */ printf("\033[2J\033[0;0f"); if (!(read_opt & SKIP_HEADER)) opts.remaining_header = netlink_get_info(state, nl_cmd, header); msg = nlmsg_alloc(); if (!msg) continue; genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, state->batadv_family, 0, NLM_F_DUMP, nl_cmd, 1); nla_put_u32(msg, BATADV_ATTR_MESH_IFINDEX, state->mesh_ifindex); if (hardifindex) nla_put_u32(msg, BATADV_ATTR_HARD_IFINDEX, hardifindex); nl_send_auto_complete(state->sock, msg); nlmsg_free(msg); last_err = 0; nl_recvmsgs(state->sock, state->cb); /* the header should still be printed when no entry was received */ if (!last_err) netlink_print_remaining_header(&opts); if (!last_err && read_opt & (CONT_READ | CLR_CONT_READ)) usleep(1000000 * watch_interval); } while (!last_err && read_opt & (CONT_READ | CLR_CONT_READ)); bat_hosts_free(); return last_err; } static int nlquery_error_cb(struct sockaddr_nl *nla __maybe_unused, struct nlmsgerr *nlerr, void *arg) { struct nlquery_opts *query_opts = arg; query_opts->err = nlerr->error; return NL_STOP; } static int nlquery_stop_cb(struct nl_msg *msg, void *arg) { struct nlmsghdr *nlh = nlmsg_hdr(msg); struct nlquery_opts *query_opts = arg; int *error = nlmsg_data(nlh); if (*error) query_opts->err = *error; return NL_STOP; } int netlink_query_common(struct state *state, unsigned int mesh_ifindex, uint8_t nl_cmd, nl_recvmsg_msg_cb_t callback, nl_recvmsg_msg_cb_t attribute_cb, int flags, struct nlquery_opts *query_opts) { struct nl_msg *msg; struct nl_cb *cb; int ret; query_opts->err = 0; cb = nl_cb_alloc(NL_CB_DEFAULT); if (!cb) return -ENOMEM; nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, callback, query_opts); nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, nlquery_stop_cb, query_opts); nl_cb_err(cb, NL_CB_CUSTOM, nlquery_error_cb, query_opts); msg = nlmsg_alloc(); if (!msg) { query_opts->err = -ENOMEM; goto err_free_cb; } genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, state->batadv_family, 0, flags, nl_cmd, 1); nla_put_u32(msg, BATADV_ATTR_MESH_IFINDEX, mesh_ifindex); if (attribute_cb) { ret = attribute_cb(msg, state); if (ret < 0) { nlmsg_free(msg); goto err_free_cb; } } nl_send_auto_complete(state->sock, msg); nlmsg_free(msg); ret = nl_recvmsgs(state->sock, cb); if (ret < 0) { query_opts->err = ret; goto err_free_cb; } if (!(flags & NLM_F_DUMP)) nl_wait_for_ack(state->sock); err_free_cb: nl_cb_put(cb); return query_opts->err; } static const int translate_mac_netlink_mandatory[] = { BATADV_ATTR_TT_ADDRESS, BATADV_ATTR_ORIG_ADDRESS, }; struct translate_mac_netlink_opts { struct ether_addr mac; uint8_t found:1; struct nlquery_opts query_opts; }; static int translate_mac_netlink_cb(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct translate_mac_netlink_opts *opts; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct nlquery_opts *query_opts = arg; struct genlmsghdr *ghdr; uint8_t *addr; uint8_t *orig; opts = container_of(query_opts, struct translate_mac_netlink_opts, query_opts); if (!genlmsg_valid_hdr(nlh, 0)) return NL_OK; ghdr = nlmsg_data(nlh); if (ghdr->cmd != BATADV_CMD_GET_TRANSTABLE_GLOBAL) return NL_OK; if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { return NL_OK; } if (missing_mandatory_attrs(attrs, translate_mac_netlink_mandatory, ARRAY_SIZE(translate_mac_netlink_mandatory))) return NL_OK; addr = nla_data(attrs[BATADV_ATTR_TT_ADDRESS]); orig = nla_data(attrs[BATADV_ATTR_ORIG_ADDRESS]); if (!attrs[BATADV_ATTR_FLAG_BEST]) return NL_OK; if (memcmp(&opts->mac, addr, ETH_ALEN) != 0) return NL_OK; memcpy(&opts->mac, orig, ETH_ALEN); opts->found = true; opts->query_opts.err = 0; return NL_OK; } int translate_mac_netlink(struct state *state, const struct ether_addr *mac, struct ether_addr *mac_out) { struct translate_mac_netlink_opts opts = { .found = false, .query_opts = { .err = 0, }, }; int ret; memcpy(&opts.mac, mac, ETH_ALEN); ret = netlink_query_common(state, state->mesh_ifindex, BATADV_CMD_GET_TRANSTABLE_GLOBAL, translate_mac_netlink_cb, NULL, NLM_F_DUMP, &opts.query_opts); if (ret < 0) return ret; if (!opts.found) return -ENOENT; memcpy(mac_out, &opts.mac, ETH_ALEN); return 0; } static const int get_nexthop_netlink_mandatory[] = { BATADV_ATTR_ORIG_ADDRESS, BATADV_ATTR_NEIGH_ADDRESS, BATADV_ATTR_HARD_IFINDEX, }; struct get_nexthop_netlink_opts { struct ether_addr mac; uint8_t *nexthop; char *ifname; uint8_t found:1; struct nlquery_opts query_opts; }; static int get_nexthop_netlink_cb(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct nlquery_opts *query_opts = arg; struct get_nexthop_netlink_opts *opts; struct genlmsghdr *ghdr; const uint8_t *neigh; const uint8_t *orig; const char *ifname; uint32_t index; opts = container_of(query_opts, struct get_nexthop_netlink_opts, query_opts); if (!genlmsg_valid_hdr(nlh, 0)) return NL_OK; ghdr = nlmsg_data(nlh); if (ghdr->cmd != BATADV_CMD_GET_ORIGINATORS) return NL_OK; if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { return NL_OK; } if (missing_mandatory_attrs(attrs, get_nexthop_netlink_mandatory, ARRAY_SIZE(get_nexthop_netlink_mandatory))) return NL_OK; orig = nla_data(attrs[BATADV_ATTR_ORIG_ADDRESS]); neigh = nla_data(attrs[BATADV_ATTR_NEIGH_ADDRESS]); index = nla_get_u32(attrs[BATADV_ATTR_HARD_IFINDEX]); if (!attrs[BATADV_ATTR_FLAG_BEST]) return NL_OK; if (memcmp(&opts->mac, orig, ETH_ALEN) != 0) return NL_OK; /* save result */ memcpy(opts->nexthop, neigh, ETH_ALEN); if (attrs[BATADV_ATTR_HARD_IFNAME]) { ifname = nla_get_string(attrs[BATADV_ATTR_HARD_IFNAME]); strncpy(opts->ifname, ifname, IFNAMSIZ); } else { /* compatibility for Linux < 5.14/batman-adv < 2021.2 */ ifname = if_indextoname(index, opts->ifname); if (!ifname) return NL_OK; } opts->found = true; opts->query_opts.err = 0; return NL_OK; } int get_nexthop_netlink(struct state *state, const struct ether_addr *mac, uint8_t *nexthop, char *ifname) { struct get_nexthop_netlink_opts opts = { .nexthop = 0, .found = false, .query_opts = { .err = 0, }, }; int ret; memcpy(&opts.mac, mac, ETH_ALEN); opts.nexthop = nexthop; opts.ifname = ifname; ret = netlink_query_common(state, state->mesh_ifindex, BATADV_CMD_GET_ORIGINATORS, get_nexthop_netlink_cb, NULL, NLM_F_DUMP, &opts.query_opts); if (ret < 0) return ret; if (!opts.found) return -ENOENT; return 0; } static const int get_primarymac_netlink_mandatory[] = { BATADV_ATTR_HARD_ADDRESS, }; struct get_primarymac_netlink_opts { uint8_t *primarymac; uint8_t found:1; struct nlquery_opts query_opts; }; static int get_primarymac_netlink_cb(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct get_primarymac_netlink_opts *opts; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct nlquery_opts *query_opts = arg; const uint8_t *primary_mac; struct genlmsghdr *ghdr; opts = container_of(query_opts, struct get_primarymac_netlink_opts, query_opts); if (!genlmsg_valid_hdr(nlh, 0)) return NL_OK; ghdr = nlmsg_data(nlh); if (ghdr->cmd != BATADV_CMD_GET_MESH_INFO) return NL_OK; if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { return NL_OK; } if (missing_mandatory_attrs(attrs, get_primarymac_netlink_mandatory, ARRAY_SIZE(get_primarymac_netlink_mandatory))) return NL_OK; primary_mac = nla_data(attrs[BATADV_ATTR_HARD_ADDRESS]); /* save result */ memcpy(opts->primarymac, primary_mac, ETH_ALEN); opts->found = true; opts->query_opts.err = 0; return NL_OK; } int get_primarymac_netlink(struct state *state, uint8_t *primarymac) { struct get_primarymac_netlink_opts opts = { .primarymac = 0, .found = false, .query_opts = { .err = 0, }, }; int ret; opts.primarymac = primarymac; ret = netlink_query_common(state, state->mesh_ifindex, BATADV_CMD_GET_MESH_INFO, get_primarymac_netlink_cb, NULL, 0, &opts.query_opts); if (ret < 0) return ret; if (!opts.found) return -ENOENT; return 0; } struct get_algoname_netlink_opts { char *algoname; size_t algoname_len; uint8_t found:1; struct nlquery_opts query_opts; }; static int get_algoname_netlink_cb(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct get_algoname_netlink_opts *opts; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct nlquery_opts *query_opts = arg; static const int mandatory[] = { BATADV_ATTR_ALGO_NAME, }; struct genlmsghdr *ghdr; const char *algoname; opts = container_of(query_opts, struct get_algoname_netlink_opts, query_opts); if (!genlmsg_valid_hdr(nlh, 0)) return NL_OK; ghdr = nlmsg_data(nlh); if (ghdr->cmd != BATADV_CMD_GET_MESH) return NL_OK; if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { return NL_OK; } if (missing_mandatory_attrs(attrs, mandatory, ARRAY_SIZE(mandatory))) return NL_OK; algoname = nla_data(attrs[BATADV_ATTR_ALGO_NAME]); /* save result */ strncpy(opts->algoname, algoname, opts->algoname_len); if (opts->algoname_len > 0) opts->algoname[opts->algoname_len - 1] = '\0'; opts->found = true; opts->query_opts.err = 0; return NL_OK; } int get_algoname_netlink(struct state *state, unsigned int mesh_ifindex, char *algoname, size_t algoname_len) { struct get_algoname_netlink_opts opts = { .algoname = algoname, .algoname_len = algoname_len, .found = false, .query_opts = { .err = 0, }, }; int ret; ret = netlink_query_common(state, mesh_ifindex, BATADV_CMD_GET_MESH, get_algoname_netlink_cb, NULL, 0, &opts.query_opts); if (ret < 0) return ret; if (!opts.found) return -EOPNOTSUPP; return 0; } batctl-2025.1/netlink.h000066400000000000000000000041531500012142700146640ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner , Andrew Lunn * * License-Filename: LICENSES/preferred/GPL-2.0 */ #ifndef _BATCTL_NETLINK_H #define _BATCTL_NETLINK_H #include #include #include struct state; struct print_opts { int read_opt; float orig_timeout; float watch_interval; nl_recvmsg_msg_cb_t callback; char *remaining_header; const char *static_header; uint8_t nl_cmd; }; struct nlquery_opts { int err; }; struct ether_addr; int netlink_create(struct state *state); void netlink_destroy(struct state *state); char *netlink_get_info(struct state *state, uint8_t nl_cmd, const char *header); int translate_mac_netlink(struct state *state, const struct ether_addr *mac, struct ether_addr *mac_out); int get_nexthop_netlink(struct state *state, const struct ether_addr *mac, uint8_t *nexthop, char *ifname); int get_primarymac_netlink(struct state *state, uint8_t *primarymac); int get_algoname_netlink(struct state *state, unsigned int mesh_ifindex, char *algoname, size_t algoname_len); extern struct nla_policy batadv_netlink_policy[]; int missing_mandatory_attrs(struct nlattr *attrs[], const int mandatory[], int num); int netlink_print_common(struct state *state, char *orig_iface, int read_opt, float orig_timeout, float watch_interval, const char *header, uint8_t nl_cmd, nl_recvmsg_msg_cb_t callback); int netlink_print_common_cb(struct nl_msg *msg, void *arg); int netlink_stop_callback(struct nl_msg *msg, void *arg); int netlink_print_error(struct sockaddr_nl *nla, struct nlmsgerr *nlerr, void *arg); void netlink_print_remaining_header(struct print_opts *opts); int netlink_query_common(struct state *state, unsigned int mesh_ifindex, uint8_t nl_cmd, nl_recvmsg_msg_cb_t callback, nl_recvmsg_msg_cb_t attribute_cb, int flags, struct nlquery_opts *query_opts); extern char algo_name_buf[256]; extern int last_err; extern int64_t mcast_flags; extern int64_t mcast_flags_priv; #endif /* _BATCTL_NETLINK_H */ batctl-2025.1/network_coding.c000066400000000000000000000030321500012142700162220ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Martin Hundebøll * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include "main.h" #include #include #include #include "batman_adv.h" #include "netlink.h" #include "sys.h" static struct simple_boolean_data network_coding; static int print_network_coding(struct nl_msg *msg, void *arg) { return sys_simple_print_boolean(msg, arg, BATADV_ATTR_NETWORK_CODING_ENABLED); } static int get_network_coding(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_GET_MESH, NULL, print_network_coding); } static int set_attrs_network_coding(struct nl_msg *msg, void *arg) { struct state *state = arg; struct settings_data *settings = state->cmd->arg; struct simple_boolean_data *data = settings->data; nla_put_u8(msg, BATADV_ATTR_NETWORK_CODING_ENABLED, data->val); return 0; } static int set_network_coding(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_SET_MESH, set_attrs_network_coding, NULL); } static struct settings_data batctl_settings_network_coding = { .data = &network_coding, .parse = parse_simple_boolean, .netlink_get = get_network_coding, .netlink_set = set_network_coding, }; COMMAND_NAMED(SUBCOMMAND_MIF, network_coding, "nc", handle_sys_setting, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_settings_network_coding, "[0|1] \tdisplay or modify network_coding setting"); batctl-2025.1/orig_interval.c000066400000000000000000000046321500012142700160610ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include "main.h" #include "sys.h" static struct orig_interval_data { uint32_t orig_interval; } orig_interval; static int parse_orig_interval(struct state *state, int argc, char *argv[]) { struct settings_data *settings = state->cmd->arg; struct orig_interval_data *data = settings->data; char *endptr; if (argc != 2) { fprintf(stderr, "Error - incorrect number of arguments (expected 1)\n"); return -EINVAL; } data->orig_interval = strtoul(argv[1], &endptr, 0); if (!endptr || *endptr != '\0') { fprintf(stderr, "Error - the supplied argument is invalid: %s\n", argv[1]); return -EINVAL; } return 0; } static int print_orig_interval(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct genlmsghdr *ghdr; int *result = arg; if (!genlmsg_valid_hdr(nlh, 0)) return NL_OK; ghdr = nlmsg_data(nlh); if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { return NL_OK; } if (!attrs[BATADV_ATTR_ORIG_INTERVAL]) return NL_OK; printf("%u\n", nla_get_u32(attrs[BATADV_ATTR_ORIG_INTERVAL])); *result = 0; return NL_STOP; } static int get_orig_interval(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_GET_MESH, NULL, print_orig_interval); } static int set_attrs_orig_interval(struct nl_msg *msg, void *arg) { struct state *state = arg; struct settings_data *settings = state->cmd->arg; struct orig_interval_data *data = settings->data; nla_put_u32(msg, BATADV_ATTR_ORIG_INTERVAL, data->orig_interval); return 0; } static int set_orig_interval(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_SET_MESH, set_attrs_orig_interval, NULL); } static struct settings_data batctl_settings_orig_interval = { .data = &orig_interval, .parse = parse_orig_interval, .netlink_get = get_orig_interval, .netlink_set = set_orig_interval, }; COMMAND_NAMED(SUBCOMMAND_MIF, orig_interval, "it", handle_sys_setting, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_settings_orig_interval, "[interval] \tdisplay or modify orig_interval setting"); batctl-2025.1/originators.c000066400000000000000000000141061500012142700155520ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Andrew Lunn * Sven Eckelmann * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include #include #include #include #include "batman_adv.h" #include "bat-hosts.h" #include "debug.h" #include "functions.h" #include "main.h" #include "netlink.h" static const int originators_mandatory[] = { BATADV_ATTR_ORIG_ADDRESS, BATADV_ATTR_NEIGH_ADDRESS, BATADV_ATTR_HARD_IFINDEX, BATADV_ATTR_LAST_SEEN_MSECS, }; static int originators_callback(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); unsigned int throughput_mbits; unsigned int throughput_kbits; struct print_opts *opts = arg; char ifname_buf[IF_NAMESIZE]; struct bat_host *bat_host; struct genlmsghdr *ghdr; int last_seen_msecs; int last_seen_secs; uint32_t ifindex; float last_seen; uint8_t *neigh; uint8_t *orig; char *ifname; char c = ' '; uint8_t tq; if (!genlmsg_valid_hdr(nlh, 0)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } ghdr = nlmsg_data(nlh); if (ghdr->cmd != BATADV_CMD_GET_ORIGINATORS) return NL_OK; if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } if (missing_mandatory_attrs(attrs, originators_mandatory, ARRAY_SIZE(originators_mandatory))) { fputs("Missing attributes from kernel\n", stderr); exit(1); } orig = nla_data(attrs[BATADV_ATTR_ORIG_ADDRESS]); neigh = nla_data(attrs[BATADV_ATTR_NEIGH_ADDRESS]); if (attrs[BATADV_ATTR_HARD_IFNAME]) { ifname = nla_get_string(attrs[BATADV_ATTR_HARD_IFNAME]); } else { /* compatibility for Linux < 5.14/batman-adv < 2021.2 */ ifindex = nla_get_u32(attrs[BATADV_ATTR_HARD_IFINDEX]); if (!if_indextoname(ifindex, ifname_buf)) ifname_buf[0] = '\0'; ifname = ifname_buf; } if (attrs[BATADV_ATTR_FLAG_BEST]) c = '*'; last_seen_msecs = nla_get_u32(attrs[BATADV_ATTR_LAST_SEEN_MSECS]); last_seen = (float)last_seen_msecs / 1000.0; last_seen_secs = last_seen_msecs / 1000; last_seen_msecs = last_seen_msecs % 1000; /* skip timed out originators */ if (opts->read_opt & NO_OLD_ORIGS) if (last_seen > opts->orig_timeout) return NL_OK; if (attrs[BATADV_ATTR_THROUGHPUT]) { throughput_kbits = nla_get_u32(attrs[BATADV_ATTR_THROUGHPUT]); throughput_mbits = throughput_kbits / 1000; throughput_kbits = throughput_kbits % 1000; if (!(opts->read_opt & USE_BAT_HOSTS)) { printf(" %c %02x:%02x:%02x:%02x:%02x:%02x %4i.%03is (%9u.%1u) %02x:%02x:%02x:%02x:%02x:%02x [%10s]\n", c, orig[0], orig[1], orig[2], orig[3], orig[4], orig[5], last_seen_secs, last_seen_msecs, throughput_mbits, throughput_kbits / 100, neigh[0], neigh[1], neigh[2], neigh[3], neigh[4], neigh[5], ifname); } else { bat_host = bat_hosts_find_by_mac((char *)orig); if (bat_host) printf(" %c %17s ", c, bat_host->name); else printf(" %c %02x:%02x:%02x:%02x:%02x:%02x ", c, orig[0], orig[1], orig[2], orig[3], orig[4], orig[5]); printf("%4i.%03is (%9u.%1u) ", last_seen_secs, last_seen_msecs, throughput_mbits, throughput_kbits / 100); bat_host = bat_hosts_find_by_mac((char *)neigh); if (bat_host) printf(" %c %17s ", c, bat_host->name); else printf(" %02x:%02x:%02x:%02x:%02x:%02x ", neigh[0], neigh[1], neigh[2], neigh[3], neigh[4], neigh[5]); printf("[%10s]\n", ifname); } } if (attrs[BATADV_ATTR_TQ]) { tq = nla_get_u8(attrs[BATADV_ATTR_TQ]); if (!(opts->read_opt & USE_BAT_HOSTS)) { printf(" %c %02x:%02x:%02x:%02x:%02x:%02x %4i.%03is (%3i) %02x:%02x:%02x:%02x:%02x:%02x [%10s]\n", c, orig[0], orig[1], orig[2], orig[3], orig[4], orig[5], last_seen_secs, last_seen_msecs, tq, neigh[0], neigh[1], neigh[2], neigh[3], neigh[4], neigh[5], ifname); } else { bat_host = bat_hosts_find_by_mac((char *)orig); if (bat_host) printf(" %c %17s ", c, bat_host->name); else printf(" %c %02x:%02x:%02x:%02x:%02x:%02x ", c, orig[0], orig[1], orig[2], orig[3], orig[4], orig[5]); printf("%4i.%03is (%3i) ", last_seen_secs, last_seen_msecs, tq); bat_host = bat_hosts_find_by_mac((char *)neigh); if (bat_host) printf("%17s ", bat_host->name); else printf("%02x:%02x:%02x:%02x:%02x:%02x ", neigh[0], neigh[1], neigh[2], neigh[3], neigh[4], neigh[5]); printf("[%10s]\n", ifname); } } return NL_OK; } static int netlink_print_originators(struct state *state, char *orig_iface, int read_opts, float orig_timeout, float watch_interval) { char *header = NULL; char *info_header; /* only parse routing algorithm name */ last_err = -EINVAL; info_header = netlink_get_info(state, BATADV_CMD_GET_ORIGINATORS, NULL); free(info_header); if (strlen(algo_name_buf) == 0) return last_err; if (!strcmp("BATMAN_IV", algo_name_buf)) header = " Originator last-seen (#/255) Nexthop [outgoingIF]\n"; if (!strcmp("BATMAN_V", algo_name_buf)) header = " Originator last-seen ( throughput) Nexthop [outgoingIF]\n"; if (!header) return -EINVAL; return netlink_print_common(state, orig_iface, read_opts, orig_timeout, watch_interval, header, BATADV_CMD_GET_ORIGINATORS, originators_callback); } static struct debug_table_data batctl_debug_table_originators = { .netlink_fn = netlink_print_originators, .option_timeout_interval = 1, .option_orig_iface = 1, }; COMMAND_NAMED(DEBUGTABLE, originators, "o", handle_debug_table, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_debug_table_originators, ""); batctl-2025.1/originators_json.c000066400000000000000000000010121500012142700165730ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Alexander Sarmanow * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include "main.h" #include "genl_json.h" static struct json_query_data batctl_json_query_originators = { .nlm_flags = NLM_F_DUMP, .cmd = BATADV_CMD_GET_ORIGINATORS, }; COMMAND_NAMED(JSON_MIF, originators_json, "oj", handle_json_query, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_json_query_originators, ""); batctl-2025.1/ping.c000066400000000000000000000220461500012142700141510ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Andreas Langer , Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "batadv_packet_compat.h" #include "main.h" #include "functions.h" #include "bat-hosts.h" #include "icmp_helper.h" static volatile sig_atomic_t is_aborted; static void ping_usage(void) { fprintf(stderr, "Usage: batctl [options] ping [parameters] mac|bat-host|host_name|IPv4_address\n"); fprintf(stderr, "parameters:\n"); fprintf(stderr, " \t -c ping packet count\n"); fprintf(stderr, " \t -h print this help\n"); fprintf(stderr, " \t -i interval in seconds\n"); fprintf(stderr, " \t -t timeout in seconds\n"); fprintf(stderr, " \t -R record route\n"); fprintf(stderr, " \t -T don't try to translate mac to originator address\n"); } static void sig_handler(int sig) { switch (sig) { case SIGINT: case SIGTERM: is_aborted = 1; break; default: break; } } static int ping(struct state *state, int argc, char **argv) { struct batadv_icmp_packet_rr icmp_packet_out; struct batadv_icmp_packet_rr icmp_packet_in; uint8_t last_rr[BATADV_RR_LEN][ETH_ALEN]; struct timespec loop_interval = {0, 0}; struct ether_addr *dst_mac = NULL; struct ether_addr *rr_mac = NULL; int disable_translate_mac = 0; double fractional_part = 0.0; unsigned int seq_counter = 0; unsigned int packets_out = 0; unsigned int packets_in = 0; double ping_interval = 0.0; double integral_part = 0.0; unsigned int packets_loss; struct bat_host *bat_host; struct bat_host *rr_host; uint8_t last_rr_cur = 0; int ret = EXIT_FAILURE; int loop_count = -1; int found_args = 1; size_t packet_len; struct timeval tv; double time_delta; float mdev = 0.0; ssize_t read_len; char *dst_string; char *mac_string; char *rr_string; float min = 0.0; float max = 0.0; float avg = 0.0; int timeout = 1; char *endptr; int optchar; int rr = 0; int res; int i; while ((optchar = getopt(argc, argv, "hc:i:t:RT")) != -1) { switch (optchar) { case 'c': loop_count = strtol(optarg, NULL, 10); if (loop_count < 1) loop_count = -1; found_args += ((*((char *)(optarg - 1)) == optchar) ? 1 : 2); break; case 'h': ping_usage(); return EXIT_SUCCESS; case 'i': errno = 0; ping_interval = strtod(optarg, &endptr); if (errno || *endptr != '\0') { fprintf(stderr, "Error - invalid ping interval '%s'\n", optarg); goto out; } ping_interval = fmax(ping_interval, 0.001); fractional_part = modf(ping_interval, &integral_part); loop_interval.tv_sec = (time_t)integral_part; loop_interval.tv_nsec = (long)(fractional_part * 1000000000l); found_args += ((*((char *)(optarg - 1)) == optchar) ? 1 : 2); break; case 't': timeout = strtol(optarg, NULL, 10); if (timeout < 1) timeout = 1; found_args += ((*((char *)(optarg - 1)) == optchar) ? 1 : 2); break; case 'R': rr = 1; found_args++; break; case 'T': disable_translate_mac = 1; found_args += 1; break; default: ping_usage(); return EXIT_FAILURE; } } if (argc <= found_args) { fprintf(stderr, "Error - target mac address or bat-host name not specified\n"); ping_usage(); return EXIT_FAILURE; } dst_string = argv[found_args]; bat_hosts_init(0); bat_host = bat_hosts_find_by_name(dst_string); if (bat_host) dst_mac = &bat_host->mac_addr; if (!dst_mac) { dst_mac = resolve_mac(dst_string); if (!dst_mac) { fprintf(stderr, "Error - mac address of the ping destination could not be resolved and is not a bat-host name: %s\n", dst_string); goto out; } } if (!disable_translate_mac) dst_mac = translate_mac(state, dst_mac); mac_string = ether_ntoa_long(dst_mac); signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); icmp_interfaces_init(); packet_len = sizeof(struct batadv_icmp_packet); memset(&icmp_packet_out, 0, sizeof(icmp_packet_out)); memcpy(&icmp_packet_out.dst, dst_mac, ETH_ALEN); icmp_packet_out.packet_type = BATADV_ICMP; icmp_packet_out.version = BATADV_COMPAT_VERSION; icmp_packet_out.msg_type = BATADV_ECHO_REQUEST; icmp_packet_out.ttl = 50; icmp_packet_out.seqno = 0; if (rr) { packet_len = sizeof(struct batadv_icmp_packet_rr); icmp_packet_out.rr_cur = 1; memset(&icmp_packet_out.rr, 0, BATADV_RR_LEN * ETH_ALEN); memset(last_rr, 0, BATADV_RR_LEN * ETH_ALEN); } else { ((struct batadv_icmp_packet *)&icmp_packet_out)->reserved = 0; } printf("PING %s (%s) %zu(%zu) bytes of data\n", dst_string, mac_string, packet_len, packet_len + 28); while (!is_aborted) { tv.tv_sec = timeout; tv.tv_usec = 0; if (loop_count == 0) break; if (loop_count > 0) loop_count--; icmp_packet_out.seqno = htons(++seq_counter); res = icmp_interface_write(state, (struct batadv_icmp_header *)&icmp_packet_out, packet_len); if (res < 0) { fprintf(stderr, "Error - can't send icmp packet: %s\n", strerror(-res)); goto sleep; } read_packet: start_timer(); read_len = icmp_interface_read((struct batadv_icmp_header *)&icmp_packet_in, packet_len, &tv); if (is_aborted) break; packets_out++; if (read_len == 0) { printf("Reply from host %s timed out\n", dst_string); goto sleep; } if (read_len < 0) { fprintf(stderr, "Error - can't receive icmp packets: %s\n", strerror(-read_len)); goto sleep; } if ((size_t)read_len < packet_len) { printf("Warning - dropping received packet as it is smaller than expected (%zu): %zd\n", packet_len, read_len); goto sleep; } /* after receiving an unexpected seqno we keep waiting for our answer */ if (htons(seq_counter) != icmp_packet_in.seqno) goto read_packet; switch (icmp_packet_in.msg_type) { case BATADV_ECHO_REPLY: time_delta = end_timer(); printf("%zd bytes from %s icmp_seq=%hu ttl=%d time=%.2f ms", read_len, dst_string, ntohs(icmp_packet_in.seqno), icmp_packet_in.ttl, time_delta); if (read_len == sizeof(struct batadv_icmp_packet_rr)) { if (last_rr_cur == icmp_packet_in.rr_cur && !memcmp(last_rr, icmp_packet_in.rr, BATADV_RR_LEN * ETH_ALEN)) { printf("\t(same route)"); } else { printf("\nRR: "); for (i = 0; i < BATADV_RR_LEN && i < icmp_packet_in.rr_cur; i++) { rr_mac = (struct ether_addr *)&icmp_packet_in.rr[i]; rr_host = bat_hosts_find_by_mac((char *)rr_mac); if (rr_host) rr_string = rr_host->name; else rr_string = ether_ntoa_long(rr_mac); printf("\t%s\n", rr_string); if (memcmp(rr_mac, dst_mac, ETH_ALEN) == 0) printf("\t%s\n", rr_string); } last_rr_cur = icmp_packet_in.rr_cur; memcpy(last_rr, icmp_packet_in.rr, BATADV_RR_LEN * ETH_ALEN); } } printf("\n"); if (time_delta < min || min == 0.0) min = time_delta; if (time_delta > max) max = time_delta; avg += time_delta; mdev += time_delta * time_delta; packets_in++; break; case BATADV_DESTINATION_UNREACHABLE: printf("From %s: Destination Host Unreachable (icmp_seq %hu)\n", dst_string, ntohs(icmp_packet_in.seqno)); break; case BATADV_TTL_EXCEEDED: printf("From %s: Time to live exceeded (icmp_seq %hu)\n", dst_string, ntohs(icmp_packet_in.seqno)); break; case BATADV_PARAMETER_PROBLEM: fprintf(stderr, "Error - the batman adv kernel module version (%d) differs from ours (%d)\n", icmp_packet_in.version, BATADV_COMPAT_VERSION); printf("Please make sure to use compatible versions!\n"); goto out; default: printf("Unknown message type %d len %zd received\n", icmp_packet_in.msg_type, read_len); break; } sleep: /* skip last sleep in case no more packets will be sent out */ if (loop_count == 0) continue; if (loop_interval.tv_sec > 0 || loop_interval.tv_nsec > 0) nanosleep(&loop_interval, NULL); else if ((tv.tv_sec != 0) || (tv.tv_usec != 0)) select(0, NULL, NULL, NULL, &tv); } if (packets_out == 0) packets_loss = 0; else packets_loss = ((packets_out - packets_in) * 100) / packets_out; if (packets_in) { avg /= packets_in; mdev /= packets_in; mdev = mdev - avg * avg; if (mdev > 0.0) mdev = sqrt(mdev); else mdev = 0.0; } else { avg = 0.0; mdev = 0.0; } printf("--- %s ping statistics ---\n", dst_string); printf("%u packets transmitted, %u received, %u%% packet loss\n", packets_out, packets_in, packets_loss); printf("rtt min/avg/max/mdev = %.3f/%.3f/%.3f/%.3f ms\n", min, avg, max, mdev); if (packets_in) ret = EXIT_SUCCESS; else ret = EXIT_NOSUCCESS; out: icmp_interfaces_clean(); bat_hosts_free(); return ret; } COMMAND(SUBCOMMAND_MIF, ping, "p", COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, NULL, " \tping another batman adv host via layer 2"); batctl-2025.1/routing_algo.c000066400000000000000000000151561500012142700157110ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "batman_adv.h" #include "debug.h" #include "functions.h" #include "main.h" #include "netlink.h" #include "sys.h" #define SYS_SELECTED_RA_PATH "/sys/module/batman_adv/parameters/routing_algo" static void ra_mode_usage(void) { fprintf(stderr, "Usage: batctl [options] routing_algo [algorithm]\n"); fprintf(stderr, "options:\n"); fprintf(stderr, " \t -h print this help\n"); } static const int routing_algos_mandatory[] = { BATADV_ATTR_ALGO_NAME, }; static int routing_algos_callback(struct nl_msg *msg, void *arg __maybe_unused) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct genlmsghdr *ghdr; const char *algo_name; if (!genlmsg_valid_hdr(nlh, 0)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } ghdr = nlmsg_data(nlh); if (ghdr->cmd != BATADV_CMD_GET_ROUTING_ALGOS) return NL_OK; if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { fputs("Received invalid data from kernel.\n", stderr); exit(1); } if (missing_mandatory_attrs(attrs, routing_algos_mandatory, ARRAY_SIZE(routing_algos_mandatory))) { fputs("Missing attributes from kernel\n", stderr); exit(1); } algo_name = nla_get_string(attrs[BATADV_ATTR_ALGO_NAME]); printf(" * %s\n", algo_name); return NL_OK; } static int print_routing_algos(struct state *state) { struct print_opts opts = { .callback = routing_algos_callback, }; struct nl_msg *msg; struct nl_cb *cb; msg = nlmsg_alloc(); if (!msg) return -ENOMEM; genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, state->batadv_family, 0, NLM_F_DUMP, BATADV_CMD_GET_ROUTING_ALGOS, 1); nl_send_auto_complete(state->sock, msg); nlmsg_free(msg); opts.remaining_header = strdup("Available routing algorithms:\n"); cb = nl_cb_alloc(NL_CB_DEFAULT); if (!cb) return -ENOMEM; nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, netlink_print_common_cb, &opts); nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, netlink_stop_callback, NULL); nl_cb_err(cb, NL_CB_CUSTOM, netlink_print_error, NULL); nl_recvmsgs(state->sock, cb); if (!last_err) netlink_print_remaining_header(&opts); return last_err; } static int write_default_ra(const char *full_path, const char *arg1) { ssize_t write_len; int fd = -1; fd = open(full_path, O_WRONLY); if (fd < 0) { fprintf(stderr, "Error - can't open file '%s': %s\n", full_path, strerror(errno)); return EXIT_FAILURE; } write_len = write(fd, arg1, strlen(arg1) + 1); close(fd); if (write_len < 0) { fprintf(stderr, "Error - can't write to file '%s': %s\n", full_path, strerror(errno)); return EXIT_FAILURE; } return EXIT_SUCCESS; } static struct nla_policy link_policy[IFLA_MAX + 1] = { [IFLA_IFNAME] = { .type = NLA_STRING, .maxlen = IFNAMSIZ }, }; struct print_ra_interfaces_rtnl_arg { uint8_t header_shown:1; struct state *state; }; static int print_ra_interfaces_rtnl_parse(struct nl_msg *msg, void *arg) { struct print_ra_interfaces_rtnl_arg *print_arg = arg; struct nlattr *attrs[IFLA_MAX + 1]; struct ifinfomsg *ifm; char algoname[256]; char *mesh_iface; int ret; ifm = nlmsg_data(nlmsg_hdr(msg)); ret = nlmsg_parse(nlmsg_hdr(msg), sizeof(*ifm), attrs, IFLA_MAX, link_policy); if (ret < 0) goto err; if (!attrs[IFLA_IFNAME]) goto err; mesh_iface = nla_get_string(attrs[IFLA_IFNAME]); ret = get_algoname_netlink(print_arg->state, ifm->ifi_index, algoname, sizeof(algoname)); if (ret < 0) goto err; if (!print_arg->header_shown) { print_arg->header_shown = true; printf("Active routing protocol configuration:\n"); } printf(" * %s: %s\n", mesh_iface, algoname); err: return NL_OK; } static int print_ra_interfaces(struct state *state) { struct print_ra_interfaces_rtnl_arg print_arg = { .state = state, }; struct ifinfomsg rt_hdr = { .ifi_family = IFLA_UNSPEC, }; struct nlattr *linkinfo; struct nl_sock *sock; struct nl_msg *msg; struct nl_cb *cb; int err = 0; int ret; sock = nl_socket_alloc(); if (!sock) return -ENOMEM; ret = nl_connect(sock, NETLINK_ROUTE); if (ret < 0) { err = -ENOMEM; goto err_free_sock; } cb = nl_cb_alloc(NL_CB_DEFAULT); if (!cb) { err = -ENOMEM; goto err_free_sock; } nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, print_ra_interfaces_rtnl_parse, &print_arg); msg = nlmsg_alloc_simple(RTM_GETLINK, NLM_F_REQUEST | NLM_F_DUMP); if (!msg) { err = -ENOMEM; goto err_free_cb; } ret = nlmsg_append(msg, &rt_hdr, sizeof(rt_hdr), NLMSG_ALIGNTO); if (ret < 0) { err = -ENOMEM; goto err_free_msg; } linkinfo = nla_nest_start(msg, IFLA_LINKINFO); if (!linkinfo) { err = -ENOMEM; goto err_free_msg; } ret = nla_put_string(msg, IFLA_INFO_KIND, "batadv"); if (ret < 0) { err = -ENOMEM; goto err_free_msg; } nla_nest_end(msg, linkinfo); ret = nl_send_auto_complete(sock, msg); if (ret < 0) goto err_free_msg; nl_recvmsgs(sock, cb); if (print_arg.header_shown) printf("\n"); err_free_msg: nlmsg_free(msg); err_free_cb: nl_cb_put(cb); err_free_sock: nl_socket_free(sock); return err; } static int routing_algo(struct state *state, int argc, char **argv) { int res = EXIT_FAILURE; int optchar; int ret; while ((optchar = getopt(argc, argv, "h")) != -1) { switch (optchar) { case 'h': ra_mode_usage(); return EXIT_SUCCESS; default: ra_mode_usage(); return EXIT_FAILURE; } } if (argc == 2) return write_default_ra(SYS_SELECTED_RA_PATH, argv[1]); /* duplicated code here from the main() because interface doesn't always * need COMMAND_FLAG_MESH_IFACE and COMMAND_FLAG_NETLINK */ ret = netlink_create(state); if (ret < 0) return EXIT_FAILURE; print_ra_interfaces(state); res = read_file(SYS_SELECTED_RA_PATH, USE_READ_BUFF); if (res != EXIT_SUCCESS) goto err_free_netlink; printf("Selected routing algorithm (used when next batX interface is created):\n"); printf(" => %s\n", line_ptr); free(line_ptr); line_ptr = NULL; print_routing_algos(state); res = EXIT_SUCCESS; err_free_netlink: netlink_destroy(state); return res; } COMMAND(SUBCOMMAND, routing_algo, "ra", 0, NULL, "[mode] \tdisplay or modify the routing algorithm"); batctl-2025.1/statistics.c000066400000000000000000000052041500012142700154030ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include "main.h" void check_root_or_die(const char *cmd); /* code borrowed from ethtool */ static int statistics_custom_get(int fd, struct ifreq *ifr) { struct ethtool_gstrings *strings = NULL; struct ethtool_stats *stats = NULL; struct ethtool_drvinfo drvinfo; int ret = EXIT_FAILURE; unsigned int sz_stats; unsigned int n_stats; unsigned int sz_str; unsigned int i; int err; drvinfo.cmd = ETHTOOL_GDRVINFO; ifr->ifr_data = (void *)&drvinfo; err = ioctl(fd, SIOCETHTOOL, ifr); if (err < 0) { perror("Error - can't open driver information"); goto out; } n_stats = drvinfo.n_stats; if (n_stats < 1) goto success; sz_str = n_stats * ETH_GSTRING_LEN; sz_stats = n_stats * sizeof(uint64_t); strings = calloc(1, sz_str + sizeof(struct ethtool_gstrings)); stats = calloc(1, sz_stats + sizeof(struct ethtool_stats)); if (!strings || !stats) { fprintf(stderr, "Error - out of memory\n"); goto out; } strings->cmd = ETHTOOL_GSTRINGS; strings->string_set = ETH_SS_STATS; strings->len = n_stats; ifr->ifr_data = (void *)strings; err = ioctl(fd, SIOCETHTOOL, ifr); if (err < 0) { perror("Error - can't get stats strings information"); goto out; } stats->cmd = ETHTOOL_GSTATS; stats->n_stats = n_stats; ifr->ifr_data = (void *)stats; err = ioctl(fd, SIOCETHTOOL, ifr); if (err < 0) { perror("Error - can't get stats information"); goto out; } for (i = 0; i < n_stats; i++) { printf("\t%.*s: %llu\n", ETH_GSTRING_LEN, &strings->data[i * ETH_GSTRING_LEN], stats->data[i]); } success: ret = EXIT_SUCCESS; out: free(strings); free(stats); return ret; } static int statistics(struct state *state, int argc __maybe_unused, char **argv __maybe_unused) { int ret = EXIT_FAILURE; struct ifreq ifr; int fd = -1; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, state->mesh_iface, sizeof(ifr.ifr_name)); ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = '\0'; fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("Error - can't open socket"); goto out; } ret = statistics_custom_get(fd, &ifr); out: if (fd >= 0) close(fd); return ret; } COMMAND(SUBCOMMAND_MIF, statistics, "s", COMMAND_FLAG_MESH_IFACE, NULL, " \tprint mesh statistics"); batctl-2025.1/sys.c000066400000000000000000000123321500012142700140270ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include "main.h" #include "sys.h" #include "functions.h" #include "debug.h" int parse_simple_boolean(struct state *state, int argc, char *argv[]) { struct settings_data *settings = state->cmd->arg; struct simple_boolean_data *data = settings->data; int ret; if (argc != 2) { fprintf(stderr, "Error - incorrect number of arguments (expected 1)\n"); return -EINVAL; } ret = parse_bool(argv[1], &data->val); if (ret < 0) { fprintf(stderr, "Error - the supplied argument is invalid: %s\n", argv[1]); fprintf(stderr, "The following values are allowed:\n"); fprintf(stderr, " * 0\n"); fprintf(stderr, " * disable\n"); fprintf(stderr, " * disabled\n"); fprintf(stderr, " * 1\n"); fprintf(stderr, " * enable\n"); fprintf(stderr, " * enabled\n"); return ret; } return 0; } static int sys_simple_nlerror(struct sockaddr_nl *nla __maybe_unused, struct nlmsgerr *nlerr, void *arg) { int *result = arg; if (nlerr->error != -EOPNOTSUPP) fprintf(stderr, "Error received: %s\n", strerror(-nlerr->error)); *result = nlerr->error; return NL_STOP; } int sys_simple_nlquery(struct state *state, enum batadv_nl_commands nl_cmd, nl_recvmsg_msg_cb_t attribute_cb, nl_recvmsg_msg_cb_t callback) { struct nl_msg *msg; int result; int ret; if (!state->sock) return -EOPNOTSUPP; if (callback) { result = -EOPNOTSUPP; nl_cb_set(state->cb, NL_CB_VALID, NL_CB_CUSTOM, callback, &result); } else { result = 0; } nl_cb_err(state->cb, NL_CB_CUSTOM, sys_simple_nlerror, &result); msg = nlmsg_alloc(); if (!msg) return -ENOMEM; genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, state->batadv_family, 0, 0, nl_cmd, 1); nla_put_u32(msg, BATADV_ATTR_MESH_IFINDEX, state->mesh_ifindex); if (attribute_cb) { ret = attribute_cb(msg, state); if (ret < 0) { nlmsg_free(msg); return -ENOMEM; } } nl_send_auto_complete(state->sock, msg); nlmsg_free(msg); if (callback) { ret = nl_recvmsgs(state->sock, state->cb); if (ret < 0) return ret; } nl_wait_for_ack(state->sock); return result; } int sys_simple_print_boolean(struct nl_msg *msg, void *arg, enum batadv_nl_attrs attr) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct genlmsghdr *ghdr; int *result = arg; if (!genlmsg_valid_hdr(nlh, 0)) return NL_OK; ghdr = nlmsg_data(nlh); if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { return NL_OK; } if (!attrs[attr]) return NL_OK; printf("%s\n", nla_get_u8(attrs[attr]) ? "enabled" : "disabled"); *result = 0; return NL_STOP; } static void settings_usage(struct state *state) { static const char * const default_prefixes[] = { "", NULL, }; static const char * const meshif_prefixes[] = { "meshif ", NULL, }; static const char * const vlan_prefixes[] = { "vlan ", "meshif vid ", NULL, }; static const char * const hardif_prefixes[] = { "hardif ", NULL, }; const char *linestart = "Usage:"; const char * const *prefixes; const char * const *prefix; switch (state->cmd->type) { case SUBCOMMAND_MIF: prefixes = meshif_prefixes; break; case SUBCOMMAND_VID: prefixes = vlan_prefixes; break; case SUBCOMMAND_HIF: prefixes = hardif_prefixes; break; default: prefixes = default_prefixes; break; } for (prefix = &prefixes[0]; *prefix; prefix++) { fprintf(stderr, "%s batctl [options] %s%s|%s [parameters] %s\n", linestart, *prefix, state->cmd->name, state->cmd->abbr, state->cmd->usage ? state->cmd->usage : ""); linestart = " "; } fprintf(stderr, "parameters:\n"); fprintf(stderr, " \t -h print this help\n"); } static int sys_read_setting(struct state *state) { struct settings_data *settings = state->cmd->arg; int res = EXIT_FAILURE; if (settings->netlink_get) { res = settings->netlink_get(state); if (res < 0) return EXIT_FAILURE; else return EXIT_SUCCESS; } return res; } static int sys_write_setting(struct state *state) { struct settings_data *settings = state->cmd->arg; int res = EXIT_FAILURE; if (settings->netlink_set) { res = settings->netlink_set(state); if (res < 0) return EXIT_FAILURE; else return EXIT_SUCCESS; } return res; } int handle_sys_setting(struct state *state, int argc, char **argv) { struct settings_data *settings = state->cmd->arg; int optchar, res = EXIT_FAILURE; while ((optchar = getopt(argc, argv, "h")) != -1) { switch (optchar) { case 'h': settings_usage(state); return EXIT_SUCCESS; default: settings_usage(state); return EXIT_FAILURE; } } if (argc == 1) return sys_read_setting(state); if (settings->parse) { res = settings->parse(state, argc, argv); if (res < 0) return EXIT_FAILURE; } return sys_write_setting(state); } batctl-2025.1/sys.h000066400000000000000000000020161500012142700140320ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #ifndef _BATCTL_SYS_H #define _BATCTL_SYS_H #include "main.h" #include #include #include #include "batman_adv.h" #include "netlink.h" #define VLAN_ID_MAX_LEN 4 struct settings_data { void *data; int (*parse)(struct state *state, int argc, char *argv[]); int (*netlink_get)(struct state *state); int (*netlink_set)(struct state *state); }; struct simple_boolean_data { bool val; }; int handle_sys_setting(struct state *state, int argc, char **argv); int parse_simple_boolean(struct state *state, int argc, char *argv[]); int sys_simple_nlquery(struct state *state, enum batadv_nl_commands nl_cmd, nl_recvmsg_msg_cb_t attribute_cb, nl_recvmsg_msg_cb_t callback); int sys_simple_print_boolean(struct nl_msg *msg, void *arg, enum batadv_nl_attrs attr); #endif batctl-2025.1/tcpdump.c000066400000000000000000001367321500012142700147000ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Andreas Langer * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "batadv_packet_compat.h" #include "tcpdump.h" #include "bat-hosts.h" #include "functions.h" #define BATADV_THROUGHPUT_MAX_VALUE 0xFFFFFFFF #ifndef ETH_P_BATMAN #define ETH_P_BATMAN 0x4305 #endif /* ETH_P_BATMAN */ #define IPV6_MIN_MTU 1280 #define LEN_CHECK(buff_len, check_len, desc) \ do { \ size_t __buff_len = (size_t)(buff_len); \ size_t __check_len = (size_t)(check_len); \ if (__buff_len < __check_len) { \ fprintf(stderr, \ "Warning - dropping received %s packet as it is smaller than expected (%zu): %zu\n", \ desc, __check_len, __buff_len); \ return; \ } \ } while (0) static unsigned short dump_level_all = DUMP_TYPE_BATOGM | DUMP_TYPE_BATOGM2 | DUMP_TYPE_BATELP | DUMP_TYPE_BATICMP | DUMP_TYPE_BATUCAST | DUMP_TYPE_BATBCAST | DUMP_TYPE_BATUTVLV | DUMP_TYPE_BATFRAG | DUMP_TYPE_NONBAT | DUMP_TYPE_BATCODED | DUMP_TYPE_BATMCAST; static unsigned short dump_level; static void parse_eth_hdr(unsigned char *packet_buff, ssize_t buff_len, int read_opt, int time_printed); static void tcpdump_usage(void) { fprintf(stderr, "Usage: batctl tcpdump [parameters] interface [interface]\n"); fprintf(stderr, "parameters:\n"); fprintf(stderr, " \t -c compat filter - only display packets matching own compat version (%i)\n", BATADV_COMPAT_VERSION); fprintf(stderr, " \t -h print this help\n"); fprintf(stderr, " \t -n don't convert addresses to bat-host names\n"); fprintf(stderr, " \t -p dump specific packet type\n"); fprintf(stderr, " \t -x dump all packet types except specified\n"); fprintf(stderr, "packet types:\n"); fprintf(stderr, " \t\t%3d - batman ogm packets\n", DUMP_TYPE_BATOGM); fprintf(stderr, " \t\t%3d - batman ogmv2 packets\n", DUMP_TYPE_BATOGM2); fprintf(stderr, " \t\t%3d - batman elp packets\n", DUMP_TYPE_BATELP); fprintf(stderr, " \t\t%3d - batman icmp packets\n", DUMP_TYPE_BATICMP); fprintf(stderr, " \t\t%3d - batman unicast packets\n", DUMP_TYPE_BATUCAST); fprintf(stderr, " \t\t%3d - batman broadcast packets\n", DUMP_TYPE_BATBCAST); fprintf(stderr, " \t\t%3d - batman fragmented packets\n", DUMP_TYPE_BATFRAG); fprintf(stderr, " \t\t%3d - batman unicast tvlv packets\n", DUMP_TYPE_BATUTVLV); fprintf(stderr, " \t\t%3d - non batman packets\n", DUMP_TYPE_NONBAT); fprintf(stderr, " \t\t%3d - batman coded packets\n", DUMP_TYPE_BATCODED); fprintf(stderr, " \t\t%3d - batman multicast packets\n", DUMP_TYPE_BATMCAST); fprintf(stderr, " \t\t%3d - batman ogm & non batman packets\n", DUMP_TYPE_BATOGM | DUMP_TYPE_NONBAT); } static int print_time(void) { struct timeval tv; struct tm *tm; gettimeofday(&tv, NULL); tm = localtime(&tv.tv_sec); if (tm) printf("%02d:%02d:%02d.%06ld ", tm->tm_hour, tm->tm_min, tm->tm_sec, tv.tv_usec); else printf("00:00:00.000000 "); return 1; } static void batctl_tvlv_parse_gw_v1(void *buff, ssize_t buff_len, int read_opt __maybe_unused) { struct batadv_tvlv_gateway_data *tvlv = buff; uint32_t down, up; if (buff_len != sizeof(*tvlv)) { fprintf(stderr, "Warning - dropping received %s packet as it is not the correct size (%zu): %zu\n", "TVLV GWv1", sizeof(*tvlv), buff_len); return; } down = ntohl(tvlv->bandwidth_down); up = ntohl(tvlv->bandwidth_up); printf("\tTVLV GWv1: down %d.%.1dMbps, up %d.%1dMbps\n", down / 10, down % 10, up / 10, up % 10); } static void batctl_tvlv_parse_dat_v1(void *buff __maybe_unused, ssize_t buff_len, int read_opt __maybe_unused) { if (buff_len != 0) { fprintf(stderr, "Warning - dropping received %s packet as it is not the correct size (0): %zu\n", "TVLV DATv1", buff_len); return; } printf("\tTVLV DATv1: enabled\n"); } static void batctl_tvlv_parse_nc_v1(void *buff __maybe_unused, ssize_t buff_len, int read_opt __maybe_unused) { if (buff_len != 0) { fprintf(stderr, "Warning - dropping received %s packet as it is not the correct size (0): %zu\n", "TVLV NCv1", buff_len); return; } printf("\tTVLV NCv1: enabled\n"); } static void batctl_tvlv_parse_tt_v1(void *buff, ssize_t buff_len, int read_opt __maybe_unused) { struct batadv_tvlv_tt_data *tvlv = buff; struct batadv_tvlv_tt_vlan_data *vlan; unsigned short num_entry; unsigned short num_vlan; const char *type; size_t vlan_len; int i; LEN_CHECK(buff_len, sizeof(*tvlv), "TVLV TTv1"); if (tvlv->flags & BATADV_TT_OGM_DIFF) type = "OGM DIFF"; else if (tvlv->flags & BATADV_TT_REQUEST) type = "TT REQUEST"; else if (tvlv->flags & BATADV_TT_RESPONSE) type = "TT RESPONSE"; else type = "UNKNOWN"; num_vlan = ntohs(tvlv->num_vlan); vlan_len = sizeof(*tvlv) + sizeof(*vlan) * num_vlan; LEN_CHECK(buff_len, vlan_len, "TVLV TTv1 VLAN"); buff_len -= vlan_len; num_entry = buff_len / sizeof(struct batadv_tvlv_tt_change); printf("\tTVLV TTv1: %s [%c] ttvn=%hhu vlan_num=%hu entry_num=%hu\n", type, tvlv->flags & BATADV_TT_FULL_TABLE ? 'F' : '.', tvlv->ttvn, num_vlan, num_entry); vlan = (struct batadv_tvlv_tt_vlan_data *)(tvlv + 1); for (i = 0; i < num_vlan; i++) { printf("\t\tVLAN ID %hd, crc %#.8x\n", BATADV_PRINT_VID(ntohs(vlan->vid)), ntohl(vlan->crc)); vlan++; } } static void batctl_tvlv_parse_roam_v1(void *buff, ssize_t buff_len, int read_opt __maybe_unused) { struct batadv_tvlv_roam_adv *tvlv = buff; if (buff_len != sizeof(*tvlv)) { fprintf(stderr, "Warning - dropping received %s packet as it is not the correct size (%zu): %zu\n", "TVLV ROAMv1", sizeof(*tvlv), buff_len); return; } printf("\tTVLV ROAMv1: client %s, VLAN ID %d\n", get_name_by_macaddr((struct ether_addr *)tvlv->client, NO_FLAGS), BATADV_PRINT_VID(ntohs(tvlv->vid))); } static void batctl_tvlv_parse_mcast_v1(void *buff __maybe_unused, ssize_t buff_len, int read_opt __maybe_unused) { struct batadv_tvlv_mcast_data *tvlv = buff; uint8_t flags; if (buff_len != sizeof(*tvlv)) { fprintf(stderr, "Warning - dropping received %s packet as it is not the correct size (%zu): %zu\n", "TVLV MCASTv1", sizeof(*tvlv), buff_len); return; } flags = tvlv->flags; printf("\tTVLV MCASTv1: [%c%c%c]\n", flags & BATADV_MCAST_WANT_ALL_UNSNOOPABLES ? 'U' : '.', flags & BATADV_MCAST_WANT_ALL_IPV4 ? '4' : '.', flags & BATADV_MCAST_WANT_ALL_IPV6 ? '6' : '.'); } static void batctl_tvlv_parse_mcast_v2(void *buff, ssize_t buff_len, int read_opt __maybe_unused) { struct batadv_tvlv_mcast_data *tvlv = buff; uint8_t flags; if (buff_len != sizeof(*tvlv)) { fprintf(stderr, "Warning - dropping received %s packet as it is not the correct size (%zu): %zu\n", "TVLV MCASTv2", sizeof(*tvlv), buff_len); return; } flags = tvlv->flags; printf("\tTVLV MCASTv2: [%c%c%c%s%s%c]\n", flags & BATADV_MCAST_WANT_ALL_UNSNOOPABLES ? 'U' : '.', flags & BATADV_MCAST_WANT_ALL_IPV4 ? '4' : '.', flags & BATADV_MCAST_WANT_ALL_IPV6 ? '6' : '.', !(flags & BATADV_MCAST_WANT_NO_RTR4) ? "R4" : ". ", !(flags & BATADV_MCAST_WANT_NO_RTR6) ? "R6" : ". ", !(flags & BATADV_MCAST_HAVE_MC_PTYPE_CAPA) ? 'P' : '.'); } static void batctl_tvlv_parse_mcast_tracker_v1(void *buff, ssize_t buff_len, int read_opt) { struct batadv_tvlv_mcast_tracker *tvlv = buff; size_t tvlv_len = sizeof(*tvlv); struct ether_addr *dst; uint16_t num_dests; if (buff_len < (ssize_t)tvlv_len) { fprintf(stderr, "Warning - dropping received %s packet as it is not the correct size (%zu): %zu\n", "TVLV MCAST TRACKER v1", tvlv_len, buff_len); return; } num_dests = ntohs(tvlv->num_dests); tvlv_len += num_dests * ETH_ALEN; dst = (struct ether_addr *)(tvlv + 1); if (buff_len < (ssize_t)tvlv_len) { fprintf(stderr, "Warning - dropping received %s packet as it is not the correct size (%zu): %zu\n", "TVLV MCAST TRACKER v1 (with destinations)", tvlv_len, buff_len); return; } printf("\tTVLV MCAST TRACKER v1, destinations (%hu):\n", num_dests); for (int i = 0; i < num_dests; i++) printf("\t\t%s\n", get_name_by_macaddr(dst++, read_opt)); } typedef void (*batctl_tvlv_parser_t)(void *buff, ssize_t buff_len, int read_opt); static batctl_tvlv_parser_t tvlv_parser_get(uint8_t type, uint8_t version) { switch (type) { case BATADV_TVLV_GW: switch (version) { case 1: return batctl_tvlv_parse_gw_v1; default: return NULL; } case BATADV_TVLV_DAT: switch (version) { case 1: return batctl_tvlv_parse_dat_v1; default: return NULL; } case BATADV_TVLV_NC: switch (version) { case 1: return batctl_tvlv_parse_nc_v1; default: return NULL; } case BATADV_TVLV_TT: switch (version) { case 1: return batctl_tvlv_parse_tt_v1; default: return NULL; } case BATADV_TVLV_ROAM: switch (version) { case 1: return batctl_tvlv_parse_roam_v1; default: return NULL; } case BATADV_TVLV_MCAST: switch (version) { case 1: return batctl_tvlv_parse_mcast_v1; case 2: return batctl_tvlv_parse_mcast_v2; default: return NULL; } case BATADV_TVLV_MCAST_TRACKER: switch (version) { case 1: return batctl_tvlv_parse_mcast_tracker_v1; default: return NULL; } default: return NULL; } } static void dump_tvlv(unsigned char *ptr, ssize_t tvlv_len, int read_opt) { struct batadv_tvlv_hdr *tvlv_hdr; batctl_tvlv_parser_t parser; ssize_t len; while (tvlv_len >= (ssize_t)sizeof(*tvlv_hdr)) { tvlv_hdr = (struct batadv_tvlv_hdr *)ptr; /* data after TVLV header */ ptr = (uint8_t *)(tvlv_hdr + 1); tvlv_len -= sizeof(*tvlv_hdr); len = ntohs(tvlv_hdr->len); LEN_CHECK(tvlv_len, (size_t)len, "BAT TVLV"); parser = tvlv_parser_get(tvlv_hdr->type, tvlv_hdr->version); if (parser) parser(ptr, len, read_opt); /* go to the next container */ ptr += len; tvlv_len -= len; } } static void dump_batman_ucast_tvlv(unsigned char *packet_buff, ssize_t buff_len, int read_opt, int time_printed) { struct batadv_unicast_tvlv_packet *tvlv_packet; struct ether_header *ether_header; struct ether_addr *src; struct ether_addr *dst; ssize_t check_len; ssize_t tvlv_len; check_len = (size_t)buff_len - sizeof(struct ether_header); LEN_CHECK(check_len, sizeof(*tvlv_packet), "BAT UCAST TVLV"); check_len -= sizeof(*tvlv_packet); ether_header = (struct ether_header *)packet_buff; tvlv_packet = (struct batadv_unicast_tvlv_packet *)(ether_header + 1); LEN_CHECK(check_len, (size_t)ntohs(tvlv_packet->tvlv_len), "BAT TVLV (containers)"); if (!time_printed) time_printed = print_time(); src = (struct ether_addr *)tvlv_packet->src; printf("BAT %s > ", get_name_by_macaddr(src, read_opt)); dst = (struct ether_addr *)tvlv_packet->dst; tvlv_len = ntohs(tvlv_packet->tvlv_len); printf("%s: TVLV, len %zu, tvlv_len %zu, ttl %hhu\n", get_name_by_macaddr(dst, read_opt), buff_len - sizeof(struct ether_header), tvlv_len, tvlv_packet->ttl); dump_tvlv((uint8_t *)(tvlv_packet + 1), tvlv_len, read_opt); } static int dump_bla2_claim(struct ether_header *eth_hdr, struct ether_arp *arphdr, int read_opt) { uint8_t bla_claim_magic[3] = {0xff, 0x43, 0x05}; struct batadv_bla_claim_dst *bla_dst; int arp_is_bla2_claim = 0; uint8_t *hw_src; uint8_t *hw_dst; if (arphdr->ea_hdr.ar_hrd != htons(ARPHRD_ETHER)) goto out; if (arphdr->ea_hdr.ar_pro != htons(ETH_P_IP)) goto out; if (arphdr->ea_hdr.ar_hln != ETH_ALEN) goto out; if (arphdr->ea_hdr.ar_pln != 4) goto out; hw_src = arphdr->arp_sha; hw_dst = arphdr->arp_tha; bla_dst = (struct batadv_bla_claim_dst *)hw_dst; if (memcmp(bla_dst->magic, bla_claim_magic, sizeof(bla_claim_magic)) != 0) goto out; switch (bla_dst->type) { case BATADV_CLAIM_TYPE_CLAIM: printf("BLA CLAIM, backbone %s, ", get_name_by_macaddr((struct ether_addr *)hw_src, read_opt)); printf("client %s, bla group %04x\n", get_name_by_macaddr((struct ether_addr *)eth_hdr->ether_shost, read_opt), ntohs(bla_dst->group)); break; case BATADV_CLAIM_TYPE_UNCLAIM: printf("BLA UNCLAIM, backbone %s, ", get_name_by_macaddr((struct ether_addr *)eth_hdr->ether_shost, read_opt)); printf("client %s, bla group %04x\n", get_name_by_macaddr((struct ether_addr *)hw_src, read_opt), ntohs(bla_dst->group)); break; case BATADV_CLAIM_TYPE_ANNOUNCE: printf("BLA ANNOUNCE, backbone %s, bla group %04x, crc %04x\n", get_name_by_macaddr((struct ether_addr *)eth_hdr->ether_shost, read_opt), ntohs(bla_dst->group), ntohs(*((uint16_t *)(&hw_src[4])))); break; case BATADV_CLAIM_TYPE_REQUEST: printf("BLA REQUEST, src backbone %s, ", get_name_by_macaddr((struct ether_addr *)eth_hdr->ether_shost, read_opt)); printf("dst backbone %s\n", get_name_by_macaddr((struct ether_addr *)eth_hdr->ether_dhost, read_opt)); break; case BATADV_CLAIM_TYPE_LOOPDETECT: printf("BLA LOOPDETECT, src backbone %s, ", get_name_by_macaddr((struct ether_addr *)eth_hdr->ether_shost, read_opt)); printf("dst backbone %s\n", get_name_by_macaddr((struct ether_addr *)eth_hdr->ether_dhost, read_opt)); break; default: printf("BLA UNKNOWN, type %hhu\n", bla_dst->type); break; } arp_is_bla2_claim = 1; out: return arp_is_bla2_claim; } static void dump_arp(unsigned char *packet_buff, ssize_t buff_len, struct ether_header *eth_hdr, int read_opt, int time_printed) { struct ether_arp *arphdr; int arp_is_bla2_claim; LEN_CHECK((size_t)buff_len, sizeof(struct ether_arp), "ARP"); if (!time_printed) print_time(); arphdr = (struct ether_arp *)packet_buff; switch (ntohs(arphdr->arp_op)) { case ARPOP_REQUEST: printf("ARP, Request who-has %s", inet_ntoa(*(struct in_addr *)&arphdr->arp_tpa)); printf(" tell %s (%s), length %zd\n", inet_ntoa(*(struct in_addr *)&arphdr->arp_spa), ether_ntoa_long((struct ether_addr *)&arphdr->arp_sha), buff_len); break; case ARPOP_REPLY: arp_is_bla2_claim = dump_bla2_claim(eth_hdr, arphdr, read_opt); if (arp_is_bla2_claim) break; printf("ARP, Reply %s is-at %s, length %zd\n", inet_ntoa(*(struct in_addr *)&arphdr->arp_spa), ether_ntoa_long((struct ether_addr *)&arphdr->arp_sha), buff_len); break; default: printf("ARP, unknown op code: %i\n", ntohs(arphdr->arp_op)); break; } } static void dump_tcp(const char ip_string[], unsigned char *packet_buff, ssize_t buff_len, size_t ip6_header_len, char *src_addr, char *dst_addr) { uint16_t tcp_header_len; struct tcphdr *tcphdr; LEN_CHECK((size_t)buff_len - ip6_header_len, sizeof(struct tcphdr), "TCP"); tcphdr = (struct tcphdr *)(packet_buff + ip6_header_len); tcp_header_len = tcphdr->doff * 4; printf("%s %s.%i > ", ip_string, src_addr, ntohs(tcphdr->source)); printf("%s.%i: TCP, Flags [%c%c%c%c%c%c], length %zu\n", dst_addr, ntohs(tcphdr->dest), (tcphdr->fin ? 'F' : '.'), (tcphdr->syn ? 'S' : '.'), (tcphdr->rst ? 'R' : '.'), (tcphdr->psh ? 'P' : '.'), (tcphdr->ack ? 'A' : '.'), (tcphdr->urg ? 'U' : '.'), (size_t)buff_len - ip6_header_len - tcp_header_len); } static void dump_udp(const char ip_string[], unsigned char *packet_buff, ssize_t buff_len, size_t ip6_header_len, char *src_addr, char *dst_addr) { struct udphdr *udphdr; LEN_CHECK((size_t)buff_len - ip6_header_len, sizeof(struct udphdr), "UDP"); udphdr = (struct udphdr *)(packet_buff + ip6_header_len); printf("%s %s.%i > ", ip_string, src_addr, ntohs(udphdr->source)); switch (ntohs(udphdr->dest)) { case 67: LEN_CHECK((size_t)buff_len - ip6_header_len - sizeof(struct udphdr), (size_t)44, "DHCP"); printf("%s.67: BOOTP/DHCP, Request from %s, length %zu\n", dst_addr, ether_ntoa_long((struct ether_addr *) (((char *)udphdr) + sizeof(struct udphdr) + 28)), (size_t)buff_len - ip6_header_len - sizeof(struct udphdr)); break; case 68: printf("%s.68: BOOTP/DHCP, Reply, length %zu\n", dst_addr, (size_t)buff_len - ip6_header_len - sizeof(struct udphdr)); break; default: printf("%s.%i: UDP, length %zu\n", dst_addr, ntohs(udphdr->dest), (size_t)buff_len - ip6_header_len - sizeof(struct udphdr)); break; } } static void dump_ipv6(unsigned char *packet_buff, ssize_t buff_len, int time_printed) { struct nd_neighbor_solicit *nd_neigh_sol; struct nd_neighbor_advert *nd_advert; char nd_nas_target[INET6_ADDRSTRLEN]; static const char ip_string[] = "IP6"; char ipsrc[INET6_ADDRSTRLEN]; char ipdst[INET6_ADDRSTRLEN]; struct icmp6_hdr *icmphdr; struct ip6_hdr *iphdr; iphdr = (struct ip6_hdr *)packet_buff; LEN_CHECK((size_t)buff_len, (size_t)(sizeof(struct ip6_hdr)), ip_string); if (!time_printed) print_time(); if (!inet_ntop(AF_INET6, &iphdr->ip6_src, ipsrc, sizeof(ipsrc))) { fprintf(stderr, "Cannot decode source IPv6\n"); return; } if (!inet_ntop(AF_INET6, &iphdr->ip6_dst, ipdst, sizeof(ipdst))) { fprintf(stderr, "Cannot decode destination IPv6\n"); return; } switch (iphdr->ip6_nxt) { case IPPROTO_ICMPV6: LEN_CHECK((size_t)buff_len - (size_t)(sizeof(struct ip6_hdr)), sizeof(struct icmp6_hdr), "ICMPv6"); icmphdr = (struct icmp6_hdr *)(packet_buff + sizeof(struct ip6_hdr)); printf("%s %s > %s ", ip_string, ipsrc, ipdst); if (icmphdr->icmp6_type < ICMP6_INFOMSG_MASK && (size_t)(buff_len) > IPV6_MIN_MTU) { fprintf(stderr, "Warning - dropping received 'ICMPv6 destination unreached' packet as it is bigger than maximum allowed size (%u): %zu\n", IPV6_MIN_MTU, (size_t)(buff_len)); return; } printf("ICMP6"); switch (icmphdr->icmp6_type) { case ICMP6_DST_UNREACH: switch (icmphdr->icmp6_code) { case ICMP6_DST_UNREACH_NOROUTE: printf(", unreachable route\n"); break; case ICMP6_DST_UNREACH_ADMIN: printf(", unreachable prohibited\n"); break; case ICMP6_DST_UNREACH_ADDR: printf(", unreachable address\n"); break; case ICMP6_DST_UNREACH_BEYONDSCOPE: printf(", beyond scope\n"); break; case ICMP6_DST_UNREACH_NOPORT: printf(", unreachable port\n"); break; default: printf(", unknown unreach code (%u)\n", icmphdr->icmp6_code); } break; case ICMP6_ECHO_REQUEST: printf(" echo request, id: %d, seq: %d, length: %hu\n", ntohs(icmphdr->icmp6_id), ntohs(icmphdr->icmp6_seq), ntohs(iphdr->ip6_plen)); break; case ICMP6_ECHO_REPLY: printf(" echo reply, id: %d, seq: %d, length: %hu\n", ntohs(icmphdr->icmp6_id), ntohs(icmphdr->icmp6_seq), ntohs(iphdr->ip6_plen)); break; case ICMP6_TIME_EXCEEDED: printf(" time exceeded in-transit, length %zu\n", (size_t)buff_len - sizeof(struct icmp6_hdr)); break; case ND_NEIGHBOR_SOLICIT: LEN_CHECK((size_t)buff_len - (size_t)(sizeof(struct ip6_hdr)), sizeof(*nd_neigh_sol), "ICMPv6 Neighbor Solicitation"); nd_neigh_sol = (struct nd_neighbor_solicit *)icmphdr; inet_ntop(AF_INET6, &nd_neigh_sol->nd_ns_target, nd_nas_target, 40); printf(" neighbor solicitation, who has %s, length %zd\n", nd_nas_target, buff_len); break; case ND_NEIGHBOR_ADVERT: LEN_CHECK((size_t)buff_len - (size_t)(sizeof(struct ip6_hdr)), sizeof(*nd_advert), "ICMPv6 Neighbor Advertisement"); nd_advert = (struct nd_neighbor_advert *)icmphdr; inet_ntop(AF_INET6, &nd_advert->nd_na_target, nd_nas_target, 40); printf(" neighbor advertisement, tgt is %s, length %zd\n", nd_nas_target, buff_len); break; default: printf(", destination unreachable, unknown icmp6 type (%u)\n", icmphdr->icmp6_type); break; } break; case IPPROTO_TCP: dump_tcp(ip_string, packet_buff, buff_len, sizeof(struct ip6_hdr), ipsrc, ipdst); break; case IPPROTO_UDP: dump_udp(ip_string, packet_buff, buff_len, sizeof(struct ip6_hdr), ipsrc, ipdst); break; default: printf(" IPv6 unknown protocol: %i\n", iphdr->ip6_nxt); } } static void dump_ip(unsigned char *packet_buff, ssize_t buff_len, int time_printed) { static const char ip_string[] = "IP"; char ipsrc[INET_ADDRSTRLEN]; char ipdst[INET_ADDRSTRLEN]; struct udphdr *tmp_udphdr; struct icmphdr *icmphdr; struct iphdr *tmp_iphdr; struct iphdr *iphdr; iphdr = (struct iphdr *)packet_buff; LEN_CHECK((size_t)buff_len, sizeof(*iphdr), ip_string); LEN_CHECK((size_t)buff_len, (size_t)(iphdr->ihl * 4), ip_string); LEN_CHECK((size_t)(iphdr->ihl * 4), sizeof(*iphdr), ip_string); if (!time_printed) print_time(); if (!inet_ntop(AF_INET, &iphdr->saddr, ipsrc, sizeof(ipsrc))) { fprintf(stderr, "Cannot decode source IP\n"); return; } if (!inet_ntop(AF_INET, &iphdr->daddr, ipdst, sizeof(ipdst))) { fprintf(stderr, "Cannot decode destination IP\n"); return; } switch (iphdr->protocol) { case IPPROTO_ICMP: LEN_CHECK((size_t)buff_len - (iphdr->ihl * 4), sizeof(struct icmphdr), "ICMP"); icmphdr = (struct icmphdr *)(packet_buff + (iphdr->ihl * 4)); printf("%s %s > ", ip_string, ipsrc); switch (icmphdr->type) { case ICMP_ECHOREPLY: printf("%s: ICMP echo reply, id %hu, seq %hu, length %zu\n", ipdst, ntohs(icmphdr->un.echo.id), ntohs(icmphdr->un.echo.sequence), (size_t)buff_len - (iphdr->ihl * 4)); break; case ICMP_DEST_UNREACH: switch (icmphdr->code) { case ICMP_PORT_UNREACH: LEN_CHECK((size_t)buff_len - (iphdr->ihl * 4) - sizeof(struct icmphdr), sizeof(struct iphdr), "ICMP DEST_UNREACH"); /* validate inner IP header information */ tmp_iphdr = (struct iphdr *)(((char *)icmphdr) + sizeof(struct icmphdr)); LEN_CHECK((size_t)buff_len - (iphdr->ihl * 4) - sizeof(struct icmphdr), (size_t)(tmp_iphdr->ihl * 4), "ICMP DEST_UNREACH"); LEN_CHECK((size_t)(tmp_iphdr->ihl * 4), sizeof(*iphdr), "ICMP DEST_UNREACH"); LEN_CHECK((size_t)buff_len - (iphdr->ihl * 4) - sizeof(struct icmphdr) - (tmp_iphdr->ihl * 4), sizeof(*tmp_udphdr), "ICMP DEST_UNREACH"); tmp_udphdr = (struct udphdr *)(((char *)tmp_iphdr) + (tmp_iphdr->ihl * 4)); printf("%s: ICMP ", ipdst); printf("%s udp port %hu unreachable, length %zu\n", ipdst, ntohs(tmp_udphdr->dest), (size_t)buff_len - (iphdr->ihl * 4)); break; default: printf("%s: ICMP unreachable %hhu, length %zu\n", ipdst, icmphdr->code, (size_t)buff_len - (iphdr->ihl * 4)); break; } break; case ICMP_ECHO: printf("%s: ICMP echo request, id %hu, seq %hu, length %zu\n", ipdst, ntohs(icmphdr->un.echo.id), ntohs(icmphdr->un.echo.sequence), (size_t)buff_len - (iphdr->ihl * 4)); break; case ICMP_TIME_EXCEEDED: printf("%s: ICMP time exceeded in-transit, length %zu\n", ipdst, (size_t)buff_len - (iphdr->ihl * 4)); break; default: printf("%s: ICMP type %hhu, length %zu\n", ipdst, icmphdr->type, (size_t)buff_len - (iphdr->ihl * 4)); break; } break; case IPPROTO_TCP: dump_tcp(ip_string, packet_buff, buff_len, iphdr->ihl * 4, ipsrc, ipdst); break; case IPPROTO_UDP: dump_udp(ip_string, packet_buff, buff_len, iphdr->ihl * 4, ipsrc, ipdst); break; default: printf("IP unknown protocol: %i\n", iphdr->protocol); break; } } static void dump_vlan(unsigned char *packet_buff, ssize_t buff_len, int read_opt, int time_printed) { struct vlanhdr *vlanhdr; vlanhdr = (struct vlanhdr *)(packet_buff + sizeof(struct ether_header)); LEN_CHECK((size_t)buff_len, sizeof(struct ether_header) + sizeof(struct vlanhdr), "VLAN"); if (!time_printed) time_printed = print_time(); vlanhdr->vid = ntohs(vlanhdr->vid); printf("vlan %u, p %u, ", vlanhdr->vid, vlanhdr->vid >> 12); /* overwrite vlan tags */ memmove(packet_buff + 4, packet_buff, 2 * ETH_ALEN); parse_eth_hdr(packet_buff + 4, buff_len - 4, read_opt, time_printed); } static void dump_batman_iv_ogm(unsigned char *packet_buff, ssize_t buff_len, int read_opt, int time_printed) { struct ether_header *ether_header; struct batadv_ogm_packet *batman_ogm_packet; ssize_t check_len; ssize_t tvlv_len; check_len = (size_t)buff_len - sizeof(struct ether_header); LEN_CHECK(check_len, sizeof(struct batadv_ogm_packet), "BAT IV OGM"); ether_header = (struct ether_header *)packet_buff; batman_ogm_packet = (struct batadv_ogm_packet *)(packet_buff + sizeof(struct ether_header)); if (!time_printed) print_time(); printf("BAT %s: ", get_name_by_macaddr((struct ether_addr *)batman_ogm_packet->orig, read_opt)); tvlv_len = ntohs(batman_ogm_packet->tvlv_len); printf("OGM IV via neigh %s, seq %u, tq %3d, ttl %2d, v %d, flags [%c%c%c], length %zu, tvlv_len %zu\n", get_name_by_macaddr((struct ether_addr *)ether_header->ether_shost, read_opt), ntohl(batman_ogm_packet->seqno), batman_ogm_packet->tq, batman_ogm_packet->ttl, batman_ogm_packet->version, (batman_ogm_packet->flags & BATADV_NOT_BEST_NEXT_HOP ? 'N' : '.'), (batman_ogm_packet->flags & BATADV_DIRECTLINK ? 'D' : '.'), (batman_ogm_packet->flags & BATADV_PRIMARIES_FIRST_HOP ? 'F' : '.'), check_len, tvlv_len); check_len -= sizeof(struct batadv_ogm_packet); LEN_CHECK(check_len, (size_t)tvlv_len, "BAT OGM TVLV (containers)"); dump_tvlv((uint8_t *)(batman_ogm_packet + 1), tvlv_len, read_opt); } static void dump_batman_ogm2(unsigned char *packet_buff, ssize_t buff_len, int read_opt, int time_printed) { struct batadv_ogm2_packet *batman_ogm2; struct ether_header *ether_header; struct ether_addr *ether_addr; uint32_t throughput; ssize_t check_len; ssize_t tvlv_len; char thr_str[20]; check_len = (size_t)buff_len - sizeof(struct ether_header); LEN_CHECK(check_len, BATADV_OGM2_HLEN, "BAT OGM2"); ether_header = (struct ether_header *)packet_buff; batman_ogm2 = (struct batadv_ogm2_packet *)(packet_buff + sizeof(struct ether_header)); if (!time_printed) print_time(); ether_addr = (struct ether_addr *)batman_ogm2->orig; printf("BAT %s: ", get_name_by_macaddr(ether_addr, read_opt)); tvlv_len = ntohs(batman_ogm2->tvlv_len); throughput = ntohl(batman_ogm2->throughput); if (throughput == BATADV_THROUGHPUT_MAX_VALUE) snprintf(thr_str, sizeof(thr_str), "MAX"); else snprintf(thr_str, sizeof(thr_str), "%.1fMbps", (float)ntohl(batman_ogm2->throughput) / 10); ether_addr = (struct ether_addr *)ether_header->ether_shost; printf("OGM2 via neigh %s, seq %u, throughput %s, ttl %2d, v %d, length %zu, tvlv_len %zu\n", get_name_by_macaddr(ether_addr, read_opt), ntohl(batman_ogm2->seqno), thr_str, batman_ogm2->ttl, batman_ogm2->version, check_len, tvlv_len); check_len -= BATADV_OGM2_HLEN; LEN_CHECK(check_len, (size_t)tvlv_len, "BAT OGM2 TVLV (containers)"); dump_tvlv((uint8_t *)(batman_ogm2 + 1), tvlv_len, read_opt); } static void dump_batman_elp(unsigned char *packet_buff, ssize_t buff_len, int read_opt, int time_printed) { struct batadv_elp_packet *batman_elp; struct ether_header *ether_header; struct ether_addr *ether_addr; ssize_t check_len; check_len = (size_t)buff_len - sizeof(struct ether_header); LEN_CHECK(check_len, BATADV_ELP_HLEN, "BAT ELP"); ether_header = (struct ether_header *)packet_buff; batman_elp = (struct batadv_elp_packet *)(packet_buff + sizeof(struct ether_header)); if (!time_printed) print_time(); ether_addr = (struct ether_addr *)batman_elp->orig; printf("BAT %s: ", get_name_by_macaddr(ether_addr, read_opt)); ether_addr = (struct ether_addr *)ether_header->ether_shost; printf("ELP via iface %s, seq %u, v %d, interval %ums, length %zu\n", get_name_by_macaddr(ether_addr, read_opt), ntohl(batman_elp->seqno), batman_elp->version, ntohl(batman_elp->elp_interval), check_len); } static void dump_batman_icmp(unsigned char *packet_buff, ssize_t buff_len, int read_opt, int time_printed) { struct batadv_icmp_packet *icmp_packet; struct batadv_icmp_tp_packet *tp; char *name; LEN_CHECK((size_t)buff_len - sizeof(struct ether_header), sizeof(struct batadv_icmp_packet), "BAT ICMP"); icmp_packet = (struct batadv_icmp_packet *)(packet_buff + sizeof(struct ether_header)); if (!time_printed) print_time(); printf("BAT %s > ", get_name_by_macaddr((struct ether_addr *)icmp_packet->orig, read_opt)); name = get_name_by_macaddr((struct ether_addr *)icmp_packet->dst, read_opt); switch (icmp_packet->msg_type) { case BATADV_ECHO_REPLY: printf("%s: ICMP echo reply, id %hhu, seq %hu, ttl %2d, v %d, length %zu\n", name, icmp_packet->uid, ntohs(icmp_packet->seqno), icmp_packet->ttl, icmp_packet->version, (size_t)buff_len - sizeof(struct ether_header)); break; case BATADV_ECHO_REQUEST: printf("%s: ICMP echo request, id %hhu, seq %hu, ttl %2d, v %d, length %zu\n", name, icmp_packet->uid, ntohs(icmp_packet->seqno), icmp_packet->ttl, icmp_packet->version, (size_t)buff_len - sizeof(struct ether_header)); break; case BATADV_TTL_EXCEEDED: printf("%s: ICMP time exceeded in-transit, id %hhu, seq %hu, ttl %2d, v %d, length %zu\n", name, icmp_packet->uid, ntohs(icmp_packet->seqno), icmp_packet->ttl, icmp_packet->version, (size_t)buff_len - sizeof(struct ether_header)); break; case BATADV_TP: LEN_CHECK((size_t)buff_len - sizeof(struct ether_header), sizeof(*tp), "BAT TP"); tp = (struct batadv_icmp_tp_packet *)icmp_packet; printf("%s: ICMP TP type %s (%hhu), id %hhu, seq %u, ttl %2d, v %d, length %zu\n", name, tp->subtype == BATADV_TP_MSG ? "MSG" : tp->subtype == BATADV_TP_ACK ? "ACK" : "N/A", tp->subtype, tp->uid, ntohl(tp->seqno), tp->ttl, tp->version, (size_t)buff_len - sizeof(struct ether_header)); break; default: printf("%s: ICMP type %hhu, length %zu\n", name, icmp_packet->msg_type, (size_t)buff_len - sizeof(struct ether_header)); break; } } static void dump_batman_ucast(unsigned char *packet_buff, ssize_t buff_len, int read_opt, int time_printed) { struct batadv_unicast_packet *unicast_packet; struct ether_header *ether_header; LEN_CHECK((size_t)buff_len - sizeof(struct ether_header), sizeof(struct batadv_unicast_packet), "BAT UCAST"); LEN_CHECK((size_t)buff_len - sizeof(struct ether_header) - sizeof(struct batadv_unicast_packet), sizeof(struct ether_header), "BAT UCAST (unpacked)"); ether_header = (struct ether_header *)packet_buff; unicast_packet = (struct batadv_unicast_packet *)(packet_buff + sizeof(struct ether_header)); if (!time_printed) time_printed = print_time(); printf("BAT %s > ", get_name_by_macaddr((struct ether_addr *)ether_header->ether_shost, read_opt)); printf("%s: UCAST, ttvn %d, ttl %hhu, ", get_name_by_macaddr((struct ether_addr *)unicast_packet->dest, read_opt), unicast_packet->ttvn, unicast_packet->ttl); parse_eth_hdr(packet_buff + ETH_HLEN + sizeof(struct batadv_unicast_packet), buff_len - ETH_HLEN - sizeof(struct batadv_unicast_packet), read_opt, time_printed); } static void dump_batman_ucast_frag(unsigned char *packet_buff, ssize_t buff_len, int read_opt, int time_printed) { struct batadv_frag_packet *frag_packet; struct ether_header *ether_header; LEN_CHECK((size_t)buff_len - sizeof(*ether_header), sizeof(*frag_packet), "BAT UCAST FRAG"); ether_header = (struct ether_header *)packet_buff; frag_packet = (struct batadv_frag_packet *)(packet_buff + sizeof(*ether_header)); if (!time_printed) time_printed = print_time(); printf("BAT %s > ", get_name_by_macaddr((struct ether_addr *)ether_header->ether_shost, read_opt)); printf("%s: UCAST FRAG, seqno %d, no %d, ttl %hhu\n", get_name_by_macaddr((struct ether_addr *)frag_packet->dest, read_opt), frag_packet->seqno, frag_packet->no, frag_packet->ttl); } static void dump_batman_bcast(unsigned char *packet_buff, ssize_t buff_len, int read_opt, int time_printed) { struct batadv_bcast_packet *bcast_packet; struct ether_header *ether_header; LEN_CHECK((size_t)buff_len - sizeof(struct ether_header), sizeof(struct batadv_bcast_packet), "BAT BCAST"); LEN_CHECK((size_t)buff_len - sizeof(struct ether_header) - sizeof(struct batadv_bcast_packet), sizeof(struct ether_header), "BAT BCAST (unpacked)"); ether_header = (struct ether_header *)packet_buff; bcast_packet = (struct batadv_bcast_packet *)(packet_buff + sizeof(struct ether_header)); if (!time_printed) time_printed = print_time(); printf("BAT %s: ", get_name_by_macaddr((struct ether_addr *)ether_header->ether_shost, read_opt)); printf("BCAST, orig %s, seq %u, ", get_name_by_macaddr((struct ether_addr *)bcast_packet->orig, read_opt), ntohl(bcast_packet->seqno)); parse_eth_hdr(packet_buff + ETH_HLEN + sizeof(struct batadv_bcast_packet), buff_len - ETH_HLEN - sizeof(struct batadv_bcast_packet), read_opt, time_printed); } static void dump_batman_mcast(unsigned char *packet_buff, ssize_t buff_len, int read_opt, int time_printed) { struct batadv_mcast_packet *mcast_packet; struct ether_header *ether_header; uint8_t *tvlv_hdr; ssize_t check_len; size_t tvlv_len; check_len = (size_t)buff_len - sizeof(struct ether_header); LEN_CHECK(check_len, sizeof(*mcast_packet), "BAT MCAST"); check_len -= sizeof(*mcast_packet); ether_header = (struct ether_header *)packet_buff; mcast_packet = (struct batadv_mcast_packet *)(ether_header + 1); tvlv_len = ntohs(mcast_packet->tvlv_len); LEN_CHECK(check_len, tvlv_len, "BAT MCAST TVLV (containers)"); check_len -= tvlv_len; tvlv_hdr = (uint8_t *)(mcast_packet + 1); if (!time_printed) time_printed = print_time(); printf("BAT %s > ", get_name_by_macaddr((struct ether_addr *)ether_header->ether_shost, read_opt)); printf("%s: MCAST, ttl %hhu, ", get_name_by_macaddr((struct ether_addr *)ether_header->ether_dhost, read_opt), mcast_packet->ttl); /* batman-adv mcast packet's data payload is optional */ if (check_len >= ETH_HLEN) { ether_header = (struct ether_header *)(tvlv_hdr + tvlv_len); printf("%s > ", get_name_by_macaddr((struct ether_addr *)ether_header->ether_shost, read_opt)); printf("%s, ", get_name_by_macaddr((struct ether_addr *)ether_header->ether_dhost, read_opt)); parse_eth_hdr((unsigned char *)ether_header, check_len, read_opt, time_printed); } else { printf("\n"); } dump_tvlv(tvlv_hdr, tvlv_len, read_opt); } static void dump_batman_coded(unsigned char *packet_buff, ssize_t buff_len, int read_opt, int time_printed) { struct batadv_coded_packet *coded_packet; struct ether_header *ether_header; LEN_CHECK((size_t)buff_len - sizeof(*ether_header), sizeof(*coded_packet), "BAT CODED"); ether_header = (struct ether_header *)packet_buff; coded_packet = (struct batadv_coded_packet *)(packet_buff + sizeof(*ether_header)); if (!time_printed) time_printed = print_time(); printf("BAT %s > ", get_name_by_macaddr((struct ether_addr *)ether_header->ether_shost, read_opt)); printf("%s|%s: CODED, ttvn %d|%d, ttl %hhu\n", get_name_by_macaddr((struct ether_addr *)coded_packet->first_orig_dest, read_opt), get_name_by_macaddr((struct ether_addr *)coded_packet->second_dest, read_opt), coded_packet->first_ttvn, coded_packet->second_ttvn, coded_packet->ttl); } static void dump_batman_4addr(unsigned char *packet_buff, ssize_t buff_len, int read_opt, int time_printed) { struct batadv_unicast_4addr_packet *unicast_4addr_packet; struct ether_header *ether_header; LEN_CHECK((size_t)buff_len - sizeof(struct ether_header), sizeof(struct batadv_unicast_4addr_packet), "BAT 4ADDR"); LEN_CHECK((size_t)buff_len - sizeof(struct ether_header) - sizeof(struct batadv_unicast_4addr_packet), sizeof(struct ether_header), "BAT 4ADDR (unpacked)"); ether_header = (struct ether_header *)packet_buff; unicast_4addr_packet = (struct batadv_unicast_4addr_packet *)(packet_buff + sizeof(struct ether_header)); if (!time_printed) time_printed = print_time(); printf("BAT %s > ", get_name_by_macaddr((struct ether_addr *)ether_header->ether_shost, read_opt)); printf("%s: 4ADDR, subtybe %hhu, ttvn %d, ttl %hhu, ", get_name_by_macaddr((struct ether_addr *)unicast_4addr_packet->u.dest, read_opt), unicast_4addr_packet->subtype, unicast_4addr_packet->u.ttvn, unicast_4addr_packet->u.ttl); parse_eth_hdr(packet_buff + ETH_HLEN + sizeof(struct batadv_unicast_4addr_packet), buff_len - ETH_HLEN - sizeof(struct batadv_unicast_4addr_packet), read_opt, time_printed); } static void parse_eth_hdr(unsigned char *packet_buff, ssize_t buff_len, int read_opt, int time_printed) { struct batadv_ogm_packet *batman_ogm_packet; struct ether_header *eth_hdr; eth_hdr = (struct ether_header *)packet_buff; switch (ntohs(eth_hdr->ether_type)) { case ETH_P_ARP: if ((dump_level & DUMP_TYPE_NONBAT) || (time_printed)) dump_arp(packet_buff + ETH_HLEN, buff_len - ETH_HLEN, eth_hdr, read_opt, time_printed); break; case ETH_P_IP: if ((dump_level & DUMP_TYPE_NONBAT) || (time_printed)) dump_ip(packet_buff + ETH_HLEN, buff_len - ETH_HLEN, time_printed); break; case ETH_P_IPV6: if ((dump_level & DUMP_TYPE_NONBAT) || (time_printed)) dump_ipv6(packet_buff + ETH_HLEN, buff_len - ETH_HLEN, time_printed); break; case ETH_P_8021Q: if ((dump_level & DUMP_TYPE_NONBAT) || (time_printed)) dump_vlan(packet_buff, buff_len, read_opt, time_printed); break; case ETH_P_BATMAN: /* check for batman-adv packet_type + version */ LEN_CHECK(buff_len, sizeof(*eth_hdr) + 2, "BAT HEADER"); batman_ogm_packet = (struct batadv_ogm_packet *)(packet_buff + ETH_HLEN); if (read_opt & COMPAT_FILTER && batman_ogm_packet->version != BATADV_COMPAT_VERSION) return; switch (batman_ogm_packet->packet_type) { case BATADV_IV_OGM: if (dump_level & DUMP_TYPE_BATOGM) dump_batman_iv_ogm(packet_buff, buff_len, read_opt, time_printed); break; case BATADV_OGM2: if (dump_level & DUMP_TYPE_BATOGM2) dump_batman_ogm2(packet_buff, buff_len, read_opt, time_printed); break; case BATADV_ELP: if (dump_level & DUMP_TYPE_BATELP) dump_batman_elp(packet_buff, buff_len, read_opt, time_printed); break; case BATADV_ICMP: if (dump_level & DUMP_TYPE_BATICMP) dump_batman_icmp(packet_buff, buff_len, read_opt, time_printed); break; case BATADV_UNICAST: if (dump_level & DUMP_TYPE_BATUCAST) dump_batman_ucast(packet_buff, buff_len, read_opt, time_printed); break; case BATADV_UNICAST_FRAG: if (dump_level & DUMP_TYPE_BATFRAG) dump_batman_ucast_frag(packet_buff, buff_len, read_opt, time_printed); break; case BATADV_BCAST: if (dump_level & DUMP_TYPE_BATBCAST) dump_batman_bcast(packet_buff, buff_len, read_opt, time_printed); break; case BATADV_MCAST: if (dump_level & DUMP_TYPE_BATMCAST) dump_batman_mcast(packet_buff, buff_len, read_opt, time_printed); break; case BATADV_CODED: if (dump_level & DUMP_TYPE_BATCODED) dump_batman_coded(packet_buff, buff_len, read_opt, time_printed); break; case BATADV_UNICAST_4ADDR: if (dump_level & DUMP_TYPE_BATUCAST) dump_batman_4addr(packet_buff, buff_len, read_opt, time_printed); break; case BATADV_UNICAST_TVLV: if ((dump_level & DUMP_TYPE_BATUCAST) || (dump_level & DUMP_TYPE_BATUTVLV)) dump_batman_ucast_tvlv(packet_buff, buff_len, read_opt, time_printed); break; default: fprintf(stderr, "Warning - packet contains unknown batman packet type: 0x%02x\n", batman_ogm_packet->packet_type); break; } break; default: fprintf(stderr, "Warning - packet contains unknown ether type: 0x%04x\n", ntohs(eth_hdr->ether_type)); break; } } static int monitor_header_length(unsigned char *packet_buff, ssize_t buff_len, int32_t hw_type) { struct radiotap_header *radiotap_hdr; switch (hw_type) { case ARPHRD_IEEE80211_PRISM: if (buff_len <= (ssize_t)PRISM_HEADER_LEN) return -1; else return PRISM_HEADER_LEN; case ARPHRD_IEEE80211_RADIOTAP: if (buff_len <= (ssize_t)RADIOTAP_HEADER_LEN) return -1; radiotap_hdr = (struct radiotap_header *)packet_buff; if (buff_len <= le16toh(radiotap_hdr->it_len)) return -1; else return le16toh(radiotap_hdr->it_len); } return -1; } static void parse_wifi_hdr(unsigned char *packet_buff, ssize_t buff_len, int read_opt, int time_printed) { struct ieee80211_hdr *wifi_hdr; struct ether_header *eth_hdr; unsigned char *shost; unsigned char *dhost; uint16_t fc; int hdr_len; /* we assume a minimum size of 38 bytes * (802.11 data frame + LLC) * before we calculate the real size */ if (buff_len <= 38) return; wifi_hdr = (struct ieee80211_hdr *)packet_buff; fc = ntohs(wifi_hdr->frame_control); /* not carrying payload */ if ((fc & IEEE80211_FCTL_FTYPE) != IEEE80211_FTYPE_DATA) return; /* encrypted packet */ if (fc & IEEE80211_FCTL_PROTECTED) return; shost = wifi_hdr->addr2; if (fc & IEEE80211_FCTL_FROMDS) shost = wifi_hdr->addr3; else if (fc & IEEE80211_FCTL_TODS) shost = wifi_hdr->addr4; dhost = wifi_hdr->addr1; if (fc & IEEE80211_FCTL_TODS) dhost = wifi_hdr->addr3; hdr_len = 24; if ((fc & IEEE80211_FCTL_FROMDS) && (fc & IEEE80211_FCTL_TODS)) hdr_len = 30; if (fc & IEEE80211_STYPE_QOS_DATA) hdr_len += 2; /* LLC */ hdr_len += 8; hdr_len -= sizeof(struct ether_header); if (buff_len <= hdr_len) return; buff_len -= hdr_len; packet_buff += hdr_len; eth_hdr = (struct ether_header *)packet_buff; memmove(eth_hdr->ether_shost, shost, ETH_ALEN); memmove(eth_hdr->ether_dhost, dhost, ETH_ALEN); parse_eth_hdr(packet_buff, buff_len, read_opt, time_printed); } static struct dump_if *create_dump_interface(char *iface) { struct dump_if *dump_if; struct ifreq req; int res; dump_if = malloc(sizeof(struct dump_if)); if (!dump_if) return NULL; memset(dump_if, 0, sizeof(struct dump_if)); dump_if->dev = iface; if (strlen(dump_if->dev) > IFNAMSIZ - 1) { fprintf(stderr, "Error - interface name too long: %s\n", dump_if->dev); goto free_dumpif; } dump_if->raw_sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (dump_if->raw_sock < 0) { perror("Error - can't create raw socket"); goto free_dumpif; } memset(&req, 0, sizeof(struct ifreq)); strncpy(req.ifr_name, dump_if->dev, IFNAMSIZ); req.ifr_name[sizeof(req.ifr_name) - 1] = '\0'; res = ioctl(dump_if->raw_sock, SIOCGIFHWADDR, &req); if (res < 0) { perror("Error - can't create raw socket (SIOCGIFHWADDR)"); goto close_socket; } dump_if->hw_type = req.ifr_hwaddr.sa_family; switch (dump_if->hw_type) { case ARPHRD_ETHER: case ARPHRD_IEEE80211_PRISM: case ARPHRD_IEEE80211_RADIOTAP: break; default: fprintf(stderr, "Error - interface '%s' is of unknown type: %i\n", dump_if->dev, dump_if->hw_type); goto close_socket; } memset(&req, 0, sizeof(struct ifreq)); strncpy(req.ifr_name, dump_if->dev, IFNAMSIZ); req.ifr_name[sizeof(req.ifr_name) - 1] = '\0'; res = ioctl(dump_if->raw_sock, SIOCGIFINDEX, &req); if (res < 0) { perror("Error - can't create raw socket (SIOCGIFINDEX)"); goto close_socket; } dump_if->addr.sll_family = AF_PACKET; dump_if->addr.sll_protocol = htons(ETH_P_ALL); dump_if->addr.sll_ifindex = req.ifr_ifindex; res = bind(dump_if->raw_sock, (struct sockaddr *)&dump_if->addr, sizeof(struct sockaddr_ll)); if (res < 0) { perror("Error - can't bind raw socket"); goto close_socket; } return dump_if; close_socket: close(dump_if->raw_sock); free_dumpif: free(dump_if); return NULL; } static volatile sig_atomic_t is_aborted; static void sig_handler(int sig) { switch (sig) { case SIGINT: case SIGTERM: is_aborted = 1; break; default: break; } } static int tcpdump(struct state *state __maybe_unused, int argc, char **argv) { unsigned char packet_buff[2000]; struct list_head dump_if_list; int read_opt = USE_BAT_HOSTS; int monitor_header_len = -1; struct dump_if *dump_if_tmp; struct dump_if *dump_if; fd_set tmp_wait_sockets; int ret = EXIT_FAILURE; fd_set wait_sockets; int found_args = 1; struct timeval tv; int max_sock = 0; ssize_t read_len; int optchar; int res; int tmp; dump_level = dump_level_all; while ((optchar = getopt(argc, argv, "chnp:x:")) != -1) { switch (optchar) { case 'c': read_opt |= COMPAT_FILTER; found_args += 1; break; case 'h': tcpdump_usage(); return EXIT_SUCCESS; case 'n': read_opt &= ~USE_BAT_HOSTS; found_args += 1; break; case 'p': tmp = strtol(optarg, NULL, 10); if (tmp > 0 && tmp <= dump_level_all) dump_level = tmp; found_args += ((*((char *)(optarg - 1)) == optchar) ? 1 : 2); break; case 'x': tmp = strtol(optarg, NULL, 10); if (tmp > 0 && tmp <= dump_level_all) dump_level &= ~tmp; found_args += ((*((char *)(optarg - 1)) == optchar) ? 1 : 2); break; default: tcpdump_usage(); return EXIT_FAILURE; } } if (argc <= found_args) { fprintf(stderr, "Error - target interface not specified\n"); tcpdump_usage(); return EXIT_FAILURE; } bat_hosts_init(read_opt); signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); /* init interfaces list */ INIT_LIST_HEAD(&dump_if_list); FD_ZERO(&wait_sockets); while (argc > found_args) { dump_if = create_dump_interface(argv[found_args]); if (!dump_if) goto out; if (dump_if->raw_sock > max_sock) max_sock = dump_if->raw_sock; FD_SET(dump_if->raw_sock, &wait_sockets); list_add_tail(&dump_if->list, &dump_if_list); found_args++; } while (!is_aborted) { memcpy(&tmp_wait_sockets, &wait_sockets, sizeof(fd_set)); tv.tv_sec = 1; tv.tv_usec = 0; res = select(max_sock + 1, &tmp_wait_sockets, NULL, NULL, &tv); if (res == 0) continue; if (res < 0) { perror("Error - can't select on raw socket"); continue; } list_for_each_entry(dump_if, &dump_if_list, list) { if (!FD_ISSET(dump_if->raw_sock, &tmp_wait_sockets)) continue; read_len = read(dump_if->raw_sock, packet_buff, sizeof(packet_buff)); if (read_len < 0) { fprintf(stderr, "Error - can't read from interface '%s': %s\n", dump_if->dev, strerror(errno)); continue; } if ((size_t)read_len < sizeof(struct ether_header)) { fprintf(stderr, "Warning - dropping received packet as it is smaller than expected (%zu): %zd\n", sizeof(struct ether_header), read_len); continue; } switch (dump_if->hw_type) { case ARPHRD_ETHER: parse_eth_hdr(packet_buff, read_len, read_opt, 0); break; case ARPHRD_IEEE80211_PRISM: case ARPHRD_IEEE80211_RADIOTAP: monitor_header_len = monitor_header_length(packet_buff, read_len, dump_if->hw_type); if (monitor_header_len >= 0) parse_wifi_hdr(packet_buff + monitor_header_len, read_len - monitor_header_len, read_opt, 0); break; default: /* should not happen */ break; } fflush(stdout); } } out: list_for_each_entry_safe(dump_if, dump_if_tmp, &dump_if_list, list) { if (dump_if->raw_sock >= 0) close(dump_if->raw_sock); list_del(&dump_if->list); free(dump_if); } bat_hosts_free(); return ret; } COMMAND(SUBCOMMAND, tcpdump, "td", 0, NULL, " \ttcpdump layer 2 traffic on the given interface"); batctl-2025.1/tcpdump.h000066400000000000000000000043321500012142700146730ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) B.A.T.M.A.N. contributors: * * Andreas Langer , Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #ifndef _BATCTL_TCPDUMP_H #define _BATCTL_TCPDUMP_H #include #include #include #include #include "main.h" #include "list.h" #ifndef ARPHRD_IEEE80211_PRISM #define ARPHRD_IEEE80211_PRISM 802 #endif #ifndef ARPHRD_IEEE80211_RADIOTAP #define ARPHRD_IEEE80211_RADIOTAP 803 #endif #define DUMP_TYPE_BATOGM 1 #define DUMP_TYPE_BATOGM2 2 #define DUMP_TYPE_BATELP 4 #define DUMP_TYPE_BATICMP 8 #define DUMP_TYPE_BATUCAST 16 #define DUMP_TYPE_BATBCAST 32 #define DUMP_TYPE_BATUTVLV 64 #define DUMP_TYPE_BATFRAG 128 #define DUMP_TYPE_NONBAT 256 #define DUMP_TYPE_BATCODED 512 #define DUMP_TYPE_BATMCAST 1024 #define IEEE80211_FCTL_FTYPE 0x0c00 #define IEEE80211_FCTL_TODS 0x0001 #define IEEE80211_FCTL_FROMDS 0x0002 #define IEEE80211_FCTL_PROTECTED 0x0040 #define IEEE80211_FTYPE_DATA 0x0800 #define IEEE80211_STYPE_QOS_DATA 0x8000 struct dump_if { struct list_head list; char *dev; int32_t raw_sock; struct sockaddr_ll addr; int32_t hw_type; }; struct vlanhdr { unsigned short vid; u_int16_t ether_type; } __attribute__ ((packed)); struct ieee80211_hdr { u_int16_t frame_control; u_int16_t duration_id; u_int8_t addr1[ETH_ALEN]; u_int8_t addr2[ETH_ALEN]; u_int8_t addr3[ETH_ALEN]; u_int16_t seq_ctrl; u_int8_t addr4[ETH_ALEN]; } __attribute__ ((packed)); struct radiotap_header { u_int8_t it_version; u_int8_t it_pad; u_int16_t it_len; u_int32_t it_present; } __attribute__((__packed__)); struct prism_item { u_int32_t did; u_int16_t status; u_int16_t len; u_int32_t data; }; struct prism_header { u_int32_t msgcode; u_int32_t msglen; u_int8_t devname[16]; struct prism_item hosttime; struct prism_item mactime; struct prism_item channel; struct prism_item rssi; struct prism_item sq; struct prism_item signal; struct prism_item noise; struct prism_item rate; struct prism_item istx; struct prism_item frmlen; }; #define PRISM_HEADER_LEN sizeof(struct prism_header) #define RADIOTAP_HEADER_LEN sizeof(struct radiotap_header) #endif batctl-2025.1/throughput_override.c000066400000000000000000000054731500012142700173310ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Marek Lindner * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include "functions.h" #include "main.h" #include "sys.h" static struct throughput_override_data { uint32_t throughput_override; } throughput_override; static int parse_throughput_override(struct state *state, int argc, char *argv[]) { struct settings_data *settings = state->cmd->arg; struct throughput_override_data *data = settings->data; bool ret; if (argc != 2) { fprintf(stderr, "Error - incorrect number of arguments (expected 1)\n"); return -EINVAL; } ret = parse_throughput(argv[1], "throughput override", &data->throughput_override); if (!ret) return -EINVAL; return 0; } static int print_throughput_override(struct nl_msg *msg, void *arg) { struct nlattr *attrs[BATADV_ATTR_MAX + 1]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct genlmsghdr *ghdr; int *result = arg; uint32_t mbit; if (!genlmsg_valid_hdr(nlh, 0)) return NL_OK; ghdr = nlmsg_data(nlh); if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { return NL_OK; } if (!attrs[BATADV_ATTR_THROUGHPUT_OVERRIDE]) return NL_OK; mbit = nla_get_u32(attrs[BATADV_ATTR_THROUGHPUT_OVERRIDE]); printf("%u.%u MBit\n", mbit / 10, mbit % 10); *result = 0; return NL_STOP; } static int get_attrs_throughput_override(struct nl_msg *msg, void *arg) { struct state *state = arg; nla_put_u32(msg, BATADV_ATTR_HARD_IFINDEX, state->hif); return 0; } static int get_throughput_override(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_GET_HARDIF, get_attrs_throughput_override, print_throughput_override); } static int set_attrs_throughput_override(struct nl_msg *msg, void *arg) { struct state *state = arg; struct settings_data *settings = state->cmd->arg; struct throughput_override_data *data = settings->data; nla_put_u32(msg, BATADV_ATTR_HARD_IFINDEX, state->hif); nla_put_u32(msg, BATADV_ATTR_THROUGHPUT_OVERRIDE, data->throughput_override); return 0; } static int set_throughput_override(struct state *state) { return sys_simple_nlquery(state, BATADV_CMD_SET_HARDIF, set_attrs_throughput_override, NULL); } static struct settings_data batctl_settings_throughput_override = { .data = &throughput_override, .parse = parse_throughput_override, .netlink_get = get_throughput_override, .netlink_set = set_throughput_override, }; COMMAND_NAMED(SUBCOMMAND_HIF, throughput_override, "to", handle_sys_setting, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_settings_throughput_override, "[mbit] \tdisplay or modify throughput_override setting"); batctl-2025.1/throughputmeter.c000066400000000000000000000245201500012142700164610ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Antonio Quartulli * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include "main.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bat-hosts.h" #include "batman_adv.h" #include "functions.h" #include "genl.h" #include "netlink.h" static struct ether_addr *dst_mac; static struct state *tp_state; struct tp_result { int error; uint32_t cookie; uint8_t return_value; uint8_t found:1; uint32_t test_time; uint64_t total_bytes; }; struct tp_cookie { int error; uint8_t found:1; uint32_t cookie; }; static int tpmeter_nl_print_error(struct sockaddr_nl *nla __maybe_unused, struct nlmsgerr *nlerr, void *arg) { struct tp_result *result = arg; if (nlerr->error != -EOPNOTSUPP) fprintf(stderr, "Error received: %s\n", strerror(-nlerr->error)); result->error = nlerr->error; return NL_STOP; } static int tp_meter_result_callback(struct nl_msg *msg, void *arg) { struct tp_result *result = arg; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct nlattr *attrs[NUM_BATADV_ATTR]; struct genlmsghdr *ghdr; uint32_t cookie; if (!genlmsg_valid_hdr(nlh, 0)) { result->error = -EINVAL; return NL_STOP; } ghdr = nlmsg_data(nlh); if (ghdr->cmd != BATADV_CMD_TP_METER) return NL_OK; if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { fputs("Received invalid data from kernel.\n", stderr); result->error = -EINVAL; return NL_STOP; } if (!attrs[BATADV_ATTR_TPMETER_COOKIE]) { result->error = -EINVAL; return NL_STOP; } if (!attrs[BATADV_ATTR_TPMETER_RESULT]) return NL_OK; cookie = nla_get_u32(attrs[BATADV_ATTR_TPMETER_COOKIE]); if (cookie != result->cookie) return NL_OK; result->found = true; result->return_value = nla_get_u8(attrs[BATADV_ATTR_TPMETER_RESULT]); if (attrs[BATADV_ATTR_TPMETER_TEST_TIME]) result->test_time = nla_get_u32(attrs[BATADV_ATTR_TPMETER_TEST_TIME]); if (attrs[BATADV_ATTR_TPMETER_BYTES]) result->total_bytes = nla_get_u64(attrs[BATADV_ATTR_TPMETER_BYTES]); return NL_OK; } static int tp_meter_cookie_callback(struct nl_msg *msg, void *arg) { struct nlattr *attrs[NUM_BATADV_ATTR]; struct nlmsghdr *nlh = nlmsg_hdr(msg); struct tp_cookie *cookie = arg; struct genlmsghdr *ghdr; if (!genlmsg_valid_hdr(nlh, 0)) { cookie->error = -EINVAL; return NL_STOP; } ghdr = nlmsg_data(nlh); if (ghdr->cmd != BATADV_CMD_TP_METER) { cookie->error = -EINVAL; return NL_STOP; } if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), batadv_netlink_policy)) { fputs("Received invalid data from kernel.\n", stderr); cookie->error = -EINVAL; return NL_STOP; } if (!attrs[BATADV_ATTR_TPMETER_COOKIE]) { cookie->error = -EINVAL; return NL_STOP; } cookie->cookie = nla_get_u32(attrs[BATADV_ATTR_TPMETER_COOKIE]); cookie->found = true; return NL_OK; } static int tp_meter_start(struct state *state, struct ether_addr *dst_mac, uint32_t time, struct tp_cookie *cookie) { struct nl_msg *msg; struct nl_cb *cb; int err = 0; cb = nl_cb_alloc(NL_CB_DEFAULT); nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, tp_meter_cookie_callback, cookie); nl_cb_err(cb, NL_CB_CUSTOM, tpmeter_nl_print_error, cookie); msg = nlmsg_alloc(); if (!msg) return -ENOMEM; genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, state->batadv_family, 0, 0, BATADV_CMD_TP_METER, 1); nla_put_u32(msg, BATADV_ATTR_MESH_IFINDEX, state->mesh_ifindex); nla_put(msg, BATADV_ATTR_ORIG_ADDRESS, ETH_ALEN, dst_mac); nla_put_u32(msg, BATADV_ATTR_TPMETER_TEST_TIME, time); nl_send_auto_complete(state->sock, msg); nlmsg_free(msg); nl_recvmsgs(state->sock, cb); nl_cb_put(cb); if (cookie->error < 0) err = cookie->error; else if (!cookie->found) err = -EINVAL; return err; } static int no_seq_check(struct nl_msg *msg __maybe_unused, void *arg __maybe_unused) { return NL_OK; } static int tp_recv_result(struct nl_sock *sock, struct tp_result *result) { struct nl_cb *cb; int err = 0; cb = nl_cb_alloc(NL_CB_DEFAULT); nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, no_seq_check, NULL); nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, tp_meter_result_callback, result); nl_cb_err(cb, NL_CB_CUSTOM, tpmeter_nl_print_error, result); while (result->error == 0 && !result->found) nl_recvmsgs(sock, cb); nl_cb_put(cb); if (result->error < 0) err = result->error; else if (!result->found) err = -EINVAL; return err; } static int tp_meter_stop(struct state *state, struct ether_addr *dst_mac) { struct nl_msg *msg; msg = nlmsg_alloc(); if (!msg) return -ENOMEM; genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, state->batadv_family, 0, 0, BATADV_CMD_TP_METER_CANCEL, 1); nla_put_u32(msg, BATADV_ATTR_MESH_IFINDEX, state->mesh_ifindex); nla_put(msg, BATADV_ATTR_ORIG_ADDRESS, ETH_ALEN, dst_mac); nl_send_auto_complete(state->sock, msg); nlmsg_free(msg); return 0; } static struct nl_sock *tp_prepare_listening_sock(void) { struct nl_sock *sock; int family; int ret; int mcid; sock = nl_socket_alloc(); if (!sock) return NULL; ret = genl_connect(sock); if (ret < 0) { fprintf(stderr, "Failed to connect to generic netlink: %d\n", ret); goto err; } family = genl_ctrl_resolve(sock, BATADV_NL_NAME); if (family < 0) { fprintf(stderr, "Failed to resolve batman-adv netlink: %d\n", family); goto err; } mcid = nl_get_multicast_id(sock, BATADV_NL_NAME, BATADV_NL_MCAST_GROUP_TPMETER); if (mcid < 0) { fprintf(stderr, "Failed to resolve batman-adv tpmeter multicast group: %d\n", mcid); goto err; } ret = nl_socket_add_membership(sock, mcid); if (ret) { fprintf(stderr, "Failed to join batman-adv tpmeter multicast group: %d\n", ret); goto err; } return sock; err: nl_socket_free(sock); return NULL; } void tp_sig_handler(int sig) { switch (sig) { case SIGINT: case SIGTERM: fflush(stdout); tp_meter_stop(tp_state, dst_mac); break; default: break; } } static void tp_meter_usage(void) { fprintf(stderr, "Usage: batctl tp [parameters] \n"); fprintf(stderr, "Parameters:\n"); fprintf(stderr, "\t -t