pax_global_header00006660000000000000000000000064150001215450014503gustar00rootroot0000000000000052 comment=cd04d1253ca179f47dae91e9a6dc8981e3c1217d alfred-2025.1/000077500000000000000000000000001500012154500130315ustar00rootroot00000000000000alfred-2025.1/.mailmap000066400000000000000000000031431500012154500144530ustar00rootroot00000000000000# # 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. # Antonio Quartulli Antonio Quartulli Antonio Quartulli Antonio Quartulli Antonio Quartulli Antonio Quartulli Antonio Quartulli Marek Lindner Marek Lindner Simon Wunderlich Simon Wunderlich Simon Wunderlich Simon Wunderlich Sven Eckelmann Sven Eckelmann Sven Eckelmann Sven Eckelmann alfred-2025.1/CHANGELOG.rst000066400000000000000000000165761500012154500150710ustar00rootroot00000000000000.. SPDX-License-Identifier: GPL-2.0 2025.1 (2025-04-17) =================== * coding style cleanups and refactoring 2025.0 (2025-02-07) =================== * coding style cleanups and refactoring 2024.4 (2024-12-10) =================== * (no changes) 2024.3 (2024-10-15) =================== * improve output for IPv6 related address errors 2024.2 (2024-06-20) =================== * (no changes) 2024.1 (2024-04-05) =================== * (no changes) 2024.0 (2024-02-01) =================== * (no changes) 2023.3 (2023-11-15) =================== * (no changes) 2023.2 (2023-08-16) =================== * receive data with valid source on unix sock without active interface 2023.1 (2023-05-25) =================== * (no changes) 2023.0 (2023-01-26) =================== * add support for gpsd 3.25 2022.3 (2022-11-10) =================== * coding style cleanups and refactoring 2022.2 (2022-07-26) =================== * support event notification via unix socket * improve timing stability of transmitted announcement packets * reduce socket handling overhead when many clients and interfaces 2022.1 (2022-05-06) =================== * coding style cleanups and refactoring * introduce 'server status' IPC call * bugs fixed: - prevent potential read outside of buffer when parsing alfred_change_interface_v0/alfred_change_bat_iface_v0 IPC message 2022.0 (2022-02-03) =================== * coding style cleanups and refactoring * allow changing of batman-adv interface at runtime * allow to start alfred without interfaces specified 2021.4 (2021-11-19) =================== * (no changes) 2021.3 (2021-09-14) =================== * (no changes) 2021.2 (2021-08-20) =================== * manpage cleanups 2021.1 (2021-05-18) =================== * Allow to force of alfred startup when the interfaces don't work/exist (yet) * coding style cleanups and refactoring 2021.0 (2021-01-28) =================== * Drop support for batman-adv's sysfs+debugfs 2020.4 (2020-10-27) =================== * (no changes) 2020.3 (2020-08-24) =================== * synchronization of batman-adv netlink header 2020.2 (2020-07-06) =================== * Rephrase names of server roles 2020.1 (2020-04-24) =================== * (no changes) 2020.0 (2020-03-04) =================== * fix build against gpsd API 9.0 2019.5 (2019-12-12) =================== * (no changes) 2019.4 (2019-10-25) =================== * fix build with musl 2019.3 (2019-08-01) =================== * avoid some kernel deprecation warning by using more generic netlink over sysfs 2019.2 (2019-05-23) =================== * synchronization of batman-adv netlink header 2019.1 (2019-03-28) =================== * synchronization of batman-adv netlink header 2019.0 (2019-02-01) =================== * (no changes) 2018.4 (2018-11-14) =================== * bugs squashed: - fixed detection of own IPv4 packets - use manual IPv4 ARP requests to retrieve MAC of neighbors 2018.3 (2018-09-14) =================== * (no changes) 2018.2 (2018-07-10) =================== * (no changes) 2018.1 (2018-04-25) =================== * synchronization of batman-adv netlink header 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: - only query debugfs when netlink failed - handle allocation errors in hashtable iterator 2017.3 (2017-09-28) =================== * reduction of memory usage when using --update-command 2017.2 (2017-07-28) =================== * reduce cpu load when rating multiple primary servers * coding style cleanups and refactoring 2017.1 (2017-05-23) =================== * (no changes) 2017.0 (2017-02-28) =================== * support IPv4 multicast distribution * coding style cleanups 2016.5 (2016-12-15) =================== * support interface validity checks on systems without debugfs * remove debugfs check during batadv-vis startup * allow out-of-order txend packets during transmissions 2016.4 2016-10-27) =================== * add expert option to specify sync interval * fix various bugs in batadv-vis netlink integration * fix build build problems with libnl-tiny 2016.3 (2016-09-01) =================== * integrate support for batman-adv netlink 2016.2 (2016-06-09) =================== * add support for automatic debugfs mount with enabled CONFIG_ALFRED_CAPABILITIES 2016.1 (2016-04-21) =================== * add support for primary servers to receive push_data packets with foreign source addresses * various code cleanups * bugs squashed: - ignore invalid EUI64 addresses 2016.0 (2016-01-19) =================== * various code and documentation cleanups 2015.2 (2015-11-23) =================== * mention libcap in the README * Fix typos 2015.1 (2015-08-04) =================== * add support to run on interfaces with multiple link-local addresses * various code cleanups * bugs squashed: - reduce of maximum payload size to always fit into UDP datagrams 2015.0 (2015-04-28) =================== * add support to call commands after data was updated * automatic reduction of process capabilities when not needed anymore * allow printing of the data version number in the alfred client mode * various code cleanups * bugs squashed: - update of the version number when data was updated with different version number - tighten size check on received packet 2014.4.0 (2014-12-31) ===================== * add support for multiple interfaces per primary * add support for changing interfaces on the fly * changes to support multiple alfred interfaces: - bind alfred to a specific interface - allow configuring the unix socket path * enhanced debugging 2014.3.0 (2014-07-21) ===================== * fix various possible memleak, access errors and strncpy issues * handle fcntl return codes * fix altitude verification check in gpsd 2014.2.0 (2014-05-15) ===================== * Handle EPERM errors on every sendto * Check for changed interface properties, e.g. recreation or changed MAC- and IPv6 addresses 2014.1.0 (2014-03-13) ===================== * don't leak socket fd in batadv-vis 2014.0.0 (2014-01-04) ===================== * add installation of the alfred-gpsd manpage * add -lm to linker flags for libgps in alfred-gpsd 2013.4.0 (2013-10-13) ===================== * add new json output format for vis * add gps location information service for alfred * allow network interface to vanish and return without restart * allow to switch between primary and secondary operation without restart * renamed vis to batadv-vis to avoid collisions with other vis binaries * add manpages * various code cleanups * bugs squashed: - handle failing write() in unix sockets - Fix crash when vis opened empty file 2013.3.0 (2013-07-28) ===================== * initial release of alfred after beta (synced release cycle with batman-adv) * allows to share arbitrary local information over a (mesh) network * initial support for vis (previously in-kernel feature of batman-adv to visualize the network) included * easy but flexible communication interface to allow data applications of all kinds * two-tiered architecture (primary and secondaries) * exchanges data via IPv6 unicast/multicast alfred-2025.1/LICENSES/000077500000000000000000000000001500012154500142365ustar00rootroot00000000000000alfred-2025.1/LICENSES/preferred/000077500000000000000000000000001500012154500162145ustar00rootroot00000000000000alfred-2025.1/LICENSES/preferred/GPL-2.0000066400000000000000000000443061500012154500170650ustar00rootroot00000000000000Valid-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. alfred-2025.1/LICENSES/preferred/MIT000066400000000000000000000025341500012154500165740ustar00rootroot00000000000000Valid-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. alfred-2025.1/Makefile000077500000000000000000000100001500012154500144630ustar00rootroot00000000000000#!/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 # alfred build BINARY_NAME = alfred OBJ += batadv_query.o OBJ += batadv_querynl.o OBJ += client.o OBJ += hash.o OBJ += main.o OBJ += netlink.o OBJ += netsock.o OBJ += recv.o OBJ += send.o OBJ += server.o OBJ += unix_sock.o OBJ += util.o MANPAGE = man/alfred.8 # alfred flags and options CFLAGS += -pedantic -Wall -W -std=gnu99 -fno-strict-aliasing -MD -MP CPPFLAGS += -D_GNU_SOURCE LDLIBS += -lrt # Turn on alfred capability dropping by default - set this to n if you don't want/need it export CONFIG_ALFRED_CAPABILITIES=y # 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 # 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 ifneq ($(CONFIG_ALFRED_VIS),n) VIS_ALL=vis-all VIS_CLEAN=vis-clean VIS_INSTALL=vis-install endif ifneq ($(CONFIG_ALFRED_GPSD),n) GPSD_ALL=gpsd-all GPSD_CLEAN=gpsd-clean GPSD_INSTALL=gpsd-install 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) ifneq ($(CONFIG_ALFRED_CAPABILITIES),n) ifeq ($(origin LIBCAP_CFLAGS) $(origin LIBCAP_LDLIBS), undefined undefined) LIBCAP_NAME ?= libcap ifeq ($(shell $(PKG_CONFIG) --modversion $(LIBCAP_NAME) 2>/dev/null),) $(error No $(LIBCAP_NAME) development libraries found!) endif LIBCAP_CFLAGS += $(shell $(PKG_CONFIG) --cflags $(LIBCAP_NAME)) LIBCAP_LDLIBS += $(shell $(PKG_CONFIG) --libs $(LIBCAP_NAME)) endif CFLAGS += $(LIBCAP_CFLAGS) CPPFLAGS += -DCONFIG_ALFRED_CAPABILITIES LDLIBS += $(LIBCAP_LDLIBS) endif # default target all: $(BINARY_NAME) $(VIS_ALL) $(GPSD_ALL) # standard build rules .SUFFIXES: .o .c .c.o: $(COMPILE.c) -o $@ $< $(BINARY_NAME): $(OBJ) $(LINK.o) $^ $(LDLIBS) -o $@ clean: $(VIS_CLEAN) $(GPSD_CLEAN) $(RM) $(BINARY_NAME) $(OBJ) $(DEP) install: $(BINARY_NAME) $(VIS_INSTALL) $(GPSD_INSTALL) $(MKDIR) $(DESTDIR)$(SBINDIR) $(MKDIR) $(DESTDIR)$(MANDIR)/man8 $(INSTALL) -m 0755 $(BINARY_NAME) $(DESTDIR)$(SBINDIR) $(INSTALL) -m 0644 $(MANPAGE) $(DESTDIR)$(MANDIR)/man8 vis-install: $(MAKE) -C vis install vis-all: $(MAKE) -C vis all vis-clean: $(MAKE) -C vis clean gpsd-install: $(MAKE) -C gpsd install gpsd-all: $(MAKE) -C gpsd all gpsd-clean: $(MAKE) -C gpsd clean # load dependencies DEP = $(OBJ:.o=.d) -include $(DEP) .PHONY: all clean install vis-install vis-all vis-clean \ gpsd-install gpsd-all gpsd-clean alfred-2025.1/README.rst000066400000000000000000000323411500012154500145230ustar00rootroot00000000000000.. SPDX-License-Identifier: GPL-2.0 ============================================================== A.L.F.R.E.D - Almighty Lightweight Fact Remote Exchange Daemon ============================================================== "alfred is a user space daemon to efficiently[tm] flood the network with useless data - like vis, weather data, network notes, etc" - Marek Lindner, 2012 Introduction ============ alfred is a user space daemon for distributing arbitrary local information over the mesh/network in a decentralized fashion. This data can be anything which appears to be useful - originally designed to replace the batman-adv visualization (vis), you may distribute hostnames, phone books, administration information, DNS information, the local weather forecast ... alfred runs as daemon in the background of the system. A user may insert information by using the alfred binary on the command line, or use special programs to communicate with alfred (done via unix sockets). alfred then takes care of distributing the local information to other alfred servers on other nodes. This is done via IPv6 link-local multicast, and does not require any configuration. A user can request data from alfred, and will receive the information available from all alfred servers in the network. Alternatively, alfred can be configured to distribute the local information via IPv4 multicast. This is configured by setting the IPv4 multicast group address in the -4 option. Compilation =========== alfred depends on: * librt (usually part of libc) * IPv6 support in the kernel/host system * libnl-3 - support for netlink sockets * libnl-3-genl - support for generic netlink messages and optionally: * libgps - if you want to distribute GPS information * libcap - if you want extra security by dropping unneeded privileges To compile alfred, simply type:: $ make This will compile alfred, batadv-vis & alfred-gpsd. To install, use:: $ make install (with the right privileges). If you don't want to compile batadv-vis, add the directive CONFIG_ALFRED_VIS=n:: $ make CONFIG_ALFRED_VIS=n $ make CONFIG_ALFRED_VIS=n install If you don't want to compile alfred-gpsd, add the directive CONFIG_ALFRED_GPSD=n:: $ make CONFIG_ALFRED_GPSD=n $ make CONFIG_ALFRED_GPSD=n install If don't want to compile with libcap to drop privileges, use:: $ make CONFIG_ALFRED_CAPABILITIES=n $ make CONFIG_ALFRED_CAPABILITIES=n install Usage ===== First, alfred must run as daemon (server) in background to be used. This can either be done by some init-scripts from your distribution (if you have received alfred as a package with your distribution). Please see their documentation how to configure alfred in this case. In any event, you can still run alfred from the command line. The relevant options are (for a full list of options, run alfred -h): -i, --interface specify the interface to listen on. use 'none' to disable interface operations -b specify the batman-adv interface configured on the system (default: bat0). use 'none' to disable the batman-adv based best server selection -m, --primary start up the daemon in primary mode, which accepts data from secondaries and syncs it with other primaries The interface option '-i' is optional. If interface 'none' is specified, the alfred daemon will not communicate with other alfred instances on the network unless the interface list is modified at runtime via the unix socket. The -b option is optional, and only needed if you run alfred on a batman-adv interface not called bat0, or if you don't use batman-adv at all (use '-b none'). In this case, alfred will still work but will not be able to find the best next primary server based on metrics. alfred servers may either run as primary or secondary in the network. Primaries will announce their status via broadcast, so that secondaries can find them. Secondaries will then send their data to their nearest primary (based on TQ). Primaries will exchange their data (which they have received from secondaries or got on their own) with other primaries. By using primaries and secondaries, overhead can be reduced while still keeping redundancy (by having multiple primaries). Obviously, at least one primary must be present in the network to let any data exchange happen. Also having all nodes in primary mode is possible (for maximum decentrality and overhead). To put it together, let us start alfred in primary mode on our bridge br0 (assuming that this bridge includes the batman interface bat0):: $ alfred -i br0 -m Now that the server is running, let us input some data. This can be done by using the alfred binary in client mode from the command line:: $ cat /etc/hostname | alfred -s 64 This will set the hostname as data for datatype 64. Note that 0 - 63 are reserved (please send us an e-mail if you want to register a datatype), and can not be used on the commandline. We skipped the version parameter allowing you to assign a version to your data which can be filtered by other alfred users. Skipping the parameter entirely has the same effect as setting the parameter to 0 ('-V 0'). After the hostname has been set on a few alfred hosts, the can be retrieved again:: $ alfred -r 64 { "fe:f1:00:00:01:01", "OpenWRT-node-1\x0a" }, { "fe:f1:00:00:02:01", "OpenWRT-node-2\x0a" }, { "fe:f1:00:00:03:01", "OpenWRT-node-3\x0a" }, Note that the information must be periodically written again to alfred, otherwise it will timeout and alfred will forget about it (after 10 minutes). One final remark on terminology: If we talk about "servers" and "clients" in alfred, we mean the local processes on one machine which talk to each other via unix sockets (client connects and talks to servers). On the other hand, "secondaries" and "primaries" are the roles alfred can take over in the network between different machines (secondaries send information to primaries). Vis === batadv-vis can be used to visualize your batman-adv mesh network. It read the neighbor information and local client table and distributes this information via alfred in the network. By gathering this local information, any vis node can get the whole picture of the network. batadv-vis, similar to to alfred, combines server (daemon) and client functionality in the 'batadv-vis' binary. The batadv-vis server must be started to let batadv-vis work:: $ batadv-vis -i bat0 -s This server will read the neighbor and client information from batman-adv every 10 seconds and set it in alfred via unix socket. Obviously, the alfred server must run too to get this information set. To get a graphviz-compatible vis output, simply type:: $ batadv-vis digraph { subgraph "cluster_fe:f0:00:00:04:01" { "fe:f0:00:00:04:01" } "fe:f0:00:00:04:01" -> "fe:f0:00:00:05:01" [label="1.000"] "fe:f0:00:00:04:01" -> "fe:f0:00:00:03:01" [label="1.004"] "fe:f0:00:00:04:01" -> "00:00:43:05:00:04" [label="TT"] "fe:f0:00:00:04:01" -> "fe:f1:00:00:04:01" [label="TT"] subgraph "cluster_fe:f0:00:00:02:01" { "fe:f0:00:00:02:01" } "fe:f0:00:00:02:01" -> "fe:f0:00:00:03:01" [label="1.000"] "fe:f0:00:00:02:01" -> "fe:f0:00:00:01:01" [label="1.008"] "fe:f0:00:00:02:01" -> "fe:f0:00:00:08:01" [label="1.000"] "fe:f0:00:00:02:01" -> "fe:f1:00:00:02:01" [label="TT"] "fe:f0:00:00:02:01" -> "00:00:43:05:00:02" [label="TT"] subgraph "cluster_fe:f0:00:00:08:01" { "fe:f0:00:00:08:01" } [...] } For a json line formatted output, use:: $ batadv-vis -f json { "primary" : "fe:f0:00:00:04:01" } { "router" : "fe:f0:00:00:04:01", "neighbor" : "fe:f0:00:00:05:01", "label" : "1.000" } { "router" : "fe:f0:00:00:04:01", "neighbor" : "fe:f0:00:00:03:01", "label" : "1.008" } { "router" : "fe:f0:00:00:04:01", "gateway" : "00:00:43:05:00:04", "label" : "TT" } { "router" : "fe:f0:00:00:04:01", "gateway" : "fe:f1:00:00:04:01", "label" : "TT" } { "primary" : "fe:f0:00:00:02:01" } { "router" : "fe:f0:00:00:02:01", "neighbor" : "fe:f0:00:00:03:01", "label" : "1.000" } { "router" : "fe:f0:00:00:02:01", "neighbor" : "fe:f0:00:00:01:01", "label" : "1.016" } { "router" : "fe:f0:00:00:02:01", "neighbor" : "fe:f0:00:00:08:01", "label" : "1.000" } { "router" : "fe:f0:00:00:02:01", "gateway" : "fe:f1:00:00:02:01", "label" : "TT" } { "router" : "fe:f0:00:00:02:01", "gateway" : "00:00:43:05:00:02", "label" : "TT" } { "primary" : "fe:f0:00:00:08:01" } [...] and for output where the complete document is json, use:: $ batadv-vis -f jsondoc { "source_version" : "2013.3.0-14-gcd34783", "algorithm" : 4, "vis" : [ { "primary" : "fe:f0:00:00:04:01", "neighbors" : [ { "router" : "fe:f0:00:00:04:01", "neighbor" : "fe:f0:00:00:05:01", "metric" : "1.000" }, { "router" : "fe:f0:00:00:04:01", "neighbor" : "fe:f0:00:00:03:01", "metric" : "1.008" } ], "clients" : [ "00:00:43:05:00:04", "fe:f1:00:00:04:01" ] }, { "primary" : "fe:f0:00:00:02:01", "neighbors" : [ { "router" : "fe:f0:00:00:02:01", "neighbor" : "fe:f0:00:00:03:01", "metric" : "1.000" }, { "router" : "fe:f0:00:00:02:01", "neighbor" : "fe:f0:00:00:01:01", "metric" : "1.016" }, { "router" : "fe:f0:00:00:02:01", "neighbor" : "fe:f0:00:00:08:01", "metric" : "1.000" } ], "clients" : [ "fe:f1:00:00:02:01", "00:00:43:05:00:02" ] }, { "primary" : "fe:f0:00:00:08:01", [...] Alfred-gpsd =========== Alfred-gpsd can be used to distibute GPS location information about your batman-adv mesh network. This information could be, for example, combined with Vis to visualize your mesh topology with true geographic layout. For mobile or nomadic nodes, Alfred-gpsd, can get location information from gpsd. Alternatively, a static location can be passed on the command line, which is useful for static nodes without a GPS. Alfred-gpsd, similar to to alfred, combines server (daemon) and client functionality in the 'alfred-gpsd' binary. The alfred-gpsd server must be started to distribute location information. When retrieving location information from gpsd, it should be started with:: $ alfred-gpsd -s For a static location, use:: $ alfred-gpsd -s -l 48.858222,2.2945,358 This server will set the location in alfred via unix socket. Obviously, the alfred server must run too to get this information set. When using gpsd, it updates alfred every 2 seconds. With a static location, the update it made every 5 minutes. To get JSON formatted output, use:: $ alfred-gpsd [ { "source" : "f6:00:48:13:d3:1e", "tpv" : {"class":"TPV","tag":"RMC","device":"/dev/ttyACM0","mode":3,"time":"2013-10-01T10:43:20.000Z","ept":0.005,"lat":52.575485000,"lon":-1.339716667,"alt":122.500,"epx":10.199,"epy":15.720,"epv":31.050,"track":0.0000,"speed":0.010,"climb":0.000,"eps":31.44} }, { "source" : "8e:4c:77:b3:65:b4", "tpv" : {"class":"TPV","device":"command line","time":"2013-10-01T10:43:05.129Z","lat":48.858222,"lon":2.2945,"alt":358.000000,"mode":3} } ] See gpsd_json(5) for documentation of the tpv object. Running alfred as non-root user =============================== Alfred currently requires special capabilities and access rights to work correctly. The user root is normally the only user having these capabilities/rights on a standard Linux system. Operations requiring special capabilities: * bind to device * creating the unix socket * accessing the netlink interface The first operation can still be executed when the admin grants the special capability CAP_NET_RAW+CAP_NET_ADMIN to anyone executing the alfred binary. The unix socket can also be moved using the parameter '-u' to a different directory which can be accessed by the user:: $ sudo setcap cap_net_admin,cap_net_raw+ep alfred $ ./alfred -u alfred.sock -i eth0 License ======= alfred, batadv-vis and alfred-gpsd are licensed under the terms of version 2 of the GNU General Public License (GPL). Please see the LICENSE file. The file "packet.h" is an exception and not licensed with the GPL. Instead, it is licensed using ISC license (see the head of this file). This allows programs to include this header file (e.g. for communicating with alfred via unix sockets) without enforcing the restrions of the GPL license on this third party program. Contact ======= As alfred was developed to help on batman-adv, we share communication channels. Please send us comments, experiences, questions, anything :) IRC: #batadv on ircs://irc.hackint.org/ Mailing-list: b.a.t.m.a.n@lists.open-mesh.org (optional subscription at https://lists.open-mesh.org/mailman3/postorius/lists/b.a.t.m.a.n.lists.open-mesh.org/) If you have test reports/patches/ideas, please read the wiki for further instruction on how to contribute: https://www.open-mesh.org/projects/open-mesh/wiki/Contribute You can also contact the Authors: * Marek Lindner * Simon Wunderlich alfred-2025.1/alfred.h000066400000000000000000000136041500012154500144430ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) B.A.T.M.A.N. contributors: * * Simon Wunderlich * * License-Filename: LICENSES/preferred/GPL-2.0 */ #ifndef SOURCE_VERSION #define SOURCE_VERSION "2025.1" #endif #include #include #include #include #include #include #include #include #include "bitops.h" #include "epoll_handle.h" #include "list.h" #include "packet.h" #define ALFRED_INTERVAL 10 #define ALFRED_IF_CHECK_INTERVAL 60 #define ALFRED_REQUEST_TIMEOUT 10 #define ALFRED_SERVER_TIMEOUT 60 #define ALFRED_DATA_TIMEOUT 600 #define ALFRED_SOCK_PATH_DEFAULT "/var/run/alfred.sock" #define NO_FILTER -1 #ifndef __unused #define __unused __attribute__((unused)) #endif #define FIXED_TLV_LEN(__tlv_type) \ htons(sizeof(__tlv_type) - sizeof((__tlv_type).header)) enum data_source { SOURCE_LOCAL = 0, SOURCE_FIRST_HAND = 1, SOURCE_SYNCED = 2, }; typedef union { struct in_addr ipv4; struct in6_addr ipv6; } alfred_addr; struct dataset { struct alfred_data data; unsigned char *buf; struct timespec last_seen; enum data_source data_source; uint8_t local_data; }; struct changed_data_type { uint8_t data_type; struct list_head list; }; struct transaction_packet { struct alfred_push_data_v0 *push; struct list_head list; }; struct transaction_head { struct ether_addr server_addr; uint16_t id; uint8_t requested_type; uint16_t txend_packets; int num_packet; int client_socket; struct timespec last_rx_time; struct list_head packet_list; }; struct server { struct ether_addr hwaddr; alfred_addr address; struct timespec last_seen; uint8_t tq; }; enum opmode { OPMODE_SECONDARY, OPMODE_PRIMARY, }; enum clientmode { CLIENT_NONE, CLIENT_REQUEST_DATA, CLIENT_SET_DATA, CLIENT_MODESWITCH, CLIENT_CHANGE_INTERFACE, CLIENT_CHANGE_BAT_IFACE, CLIENT_SERVER_STATUS, CLIENT_EVENT_MONITOR, }; struct interface { struct ether_addr hwaddr; alfred_addr address; uint32_t scope_id; char *interface; int netsock; int netsock_mcast; struct epoll_handle netsock_epoll; struct epoll_handle netsock_mcast_epoll; struct hashtable_t *server_hash; struct list_head list; }; struct event_listener { int fd; struct epoll_handle epoll; struct list_head list; }; struct globals { struct list_head interfaces; struct list_head event_listeners; char *net_iface; struct server *best_server; /* NULL if we are a server ourselves */ char *mesh_iface; enum opmode opmode; enum clientmode clientmode; int clientmode_arg; int clientmode_version; uint8_t verbose:1; uint8_t ipv4mode:1; uint8_t force:1; int epollfd; int check_timerfd; struct epoll_handle check_epoll; int unix_sock; struct epoll_handle unix_epoll; const char *unix_path; const char *update_command; DECLARE_BITMAP(changed_data_types, ALFRED_NUM_TYPES); struct timespec if_check; struct timespec sync_period; struct hashtable_t *data_hash; struct hashtable_t *transaction_hash; }; #define debugMalloc(size, num) malloc(size) #define debugFree(ptr, num) free(ptr) #define BUILD_BUG_ON(e) ((void)sizeof(char[1 - 2 * !!(e)])) #define MAX_PAYLOAD ((1 << 16) - 1 - sizeof(struct udphdr)) extern alfred_addr alfred_mcast; /* server.c */ int alfred_server(struct globals *globals); void changed_data_type(struct globals *globals, uint8_t arg); /* client.c */ int alfred_client_request_data(struct globals *globals); int alfred_client_set_data(struct globals *globals); int alfred_client_modeswitch(struct globals *globals); int alfred_client_change_interface(struct globals *globals); int alfred_client_change_bat_iface(struct globals *globals); int alfred_client_server_status(struct globals *globals); int alfred_client_event_monitor(struct globals *globals); /* recv.c */ int recv_alfred_packet(struct globals *globals, struct interface *interface, int recv_sock); struct transaction_head * transaction_add(struct globals *globals, struct ether_addr mac, uint16_t id); struct transaction_head *transaction_clean(struct globals *globals, struct transaction_head *head); static inline bool transaction_finished(struct transaction_head *head) { return head->txend_packets == head->num_packet; } /* send.c */ int push_data(struct globals *globals, struct interface *interface, alfred_addr *destination, enum data_source max_source_level, int type_filter, uint16_t tx_id); int announce_primary(struct globals *globals); int push_local_data(struct globals *globals); int sync_data(struct globals *globals); ssize_t send_alfred_packet(struct globals *globals, struct interface *interface, const alfred_addr *dest, void *buf, int length); /* unix_sock.c */ int unix_sock_open_daemon(struct globals *globals); int unix_sock_open_client(struct globals *globals); int unix_sock_close(struct globals *globals); int unix_sock_req_data_finish(struct globals *globals, struct transaction_head *head); void unix_sock_events_close_all(struct globals *globals); void unix_sock_event_notify(struct globals *globals, uint8_t type, const uint8_t source[ETH_ALEN]); /* vis.c */ int vis_update_data(struct globals *globals); /* netsock.c */ int netsock_open_all(struct globals *globals); size_t netsocket_count_interfaces(struct globals *globals); void netsock_close_all(struct globals *globals); int netsock_set_interfaces(struct globals *globals, char *interfaces); struct interface *netsock_first_interface(struct globals *globals); void netsock_reopen(struct globals *globals); int netsock_own_address(const struct globals *globals, const alfred_addr *address); /* util.c */ int time_diff(struct timespec *tv1, struct timespec *tv2, struct timespec *tvdiff); void time_random_seed(void); uint16_t get_random_id(void); bool is_valid_ether_addr(uint8_t addr[ETH_ALEN]); bool is_iface_disabled(char *iface); int ipv4_arp_request(struct interface *interface, const alfred_addr *addr, struct ether_addr *mac); alfred-2025.1/batadv_query.c000066400000000000000000000132601500012154500156650ustar00rootroot00000000000000// 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 "alfred.h" #include "batadv_query.h" #include #include #include #include #include #include #include #include #ifdef CONFIG_ALFRED_CAPABILITIES #include #endif #include #include "batadv_querynl.h" static int enable_net_admin_capability(int enable) { int ret = 0; #ifdef CONFIG_ALFRED_CAPABILITIES cap_t cap_cur; cap_flag_value_t cap_flag; cap_value_t cap_net_admin = CAP_NET_ADMIN; if (enable) cap_flag = CAP_SET; else cap_flag = CAP_CLEAR; cap_cur = cap_get_proc(); if (!cap_cur) { perror("cap_get_proc"); return -1; } ret = cap_set_flag(cap_cur, CAP_EFFECTIVE, 1, &cap_net_admin, cap_flag); if (ret < 0) { perror("cap_set_flag"); goto out; } ret = cap_set_proc(cap_cur); if (ret < 0) { perror("cap_set_proc"); goto out; } out: cap_free(cap_cur); #endif return ret; } int mac_to_ipv6(const struct ether_addr *mac, alfred_addr *addr) { memset(addr, 0, sizeof(*addr)); addr->ipv6.s6_addr[0] = 0xfe; addr->ipv6.s6_addr[1] = 0x80; addr->ipv6.s6_addr[8] = mac->ether_addr_octet[0] ^ 0x02; addr->ipv6.s6_addr[9] = mac->ether_addr_octet[1]; addr->ipv6.s6_addr[10] = mac->ether_addr_octet[2]; addr->ipv6.s6_addr[11] = 0xff; addr->ipv6.s6_addr[12] = 0xfe; addr->ipv6.s6_addr[13] = mac->ether_addr_octet[3]; addr->ipv6.s6_addr[14] = mac->ether_addr_octet[4]; addr->ipv6.s6_addr[15] = mac->ether_addr_octet[5]; return 0; } int is_ipv6_eui64(const struct in6_addr *addr) { size_t i; for (i = 2; i < 8; i++) { if (addr->s6_addr[i] != 0x0) return 0; } if (addr->s6_addr[0] != 0xfe || addr->s6_addr[1] != 0x80 || addr->s6_addr[11] != 0xff || addr->s6_addr[12] != 0xfe) return 0; return 1; } int ipv6_to_mac(const alfred_addr *addr, struct ether_addr *mac) { if (!is_ipv6_eui64(&addr->ipv6)) return -EINVAL; mac->ether_addr_octet[0] = addr->ipv6.s6_addr[8] ^ 0x02; mac->ether_addr_octet[1] = addr->ipv6.s6_addr[9]; mac->ether_addr_octet[2] = addr->ipv6.s6_addr[10]; mac->ether_addr_octet[3] = addr->ipv6.s6_addr[13]; mac->ether_addr_octet[4] = addr->ipv6.s6_addr[14]; mac->ether_addr_octet[5] = addr->ipv6.s6_addr[15]; if (!is_valid_ether_addr(mac->ether_addr_octet)) return -EINVAL; return 0; } int ipv4_to_mac(struct interface *interface, const alfred_addr *addr, struct ether_addr *mac) { if (ipv4_arp_request(interface, addr, mac) < 0) return -EINVAL; if (!is_valid_ether_addr(mac->ether_addr_octet)) return -EINVAL; return 0; } int batadv_interface_check(const char *mesh_iface) { int ret; enable_net_admin_capability(1); ret = batadv_interface_check_netlink(mesh_iface); enable_net_admin_capability(0); return ret; } static int tg_compare(void *d1, void *d2) { struct tg_entry *s1 = d1, *s2 = d2; if (memcmp(&s1->mac, &s2->mac, sizeof(s1->mac)) == 0) return 1; else return 0; } static int tg_choose(void *d1, int size) { struct tg_entry *s1 = d1; uint32_t hash = 0; size_t i; for (i = 0; i < sizeof(s1->mac); i++) { hash += s1->mac.ether_addr_octet[i]; hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash % size; } struct hashtable_t *tg_hash_new(const char *mesh_iface) { struct hashtable_t *tg_hash; tg_hash = hash_new(64, tg_compare, tg_choose); if (!tg_hash) return NULL; enable_net_admin_capability(1); translate_mac_netlink(mesh_iface, tg_hash); enable_net_admin_capability(0); return tg_hash; } void tg_hash_free(struct hashtable_t *tg_hash) { hash_delete(tg_hash, free); } int tg_hash_add(struct hashtable_t *tg_hash, struct ether_addr *mac, struct ether_addr *originator) { struct tg_entry *n; n = malloc(sizeof(*n)); if (!n) return -ENOMEM; n->mac = *mac; n->originator = *originator; if (hash_add(tg_hash, n)) { free(n); return -EEXIST; } return 0; } struct ether_addr *translate_mac(struct hashtable_t *tg_hash, const struct ether_addr *mac) { struct tg_entry search = { .mac = *mac, }; struct tg_entry *found; found = hash_find(tg_hash, &search); if (!found) return 0; return &found->originator; } static int orig_compare(void *d1, void *d2) { struct orig_entry *s1 = d1, *s2 = d2; if (memcmp(&s1->mac, &s2->mac, sizeof(s1->mac)) == 0) return 1; else return 0; } static int orig_choose(void *d1, int size) { struct orig_entry *s1 = d1; uint32_t hash = 0; size_t i; for (i = 0; i < sizeof(s1->mac); i++) { hash += s1->mac.ether_addr_octet[i]; hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash % size; } struct hashtable_t *orig_hash_new(const char *mesh_iface) { struct hashtable_t *orig_hash; orig_hash = hash_new(64, orig_compare, orig_choose); if (!orig_hash) return NULL; enable_net_admin_capability(1); get_tq_netlink(mesh_iface, orig_hash); enable_net_admin_capability(0); return orig_hash; } void orig_hash_free(struct hashtable_t *orig_hash) { hash_delete(orig_hash, free); } int orig_hash_add(struct hashtable_t *orig_hash, struct ether_addr *mac, uint8_t tq) { struct orig_entry *n; n = malloc(sizeof(*n)); if (!n) return -ENOMEM; n->mac = *mac; n->tq = tq; if (hash_add(orig_hash, n)) { free(n); return -EEXIST; } return 0; } uint8_t get_tq(struct hashtable_t *orig_hash, struct ether_addr *mac) { struct orig_entry search = { .mac = *mac, .tq = 0, }; struct orig_entry *found; found = hash_find(orig_hash, &search); if (!found) return 0; return found->tq; } alfred-2025.1/batadv_query.h000066400000000000000000000024771500012154500157020ustar00rootroot00000000000000/* 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 _BATADV_QUERY_H #define _BATADV_QUERY_H #include #include #include "hash.h" struct orig_entry { struct ether_addr mac; uint8_t tq; }; struct tg_entry { struct ether_addr mac; struct ether_addr originator; }; struct hashtable_t *tg_hash_new(const char *mesh_iface); void tg_hash_free(struct hashtable_t *tg_hash); int tg_hash_add(struct hashtable_t *tg_hash, struct ether_addr *mac, struct ether_addr *originator); struct ether_addr *translate_mac(struct hashtable_t *tg_hash, const struct ether_addr *mac); struct hashtable_t *orig_hash_new(const char *mesh_iface); void orig_hash_free(struct hashtable_t *orig_hash); int orig_hash_add(struct hashtable_t *orig_hash, struct ether_addr *mac, uint8_t tq); uint8_t get_tq(struct hashtable_t *orig_hash, struct ether_addr *mac); int batadv_interface_check(const char *mesh_iface); int mac_to_ipv6(const struct ether_addr *mac, alfred_addr *addr); int ipv6_to_mac(const alfred_addr *addr, struct ether_addr *mac); int ipv4_to_mac(struct interface *interface, const alfred_addr *addr, struct ether_addr *mac); int is_ipv6_eui64(const struct in6_addr *addr); #endif alfred-2025.1/batadv_querynl.c000066400000000000000000000106241500012154500162200ustar00rootroot00000000000000// 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 "batadv_querynl.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "alfred.h" #include "batman_adv.h" #include "batadv_query.h" #include "netlink.h" static const int translate_mac_netlink_mandatory[] = { BATADV_ATTR_TT_ADDRESS, BATADV_ATTR_ORIG_ADDRESS, }; struct translate_mac_netlink_opts { struct hashtable_t *tg_hash; 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 nlmsghdr *nlh = nlmsg_hdr(msg); struct nlquery_opts *query_opts = arg; struct translate_mac_netlink_opts *opts; struct genlmsghdr *ghdr; struct ether_addr mac_addr; struct ether_addr mac_orig; 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; memcpy(&mac_addr, addr, sizeof(mac_addr)); memcpy(&mac_orig, orig, sizeof(mac_orig)); tg_hash_add(opts->tg_hash, &mac_addr, &mac_orig); opts->query_opts.err = 0; return NL_STOP; } int translate_mac_netlink(const char *mesh_iface, struct hashtable_t *tg_hash) { struct translate_mac_netlink_opts opts = { .tg_hash = tg_hash, .query_opts = { .err = 0, }, }; int ret; ret = netlink_query_common(mesh_iface, BATADV_CMD_GET_TRANSTABLE_GLOBAL, translate_mac_netlink_cb, &opts.query_opts); if (ret < 0) return ret; return 0; } static const int get_tq_netlink_mandatory[] = { BATADV_ATTR_ORIG_ADDRESS, BATADV_ATTR_TQ, }; struct get_tq_netlink_opts { struct hashtable_t *orig_hash; struct nlquery_opts query_opts; }; static int get_tq_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_tq_netlink_opts *opts; struct genlmsghdr *ghdr; uint8_t *orig; struct ether_addr mac; uint8_t tq; opts = container_of(query_opts, struct get_tq_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_tq_netlink_mandatory, ARRAY_SIZE(get_tq_netlink_mandatory))) return NL_OK; orig = nla_data(attrs[BATADV_ATTR_ORIG_ADDRESS]); tq = nla_get_u8(attrs[BATADV_ATTR_TQ]); if (!attrs[BATADV_ATTR_FLAG_BEST]) return NL_OK; memcpy(&mac, orig, sizeof(mac)); orig_hash_add(opts->orig_hash, &mac, tq); opts->query_opts.err = 0; return NL_OK; } int get_tq_netlink(const char *mesh_iface, struct hashtable_t *orig_hash) { struct get_tq_netlink_opts opts = { .orig_hash = orig_hash, .query_opts = { .err = 0, }, }; int ret; ret = netlink_query_common(mesh_iface, BATADV_CMD_GET_ORIGINATORS, get_tq_netlink_cb, &opts.query_opts); if (ret < 0) return ret; return 0; } static int check_nlcmd_cb(struct nl_msg *msg __unused, void *arg __unused) { return NL_STOP; } int batadv_interface_check_netlink(const char *mesh_iface) { struct nlquery_opts opts = { .err = 0, }; int ret; ret = netlink_query_common(mesh_iface, BATADV_CMD_GET_ORIGINATORS, check_nlcmd_cb, &opts); if (ret < 0) return ret; ret = netlink_query_common(mesh_iface, BATADV_CMD_GET_TRANSTABLE_GLOBAL, check_nlcmd_cb, &opts); if (ret < 0) return ret; return 0; } alfred-2025.1/batadv_querynl.h000066400000000000000000000011021500012154500162140ustar00rootroot00000000000000/* 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 _BATADV_QUERYNL_H #define _BATADV_QUERYNL_H #include struct ether_addr; struct hashtable_t; int translate_mac_netlink(const char *mesh_iface, struct hashtable_t *tg_hash); int get_tq_netlink(const char *mesh_iface, struct hashtable_t *orig_hash); int batadv_interface_check_netlink(const char *mesh_iface); #endif /* _BATADV_QUERYNL_H */ alfred-2025.1/batman_adv.h000066400000000000000000000410101500012154500152720ustar00rootroot00000000000000/* 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_ */ alfred-2025.1/bitops.h000066400000000000000000000205701500012154500145060ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Minimal Linux-like bit manipulation helper functions * (reduced version for alfred) * * Copyright (c) Sven Eckelmann * * License-Filename: LICENSES/preferred/MIT */ #ifndef __LINUX_LIKE_BITOPS_H__ #define __LINUX_LIKE_BITOPS_H__ #ifdef __cplusplus extern "C" { #endif #include #include #include #include #if defined(__GNUC__) #define BITOPS_BUILTIN_USE 1 #endif #if defined(_MSC_VER) #define __inline__ __inline #endif /** * BITOPS_BUILD_BUG_ON - create "negative array" build error on expression * @e: expression which is considered to be a "bug" */ #define BITOPS_BUILD_BUG_ON(e) ((void)sizeof(char[1 - 2 * !!(e)])) /** * BITOPS_DIV_CEIL - calculate quotient of integer division (round up) * @numerator: side effect free expression for numerator of division * @denominator: side effect free expression for denominator of division * * numerator and denominator must be from a type which can store * denominator + numerator without overflow. denominator must be larger than 0 * and numerator must be positive. * * WARNING @numerator expression must be side-effect free */ #define BITOPS_DIV_CEIL(numerator, denominator) \ (((numerator) + (denominator) - 1) / (denominator)) /** * BITS_PER_BYTE - number of bits per byte/char */ #define BITS_PER_BYTE 8 /** * BITS_PER_LONG - number of bits per long */ #define BITS_PER_LONG (sizeof(unsigned long) * BITS_PER_BYTE) /** * BITS_TO_LONGS - return number of longs to save at least bit 0..(bits - 1) * @bits: number of required bits */ #define BITS_TO_LONGS(bits) \ BITOPS_DIV_CEIL(bits, BITS_PER_LONG) /** * DECLARE_BITMAP - declare bitmap to store at least bit 0..(bits -1) * @bitmap: name for the new bitmap * @bits: number of required bits */ #define DECLARE_BITMAP(bitmap, bits) \ unsigned long bitmap[BITS_TO_LONGS(bits)] /** * BITMAP_FIRST_WORD_MASK - return unsigned long mask for least significant long * @start: offset to first bits * * All bits which can be modified in the least significant unsigned long for * offset @start in the bitmap will be set to 1. All other bits will be set to * zero */ #define BITMAP_FIRST_WORD_MASK(start) (~0UL << ((start) % BITS_PER_LONG)) /** * BITMAP_LAST_WORD_MASK - return unsigned long mask for most significant long * @bits: number of bits in complete bitmap * * All bits which can be modified in the most significant unsigned long in the * bitmap will be set to 1. All other bits will be set to zero */ #define BITMAP_LAST_WORD_MASK(bits) (~0UL >> (-(bits) % BITS_PER_LONG)) /** * bitops_ffs() - find (least significant) first set bit plus one * @x: unsigned long to check * * Return: plus-one index of first set bit; zero when x is zero */ static __inline__ size_t bitops_ffs(unsigned long x) { #ifdef BITOPS_BUILTIN_USE return __builtin_ffsl(x); #else size_t i = 1; BITOPS_BUILD_BUG_ON(BITS_PER_LONG != 32 && BITS_PER_LONG != 64); if (x == 0) return 0; if (BITS_PER_LONG == 64) { if ((0x00000000fffffffful & x) == 0) { i += 32; x >>= 32; } } if ((0x0000fffful & x) == 0) { i += 16; x >>= 16; } if ((0x00fful & x) == 0) { i += 8; x >>= 8; } if ((0x0ful & x) == 0) { i += 4; x >>= 4; } if ((0x3ul & x) == 0) { i += 2; x >>= 2; } if ((0x1ul & x) == 0) { i += 1; x >>= 1; } return i; #endif } /** * hweight32() - number of set bits in an uint32_t * @x: uint32_t to sum up * * Return: number of set bits */ static __inline__ unsigned int hweight32(uint32_t x) { static const uint32_t m1 = UINT32_C(0x55555555); static const uint32_t m2 = UINT32_C(0x33333333); static const uint32_t m4 = UINT32_C(0x0f0f0f0f); /* x = (x & m1) + ((x >> 1) & m1); */ x -= (x >> 1) & m1; x = (x & m2) + ((x >> 2) & m2); x = (x + (x >> 4)) & m4; x += x >> 8; x += x >> 16; return x & 0x3f; } /** * hweight64() - number of set bits in an uint64_t * @x: uint64_t to sum up * * Return: number of set bits */ static __inline__ unsigned int hweight64(uint64_t x) { if (BITS_PER_LONG >= 64) { static const uint64_t m1 = UINT64_C(0x5555555555555555); static const uint64_t m2 = UINT64_C(0x3333333333333333); static const uint64_t m4 = UINT64_C(0x0f0f0f0f0f0f0f0f); /* x = (x & m1) + ((x >> 1) & m1); */ x -= (x >> 1) & m1; x = (x & m2) + ((x >> 2) & m2); x = (x + (x >> 4)) & m4; x += x >> 8; x += x >> 16; x += x >> 32; return x & 0x7f; } else { return hweight32((uint32_t)x) + hweight32((uint32_t)(x >> 32)); } } /** * hweight_long() - number of set bits in an unsigned long * @x: unsigned long to sum up * * Return: number of set bits */ static __inline__ unsigned int hweight_long(unsigned long x) { #ifdef BITOPS_BUILTIN_USE return __builtin_popcountl(x); #else size_t i; if (BITS_PER_LONG == 64) return hweight64((uint64_t)x); if (BITS_PER_LONG == 32) return hweight32((uint32_t)x); for (i = 0; x; i++) x &= x - 1; return i; #endif } /** * bitmap_zero() - Initializes bitmap with zero * @bitmap: bitmap to modify * @bits: number of bits * * Initializes all bits to zero. This also includes the overhead bits in the * last unsigned long which will not be used. */ static __inline__ void bitmap_zero(unsigned long *bitmap, size_t bits) { memset(bitmap, 0, BITS_TO_LONGS(bits) * sizeof(unsigned long)); } /** * set_bit() - Set bit in bitmap to one * @bit: address of bit to modify * @bitmap: bitmap to modify */ static __inline__ void set_bit(size_t bit, unsigned long *bitmap) { size_t l = bit / BITS_PER_LONG; size_t b = bit % BITS_PER_LONG; bitmap[l] |= 1UL << b; } /** * find_next_bit() - Find next set bit in bitmap * @bitmap: bitmap to check * @bits: number of bits in @bitmap * @start: start of bits to check * * Checks the modifiable bits in the bitmap. The overhead bits in the last * unsigned long will not be checked * * Return: bit position of next set bit, @bits when no set bit was found */ static __inline__ size_t find_next_bit(unsigned long *bitmap, size_t bits, size_t start) { size_t i; size_t pos; unsigned long t; size_t l = BITS_TO_LONGS(bits); size_t first_long = start / BITS_PER_LONG; size_t long_lower = start - (start % BITS_PER_LONG); if (start >= bits) return bits; t = bitmap[first_long] & BITMAP_FIRST_WORD_MASK(start); for (i = first_long + 1; !t && i < l; i++) { /* search until valid t is found */ long_lower += BITS_PER_LONG; t = bitmap[i]; } if (!t) return bits; pos = long_lower + bitops_ffs(t) - 1; if (pos >= bits) return bits; return pos; } /** * find_first_bit - Find first set bit in bitmap * @bitmap: bitmap to check * @bits: number of bits in @bitmap * * Checks the modifiable bits in the bitmap. The overhead bits in the last * unsigned long will not be checked * * Return: bit position of fist set bit, @bits when no set bit was found */ #define find_first_bit(bitmap, bits) find_next_bit(bitmap, bits, 0) /** * for_each_set_bit - iterate over set bits in bitmap * @bit: current bit * @bitmap: bitmap to iterate over * @bits: number of bits in @bitmap * * WARNING expressions @bitmap and @bits must be side-effect free */ #define for_each_set_bit(bit, bitmap, bits) \ for (bit = find_first_bit(bitmap, bits); \ bit < (bits); \ bit = find_next_bit(bitmap, bits, bit + 1)) /** * bitmap_weight() - Calculate number of set bits in bitmap * @bitmap: bitmap to sum up * @bits: number of bits * * Sums the modifiable bits in the bitmap. The overhead bits in the last * unsigned long will not summed up * * Return: number of set bits */ static __inline__ size_t bitmap_weight(const unsigned long *bitmap, size_t bits) { size_t l = BITS_TO_LONGS(bits); size_t i; size_t sum = 0; for (i = 0; i < l - 1; i++) sum += hweight_long(bitmap[i]); return sum + hweight_long(bitmap[l - 1] & BITMAP_LAST_WORD_MASK(bits)); } /** * bitmap_empty() - Check if no bit is set in bitmap * @bitmap: bitmap to test * @bits: number of bits * * Check the modifiable bits in the bitmap for zero. The overhead bits in the * last unsigned long will not be checked * * Return: true when usable bits were all zero and false otherwise */ static __inline__ bool bitmap_empty(const unsigned long *bitmap, size_t bits) { size_t l = BITS_TO_LONGS(bits); size_t i; for (i = 0; i < l - 1; i++) { if (bitmap[i]) return false; } return !(bitmap[l - 1] & BITMAP_LAST_WORD_MASK(bits)); } #ifdef __cplusplus } #endif #endif /* __LINUX_LIKE_BITOPS_H__ */ alfred-2025.1/client.c000066400000000000000000000310251500012154500144540ustar00rootroot00000000000000// 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 #include #include #include #include "alfred.h" #include "packet.h" int alfred_client_request_data(struct globals *globals) { unsigned char buf[MAX_PAYLOAD], *pos; struct alfred_request_v0 request; struct alfred_push_data_v0 *push; struct alfred_status_v0 *status; struct alfred_tlv *tlv; struct alfred_data *data; int ret, len, data_len, i; const size_t buf_data_len = sizeof(buf) - sizeof(*push) - sizeof(*data); if (unix_sock_open_client(globals)) return -1; len = sizeof(request); request.header.type = ALFRED_REQUEST; request.header.version = ALFRED_VERSION; request.header.length = FIXED_TLV_LEN(request); request.requested_type = globals->clientmode_arg; request.tx_id = get_random_id(); ret = write(globals->unix_sock, &request, len); if (ret != len) fprintf(stderr, "%s: only wrote %d of %d bytes: %s\n", __func__, ret, len, strerror(errno)); push = (struct alfred_push_data_v0 *)buf; tlv = (struct alfred_tlv *)buf; while ((ret = read(globals->unix_sock, buf, sizeof(*tlv))) > 0) { if (ret < (int)sizeof(*tlv)) break; if (tlv->type == ALFRED_STATUS_ERROR) goto recv_err; if (tlv->type != ALFRED_PUSH_DATA) break; /* read the rest of the header */ ret = read(globals->unix_sock, buf + sizeof(*tlv), sizeof(*push) - sizeof(*tlv)); /* too short */ if (ret < (int)(sizeof(*push) - (int)sizeof(*tlv))) break; /* read the rest of the header */ ret = read(globals->unix_sock, buf + sizeof(*push), sizeof(*data)); if (ret < (ssize_t)sizeof(*data)) break; data = push->data; data_len = ntohs(data->header.length); /* would it fit? it should! */ if (data_len > (int)buf_data_len) break; /* read the data */ ret = read(globals->unix_sock, buf + sizeof(*push) + sizeof(*data), data_len); /* again too short */ if (ret < data_len) break; pos = data->data; printf("{ \"%02x:%02x:%02x:%02x:%02x:%02x\", \"", data->source[0], data->source[1], data->source[2], data->source[3], data->source[4], data->source[5]); for (i = 0; i < data_len; i++) { if (pos[i] == '"') printf("\\\""); else if (pos[i] == '\\') printf("\\\\"); else if (!isprint(pos[i])) printf("\\x%02x", pos[i]); else printf("%c", pos[i]); } printf("\""); if (globals->verbose) printf(", %u", data->header.version); printf(" },\n"); } unix_sock_close(globals); return 0; recv_err: /* read the rest of the status message */ ret = read(globals->unix_sock, buf + sizeof(*tlv), sizeof(*status) - sizeof(*tlv)); /* too short */ if (ret < (int)(sizeof(*status) - sizeof(*tlv))) return -1; status = (struct alfred_status_v0 *)buf; fprintf(stderr, "Request failed with %d\n", status->tx.seqno); return status->tx.seqno; } int alfred_client_set_data(struct globals *globals) { unsigned char buf[MAX_PAYLOAD]; struct alfred_push_data_v0 *push; struct alfred_data *data; int ret, len; if (unix_sock_open_client(globals)) return -1; push = (struct alfred_push_data_v0 *)buf; data = push->data; len = sizeof(*push) + sizeof(*data); while (!feof(stdin)) { ret = fread(&buf[len], 1, sizeof(buf) - len, stdin); len += ret; if (sizeof(buf) == len) break; } push->header.type = ALFRED_PUSH_DATA; push->header.version = ALFRED_VERSION; push->header.length = htons(len - sizeof(push->header)); push->tx.id = get_random_id(); push->tx.seqno = htons(0); /* we leave data->source "empty" */ memset(data->source, 0, sizeof(data->source)); data->header.type = globals->clientmode_arg; data->header.version = globals->clientmode_version; data->header.length = htons(len - sizeof(*push) - sizeof(*data)); ret = write(globals->unix_sock, buf, len); if (ret != len) fprintf(stderr, "%s: only wrote %d of %d bytes: %s\n", __func__, ret, len, strerror(errno)); unix_sock_close(globals); return 0; } int alfred_client_modeswitch(struct globals *globals) { struct alfred_modeswitch_v0 modeswitch; int ret, len; if (unix_sock_open_client(globals)) return -1; len = sizeof(modeswitch); modeswitch.header.type = ALFRED_MODESWITCH; modeswitch.header.version = ALFRED_VERSION; modeswitch.header.length = FIXED_TLV_LEN(modeswitch); switch (globals->opmode) { case OPMODE_SECONDARY: modeswitch.mode = ALFRED_MODESWITCH_SECONDARY; break; case OPMODE_PRIMARY: modeswitch.mode = ALFRED_MODESWITCH_PRIMARY; break; default: fprintf(stderr, "%s: unknown opmode %u in modeswitch\n", __func__, globals->opmode); return -1; } ret = write(globals->unix_sock, &modeswitch, len); if (ret != len) fprintf(stderr, "%s: only wrote %d of %d bytes: %s\n", __func__, ret, len, strerror(errno)); unix_sock_close(globals); return 0; } static int check_interface(const char *iface) { int sock = -1; struct ifreq ifr; if (strlen(iface) > IFNAMSIZ) { fprintf(stderr, "%s: interface name list too long, not changing\n", __func__); return -1; } sock = socket(PF_INET, SOCK_DGRAM, 0); if (sock < 0) { perror("can't open socket"); return -1; } strncpy(ifr.ifr_name, iface, IFNAMSIZ); ifr.ifr_name[IFNAMSIZ - 1] = '\0'; if (ioctl(sock, SIOCGIFINDEX, &ifr) == -1) { fprintf(stderr, "%s: can't find interface, not changing\n", __func__); close(sock); return -1; } close(sock); return 0; } int alfred_client_change_interface(struct globals *globals) { struct alfred_change_interface_v0 change_interface; int ret, len; char *input, *token, *saveptr; size_t interface_len; if (unix_sock_open_client(globals)) return -1; interface_len = strlen(globals->net_iface); if (interface_len > sizeof(change_interface.ifaces)) { fprintf(stderr, "%s: interface name list too long, not changing\n", __func__); return 0; } len = sizeof(change_interface); change_interface.header.type = ALFRED_CHANGE_INTERFACE; change_interface.header.version = ALFRED_VERSION; change_interface.header.length = FIXED_TLV_LEN(change_interface); strncpy(change_interface.ifaces, globals->net_iface, sizeof(change_interface.ifaces)); change_interface.ifaces[sizeof(change_interface.ifaces) - 1] = '\0'; /* test it before sending * globals->net_iface is now saved in change_interface.ifaces * and can be modified by strtok_r */ input = globals->net_iface; while ((token = strtok_r(input, ",", &saveptr))) { input = NULL; ret = check_interface(token); if (ret < 0) return 0; } ret = write(globals->unix_sock, &change_interface, len); if (ret != len) fprintf(stderr, "%s: only wrote %d of %d bytes: %s\n", __func__, ret, len, strerror(errno)); unix_sock_close(globals); return 0; } int alfred_client_change_bat_iface(struct globals *globals) { struct alfred_change_bat_iface_v0 change_bat_iface; int ret, len; size_t interface_len; if (unix_sock_open_client(globals)) return -1; interface_len = strlen(globals->mesh_iface); if (interface_len > sizeof(change_bat_iface.bat_iface)) { fprintf(stderr, "%s: batman-adv interface name list too long, not changing\n", __func__); return 0; } len = sizeof(change_bat_iface); change_bat_iface.header.type = ALFRED_CHANGE_BAT_IFACE; change_bat_iface.header.version = ALFRED_VERSION; change_bat_iface.header.length = FIXED_TLV_LEN(change_bat_iface); strncpy(change_bat_iface.bat_iface, globals->mesh_iface, sizeof(change_bat_iface.bat_iface)); change_bat_iface.bat_iface[sizeof(change_bat_iface.bat_iface) - 1] = '\0'; ret = write(globals->unix_sock, &change_bat_iface, len); if (ret != len) fprintf(stderr, "%s: only wrote %d of %d bytes: %s\n", __func__, ret, len, strerror(errno)); unix_sock_close(globals); return 0; } int alfred_client_server_status(struct globals *globals) { struct alfred_server_status_net_iface_v0 *status_net_iface; struct alfred_server_status_bat_iface_v0 *status_bat_iface; struct alfred_server_status_op_mode_v0 *status_op_mode; struct alfred_server_status_req_v0 status_req; struct alfred_server_status_rep_v0 *status_rep; int ret, tlvsize, headsize, len, consumed; struct alfred_tlv *status_tlv; uint8_t buf[MAX_PAYLOAD]; if (unix_sock_open_client(globals)) return -1; len = sizeof(status_req); memset(&status_req, 0, len); status_req.header.type = ALFRED_SERVER_STATUS; status_req.header.version = ALFRED_VERSION; status_req.header.length = 0; ret = write(globals->unix_sock, (unsigned char *)&status_req, len); if (ret != len) fprintf(stderr, "%s: only wrote %d of %d bytes: %s\n", __func__, ret, len, strerror(errno)); len = read(globals->unix_sock, buf, sizeof(buf)); if (len <= 0) { perror("read from unix socket failed"); goto err; } ret = -1; status_rep = (struct alfred_server_status_rep_v0 *)buf; /* drop too small packets */ headsize = sizeof(status_rep->header); if (len < headsize) { perror("unexpected header size received from unix socket"); goto err; } if ((len - headsize) < ((int)ntohs(status_rep->header.length))) { perror("unexpected packet size received from unix socket"); goto err; } if (status_rep->header.type != ALFRED_SERVER_STATUS) { perror("alfred server_status type mismatch"); goto err; } if (status_rep->header.version != ALFRED_VERSION) { perror("alfred version mismatch"); goto err; } headsize = ntohs(status_rep->header.length); if (headsize < (int)(sizeof(*status_rep) - sizeof(status_rep->header))) goto err; consumed = sizeof(*status_rep); while (len - consumed > 0) { if (len - consumed < (int)sizeof(*status_tlv)) break; status_tlv = (struct alfred_tlv *)(buf + consumed); if (status_tlv->version != ALFRED_VERSION) break; tlvsize = ntohs(status_tlv->length); tlvsize += sizeof(*status_tlv); if (len - consumed < tlvsize) break; switch (status_tlv->type) { case ALFRED_SERVER_OP_MODE: if (tlvsize != sizeof(*status_op_mode)) break; status_op_mode = (struct alfred_server_status_op_mode_v0 *)(buf + consumed); switch (status_op_mode->mode) { case ALFRED_MODESWITCH_SECONDARY: printf("- mode: secondary\n"); break; case ALFRED_MODESWITCH_PRIMARY: printf("- mode: primary\n"); break; default: printf("- mode: unknown\n"); break; } break; case ALFRED_SERVER_NET_IFACE: if (tlvsize != sizeof(*status_net_iface)) break; status_net_iface = (struct alfred_server_status_net_iface_v0 *)(buf + consumed); status_net_iface->net_iface[sizeof(status_net_iface->net_iface) - 1] = 0; printf("- interface: %s\n", status_net_iface->net_iface); printf("\t- status: %s\n", status_net_iface->active == 1 ? "active" : "inactive"); break; case ALFRED_SERVER_BAT_IFACE: if (tlvsize != sizeof(*status_bat_iface)) break; status_bat_iface = (struct alfred_server_status_bat_iface_v0 *)(buf + consumed); status_bat_iface->bat_iface[sizeof(status_bat_iface->bat_iface) - 1] = 0; printf("- batman-adv interface: %s\n", status_bat_iface->bat_iface); break; } consumed += tlvsize; } err: unix_sock_close(globals); return 0; } int alfred_client_event_monitor(struct globals *globals) { struct alfred_event_register_v0 event_register; struct alfred_event_notify_v0 event_notify; int ret, len; if (unix_sock_open_client(globals)) return -1; len = sizeof(event_register); event_register.header.type = ALFRED_EVENT_REGISTER; event_register.header.version = ALFRED_VERSION; event_register.header.length = 0; ret = write(globals->unix_sock, &event_register, len); if (ret != len) { fprintf(stderr, "%s: only wrote %d of %d bytes: %s\n", __func__, ret, len, strerror(errno)); goto err; } while (true) { len = read(globals->unix_sock, &event_notify, sizeof(event_notify)); if (len == 0) { fprintf(stdout, "Server closed the connection\n"); goto err; } if (len < 0) { perror("read from unix socket failed"); goto err; } if (len != sizeof(event_notify)) { fprintf(stderr, "notify read bytes: %d (expected: %zu)\n", len, sizeof(event_notify)); goto err; } if (event_notify.header.version != ALFRED_VERSION) continue; if (event_notify.header.type != ALFRED_EVENT_NOTIFY) continue; fprintf(stdout, "Event: type = %hhu, source = %02x:%02x:%02x:%02x:%02x:%02x\n", event_notify.type, event_notify.source[0], event_notify.source[1], event_notify.source[2], event_notify.source[3], event_notify.source[4], event_notify.source[5]); } err: unix_sock_close(globals); return 0; } alfred-2025.1/epoll_handle.h000066400000000000000000000007601500012154500156330ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Sven Eckelmann * * License-Filename: LICENSES/preferred/GPL-2.0 */ #ifndef _ALFRED_EPOLL_HANDLE_H #define _ALFRED_EPOLL_HANDLE_H #include struct globals; struct epoll_handle; typedef void (*epoll_handler)(struct globals *globals, struct epoll_handle *handle, struct epoll_event *ev); struct epoll_handle { epoll_handler handler; }; #endif /* _ALFRED_EPOLL_HANDLE_H */ alfred-2025.1/gpsd/000077500000000000000000000000001500012154500137665ustar00rootroot00000000000000alfred-2025.1/gpsd/Makefile000077500000000000000000000042431500012154500154340ustar00rootroot00000000000000#!/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 # alfred-gpsd build BINARY_NAME = alfred-gpsd OBJ += alfred-gpsd.o MANPAGE = man/alfred-gpsd.8 # alfred flags and options CFLAGS += -pedantic -Wall -W -std=gnu99 -fno-strict-aliasing -MD -MP CPPFLAGS += -D_GNU_SOURCE # 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 LIBGPS_CFLAGS) $(origin LIBGPS_LDLIBS), undefined undefined) LIBGPS_NAME ?= libgps ifeq ($(shell $(PKG_CONFIG) --modversion $(LIBGPS_NAME) 2>/dev/null),) $(error No $(LIBGPS_NAME) development libraries found!) endif LIBGPS_CFLAGS += $(shell $(PKG_CONFIG) --cflags $(LIBGPS_NAME)) LIBGPS_LDLIBS += $(shell $(PKG_CONFIG) --libs $(LIBGPS_NAME)) -lm endif CFLAGS += $(LIBGPS_CFLAGS) LDLIBS += $(LIBGPS_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) $(LINK.o) $^ $(LDLIBS) -o $@ clean: $(RM) $(BINARY_NAME) $(OBJ) $(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:.o=.d) -include $(DEP) .PHONY: all clean install alfred-2025.1/gpsd/alfred-gpsd.c000066400000000000000000000312231500012154500163230ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) B.A.T.M.A.N. contributors: * * Andrew Lunn, Simon Wunderlich * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include "alfred-gpsd.h" #include static struct globals gpsd_globals; static int alfred_open_sock(struct globals *globals) { struct sockaddr_un addr; globals->unix_sock = socket(AF_LOCAL, SOCK_STREAM, 0); if (globals->unix_sock < 0) { perror("can't create unix socket"); return -1; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_LOCAL; strncpy(addr.sun_path, globals->unix_path, sizeof(addr.sun_path)); addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; if (connect(globals->unix_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { close(globals->unix_sock); globals->unix_sock = -1; perror("can't connect to unix socket"); return -1; } return 0; } static int gpsd_publish_data(struct globals *globals) { int len, ret; /* to push data we have to add a push header, the header for the data * and our own data type. */ globals->push->tx.id = htons(ntohs(globals->push->tx.id) + 1); len = GPSD_DATA_SIZE(globals->gpsd_data); globals->push->data->header.length = htons(len); len += sizeof(*globals->push) - sizeof(globals->push->header); len += sizeof(*globals->push->data); globals->push->header.length = htons(len); len += sizeof(globals->push->header); alfred_open_sock(globals); if (globals->unix_sock < 0) return globals->unix_sock; ret = write(globals->unix_sock, globals->buf, len); close(globals->unix_sock); if (ret < len) return -1; return 0; } static void gpsd_now_to_iso8601(char *tbuf, size_t len) { #if GPSD_API_MAJOR_VERSION >= 9 timespec_t now; clock_gettime(CLOCK_REALTIME, &now); timespec_to_iso8601(now, tbuf, len); #else timestamp_t now = timestamp(); unix_to_iso8601(now, tbuf, len); #endif } static void gpsd_get_location(struct globals *globals) { if (globals->source == SOURCE_CMDLINE) { char tbuf[JSON_DATE_MAX+1]; gpsd_now_to_iso8601(tbuf, sizeof(tbuf)); sprintf(globals->gpsd_data->tpv, "{\"class\":\"TPV\",\"device\":\"command line\"," "\"time\":\"%s\"," "\"lat\":%f,\"lon\":%f,\"alt\":%f," "\"mode\":3}", tbuf, globals->lat, globals->lon, globals->alt); globals->gpsd_data->tpv_len = htonl(strlen(globals->gpsd_data->tpv) + 1); } } static int gpsd_update_data(struct globals *globals) { gpsd_get_location(globals); gpsd_publish_data(globals); return 0; } static int gpsd_request_data(struct globals *globals) { int ret; globals->request = (struct alfred_request_v0 *) globals->buf; globals->request->header.type = ALFRED_REQUEST; globals->request->header.version = ALFRED_VERSION; globals->request->header.length = htons(sizeof(*globals->request) - sizeof(globals->request->header)); globals->request->requested_type = GPSD_PACKETTYPE; globals->request->tx_id = htons(random()); alfred_open_sock(globals); if (globals->unix_sock < 0) return globals->unix_sock; ret = write(globals->unix_sock, globals->request, sizeof(*globals->request)); if (ret < (int)sizeof(*globals->request)) { close(globals->unix_sock); return -1; } return globals->unix_sock; } static struct gpsd_v1 *gpsd_receive_answer_packet(int sock, uint16_t *len, uint8_t *source) { static uint8_t buf[65536]; struct alfred_tlv *tlv; struct alfred_push_data_v0 *push; struct alfred_data *data; int l, ret; ret = read(sock, buf, sizeof(*tlv)); if (ret < 0) return NULL; if (ret < (int)sizeof(*tlv)) return NULL; tlv = (struct alfred_tlv *)buf; /* TODO: might return an ALFRED_STATUS_ERROR too, handle it */ if (tlv->type != ALFRED_PUSH_DATA) return NULL; l = ntohs(tlv->length); /* exceed the buffer? don't read */ if (l > (int)(sizeof(buf) - sizeof(push->header))) return NULL; /* not enough for even the push packet and header? don't bother. */ if (l < (int)(sizeof(*push) - sizeof(push->header) + sizeof(*data))) return NULL; /* read the rest of the packet */ ret = read(sock, buf + sizeof(*tlv), l); if (ret < l) return NULL; push = (struct alfred_push_data_v0 *)buf; data = push->data; *len = ntohs(data->header.length); if (data->header.type != GPSD_PACKETTYPE) return NULL; if (data->header.version != GPSD_PACKETVERSION) return NULL; memcpy(source, data->source, ETH_ALEN); return (struct gpsd_v1 *) data->data; } static int gpsd_read_answer(struct globals *globals) { struct gpsd_v1 *gpsd_data; uint16_t len; uint8_t source[ETH_ALEN]; bool first_line = true; printf("[\n"); while ((gpsd_data = gpsd_receive_answer_packet(globals->unix_sock, &len, source)) != NULL) { if (len < sizeof(*gpsd_data)) break; /* check size and skip bogus packets */ if (len != GPSD_DATA_SIZE(gpsd_data)) continue; if (first_line) first_line = false; else printf(",\n"); printf(" { \"source\" : \"%02x:%02x:%02x:%02x:%02x:%02x\", " "\"tpv\" : %s }", source[0], source[1], source[2], source[3], source[4], source[5], gpsd_data->tpv); } printf("\n]\n"); return 0; } /* Standard parsing of a GPS data source spec. Taken from gpsdclient.c * remove when gpsd 3.25 is minimum supported version */ static void alfred_gpsd_source_spec(const char *arg, struct alfred_gpsd_fixsource_t *source) { /* the casts attempt to head off a -Wwrite-strings warning */ source->server = (char *)"localhost"; source->port = (char *)DEFAULT_GPSD_PORT; source->device = NULL; if (arg != NULL) { char *colon1, *skipto, *rbrk; source->spec = strdup(arg); assert(source->spec != NULL); skipto = source->spec; if (*skipto == '[' && (rbrk = strchr(skipto, ']')) != NULL) { skipto = rbrk; } colon1 = strchr(skipto, ':'); if (colon1 != NULL) { char *colon2; *colon1 = '\0'; if (colon1 != source->spec) { source->server = source->spec; } source->port = colon1 + 1; colon2 = strchr(source->port, ':'); if (colon2 != NULL) { *colon2 = '\0'; source->device = colon2 + 1; } } else if (strchr(source->spec, '/') != NULL) { source->device = source->spec; } else { source->server = source->spec; } } if (*source->server == '[') { char *rbrk = strchr(source->server, ']'); ++source->server; if (rbrk != NULL) *rbrk = '\0'; } } static int gpsd_get_data(struct globals *globals) { globals->unix_sock = gpsd_request_data(globals); if (globals->unix_sock < 0) return -1; gpsd_read_answer(globals); close(globals->unix_sock); return 0; } static void gpsd_connect_gpsd(struct globals *globals) { unsigned int flags = WATCH_ENABLE | WATCH_JSON; int ret; ret = gps_open(globals->gpsdsource.server, globals->gpsdsource.port, &globals->gpsdata); if (ret) { /* Could not connect to gpsd. Set the fd so we don't try to perform select(2) on it. */ globals->gpsdata.gps_fd = -1; return; } if (globals->gpsdsource.device != NULL) flags |= WATCH_DEVICE; gps_stream(&globals->gpsdata, flags, globals->gpsdsource.device); } static void gpsd_read_gpsd(struct globals *globals) { ssize_t ret; size_t cnt; bool eol = false; char buf[4096]; const size_t tpv_size = sizeof(globals->buf) - sizeof(*globals->push) - sizeof(struct alfred_data) - sizeof(*globals->gpsd_data); cnt = 0; do { ret = read(globals->gpsdata.gps_fd, &buf[cnt], 1); if (ret != 1) { gps_close(&globals->gpsdata); globals->gpsdata.gps_fd = -1; return; } switch (buf[cnt]) { case '\r': cnt--; break; case '\n': eol = true; buf[cnt] = '\0'; break; } } while (cnt++ < sizeof(buf) - 1 && !eol); if (!eol) { gps_close(&globals->gpsdata); globals->gpsdata.gps_fd = -1; return; } #define STARTSWITH(str, prefix) strncmp(str, prefix, sizeof(prefix)-1)==0 if (STARTSWITH(buf, "{\"class\":\"TPV\"")) { strncpy(globals->gpsd_data->tpv, buf, tpv_size); globals->gpsd_data->tpv[tpv_size - 1] = '\0'; globals->gpsd_data->tpv_len = htonl(strlen(globals->gpsd_data->tpv) + 1); } } static void gpsd_usage(void) { printf("Usage: alfred-gpsd [options]\n"); printf(" -s, --server start up in server mode, which regularly updates gpsd data from batman-adv\n"); printf(" -l ,, Static location\n"); printf(" -g server[:port[:device]] GPSD source\n"); printf(" -v, --version print the version\n"); printf(" -h, --help this help\n"); printf("\n"); } static void gpsd_parse_location(struct globals *globals, const char * optarg) { int n; float lat, lon, alt; n = sscanf(optarg, "%f,%f,%f", &lat, &lon, &alt); if (n != 3) { printf("Unable to parse location\n"); gpsd_usage(); exit(EXIT_FAILURE); } if ((lat < -90) || (lat > 90)) { printf("Invalid latitude\n"); gpsd_usage(); exit(EXIT_FAILURE); } if ((lon < -180) || (lon > 180)) { printf("Invalid longitude\n"); gpsd_usage(); exit(EXIT_FAILURE); } if ((alt < -1000) || (alt > 9000)) { /* No support for aircraft or submarines! */ printf("Invalid altitude\n"); gpsd_usage(); exit(EXIT_FAILURE); } globals->lat = lat; globals->lon = lon; globals->alt = alt; } static struct globals *gpsd_init(int argc, char *argv[]) { bool have_source = false; int opt, opt_ind; struct globals *globals; struct option long_options[] = { {"server", no_argument, NULL, 's'}, {"location", required_argument, NULL, 'l'}, {"gpsd", required_argument, NULL, 'g'}, {"unix-path", required_argument, NULL, 'u'}, {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'v'}, {NULL, 0, NULL, 0}, }; globals = &gpsd_globals; memset(globals, 0, sizeof(*globals)); globals->opmode = OPMODE_CLIENT; globals->source = SOURCE_GPSD; globals->gpsd_format = FORMAT_JSON; globals->unix_path = ALFRED_SOCK_PATH_DEFAULT; while ((opt = getopt_long(argc, argv, "shl:g:vu:", long_options, &opt_ind)) != -1) { switch (opt) { case 's': globals->opmode = OPMODE_SERVER; break; case 'l': globals->source = SOURCE_CMDLINE; gpsd_parse_location(globals, optarg); break; case 'g': alfred_gpsd_source_spec(optarg, &globals->gpsdsource); have_source = true; break; case 'u': globals->unix_path = optarg; break; case 'v': printf("%s %s\n", argv[0], SOURCE_VERSION); printf("GPSD alfred client\n"); return NULL; case 'h': default: gpsd_usage(); return NULL; } } if (globals->source == SOURCE_GPSD && !have_source) alfred_gpsd_source_spec(NULL, &globals->gpsdsource); if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) perror("could not register SIGPIPE handler"); return globals; } static int gpsd_server(struct globals *globals) { struct timeval tv; fd_set fds; int max_fd, ret; const size_t overhead = sizeof(*globals->push) + sizeof(struct alfred_data); const size_t tpv_size = sizeof(globals->buf) - sizeof(*globals->push) - sizeof(struct alfred_data) - sizeof(*globals->gpsd_data); long interval; globals->push = (struct alfred_push_data_v0 *) globals->buf; globals->gpsd_data = (struct gpsd_v1 *) (globals->buf + overhead); globals->push->header.type = ALFRED_PUSH_DATA; globals->push->header.version = ALFRED_VERSION; globals->push->tx.id = 0; globals->push->tx.seqno = 0; globals->push->data->header.type = GPSD_PACKETTYPE; globals->push->data->header.version = GPSD_PACKETVERSION; strncpy(globals->gpsd_data->tpv, GPSD_INIT_TPV, tpv_size); globals->gpsd_data->tpv[tpv_size - 1] = '\0'; globals->gpsd_data->tpv_len = htonl(strlen(globals->gpsd_data->tpv) + 1); /* If we have a static location, we don't need to update very often. */ if (globals->source == SOURCE_GPSD) { globals->gpsdata.gps_fd = -1; interval = 2; } else interval = 60 * 5; while (1) { gpsd_update_data(globals); /* If we are not connected to gpsd, try to connect. */ if (globals->source == SOURCE_GPSD && globals->gpsdata.gps_fd == -1) { gpsd_connect_gpsd(globals); } /* Use linux's select(2) behaviour of setting tv to the remaining time when it exists */ tv.tv_sec = interval; tv.tv_usec = 0; do { FD_ZERO(&fds); if (globals->source == SOURCE_GPSD && globals->gpsdata.gps_fd != -1) { FD_SET(globals->gpsdata.gps_fd, &fds); max_fd = globals->gpsdata.gps_fd + 1; } else { max_fd = 0; } errno = 0; ret = select(max_fd, &fds, NULL, NULL, &tv); if (ret == -1 && errno != EINTR) perror("select error"); if (ret == 1) gpsd_read_gpsd(globals); } while (ret != 0); } return EXIT_FAILURE; } int main(int argc, char *argv[]) { struct globals *globals; globals = gpsd_init(argc, argv); if (!globals) return EXIT_FAILURE; switch (globals->opmode) { case OPMODE_SERVER: return gpsd_server(globals); break; case OPMODE_CLIENT: return gpsd_get_data(globals); break; } return EXIT_FAILURE; } alfred-2025.1/gpsd/alfred-gpsd.h000066400000000000000000000034501500012154500163310ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) B.A.T.M.A.N. contributors: * * Andrew Lunn, Simon Wunderlich * * License-Filename: LICENSES/preferred/GPL-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../packet.h" #include "../list.h" #ifndef SOURCE_VERSION #define SOURCE_VERSION "2025.1" #endif #define ALFRED_SOCK_PATH_DEFAULT "/var/run/alfred.sock" #define PATH_BUFF_LEN 200 #define GPSD_PACKETTYPE 2 #define GPSD_PACKETVERSION 1 #define UPDATE_INTERVAL 10 enum opmode { OPMODE_SERVER, OPMODE_CLIENT }; enum source { SOURCE_CMDLINE, SOURCE_GPSD }; enum gpsd_format { FORMAT_JSON }; #define JSON_DATE_MAX 24 /* ISO8601 timestamp with 2 decimal places */ struct gpsd_v1 { uint32_t tpv_len; __extension__ char tpv[0]; } __packed; #define GPSD_INIT_TPV "{\"class\":\"TPV\",\"mode\":0}" #define GPSD_DATA_SIZE(gpsd_data) \ (sizeof(*gpsd_data) + (ntohl(gpsd_data->tpv_len))) /* struct taken from gpsdclient.h * remove when gpsd 3.25 is minimum supported version */ struct alfred_gpsd_fixsource_t { char *spec; /* pointer to actual storage */ char *server; char *port; char *device; }; struct globals { enum opmode opmode; enum source source; enum gpsd_format gpsd_format; uint8_t buf[65536]; /* internal pointers into buf */ struct alfred_request_v0 *request; struct alfred_push_data_v0 *push; struct gpsd_v1 *gpsd_data; float lat, lon, alt; int unix_sock; const char *unix_path; struct alfred_gpsd_fixsource_t gpsdsource; struct gps_data_t gpsdata; char * tpv; }; alfred-2025.1/gpsd/man/000077500000000000000000000000001500012154500145415ustar00rootroot00000000000000alfred-2025.1/gpsd/man/alfred-gpsd.8000066400000000000000000000064051500012154500170270ustar00rootroot00000000000000.\" 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 "ALFRED-GPSD" "8" "Oct 04, 2013" "Linux" "Alfred GPS distribution server" .\" 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 batadv-vis.8 -Tutf8 .\" Retrieve format warnings with .\" man --warnings batadv-vis.8 > /dev/null .\" -------------------------------------------------------------------------- .ad l .SH NAME alfred\-gpsd \- Alfred GPS distribution server .SH SYNOPSIS .B alfred\-gpsd [\fIoptions\fP] .br .SH DESCRIPTION alfred\-gpsd can be used to distribute GPS location information over your batman-adv mesh network. It reads the current location from gpsd, or a fixed location from the command line, and distributes this information via alfred. By gathering this local information, any alfred-gpsd node can get location information about other nodes. .PP .PP .SH OPTIONS .TP \fB\-v\fP, \fB\-\-version\fP Print the version .TP \fB\-h\fP, \fB\-\-help\fP Display a brief help message. .TP \fB\-u\fP, \fB\-\-unix-path\fP \fIpath\fP path to unix socket used for alfred server communication. .TP \fB\-s\fP, \fB\-\-server\fP Start up in server mode. This server will read the current location from gpsd and set it in alfred via unix socket. The alfred server must run too to get this information set. .TP \fB\-l\fP, \fB\-\-location ,,\fP Rather than read the current location from gpsd, use a fixed location. .TP \fB\-g\fP, \fB\-\-gpsd server[:port[:device]]\fP Specify the server hostname and optional port of where gpsd is listening. Additionally, a specific device connected to gpsd can be specified. . .SH EXAMPLES Start an alfred\-gpsd server which is fetching GPS data from the local gpsd. .br \fB alfred\-gpsd \-s\fP .br Start an alfred\-gpsd server with a fixed location .br \fB alfred\-gpsd \-s \-l 48.858222,2.2945,358\fP .br To get a list of GPS locations, in JSON format: .br \fB alfred-gpsd\fP .nf [ { "source" : "f6:00:48:13:d3:1e", "tpv" : {"class":"TPV","tag":"RMC", "device":"/dev/ttyACM0","mode":3,"time":"2013-10-01T10:43:20.000Z", "ept":0.005,"lat":52.575485000,"lon":-1.339716667,"alt":122.500, "epx":10.199,"epy":15.720,"epv":31.050,"track":0.0000,"speed":0.010, "climb":0.000,"eps":31.44} }, { "source" : "8e:4c:77:b3:65:b4", "tpv" : {"class":"TPV", "device":"command line","time":"2013-10-01T10:43:05.129Z", "lat":48.858222,"lon":2.2945,"alt":358.000000,"mode":3} } ] .fi .br . .SH SEE ALSO .BR alfred (8), .BR gpsd (8) .BR gpsd_json(5) .SH AUTHOR alfred\-gpsd and this Manual page was written by Andrew Lunn alfred-2025.1/hash.c000066400000000000000000000176541500012154500141350ustar00rootroot00000000000000// 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 */ #include "hash.h" #include #include #include "alfred.h" /* 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 *bucket, *last_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; debugFree(last_bucket, 1301); } } 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) { int index; struct element_t *bucket_it, *prev_bucket = NULL; 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) { debugFree(hash->table, 1302); debugFree(hash, 1303); } /* free hash_it_t pointer when stopping hash_iterate early */ void hash_iterate_free(struct hash_it_t *iter_in) { debugFree(iter_in, 1304); } /* 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 = debugMalloc(sizeof(struct hash_it_t), 301); 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) { 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 = debugMalloc(sizeof(struct hashtable_t), 302); if (!hash) return NULL; hash->size = size; hash->table = debugMalloc(sizeof(struct element_t *) * size, 303); if (!hash->table) { debugFree(hash, 1305); 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) { int ret; struct element_t *bucket; /* found the tail of the list, add new element */ bucket = debugMalloc(sizeof(struct element_t), 304); if (!bucket) return -1; ret = hash_add_bucket(hash, data, bucket, 1); if (ret < 0) debugFree(bucket, 1307); 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) { int index; struct element_t *bucket; 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; debugFree(hash_it_t->bucket, 1306); 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)) { struct element_t **first_bucket = NULL; if (hash_it_t.bucket == hash->table[hash_it_t.index]) first_bucket = &hash->table[hash_it_t.index]; hash_it_t.first_bucket = first_bucket; 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; } /* print the hash table for debugging */ /* void hash_debug(struct hashtable_t *hash) { int i; struct element_t *bucket; for (i = 0; i < hash->size; i++) { printf("[%d] ", i); bucket = hash->table[i]; while (bucket) { printf("-> [%10p] ", (void *)bucket); bucket = bucket->next; } printf("\n"); } printf("\n"); }*/ alfred-2025.1/hash.h000066400000000000000000000063701500012154500141330ustar00rootroot00000000000000/* 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 alfred-2025.1/list.h000066400000000000000000000630511500012154500141620ustar00rootroot00000000000000/* 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__ */ alfred-2025.1/main.c000066400000000000000000000242241500012154500141250ustar00rootroot00000000000000// 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 #ifdef CONFIG_ALFRED_CAPABILITIES #include #include #include #endif #include "alfred.h" #include "packet.h" #include "list.h" static struct globals alfred_globals; static void alfred_usage(void) { printf("Usage: alfred [options]\n"); printf("client mode options:\n"); printf(" -s, --set-data [data type] sets new data to distribute from stdin\n"); printf(" for the supplied data type (0-255)\n"); printf(" -r, --request [data type] collect data from the network and print on stdout\n"); printf(" -d, --verbose Show extra information in the data output\n"); printf(" -V, --req-version specify the data version set for -s\n"); printf(" -M, --modeswitch primary switch daemon to mode primary\n"); printf(" secondary switch daemon to mode secondary\n"); printf(" -I, --change-interface [interface] change to the specified interface(s)\n"); printf(" -B, --change-bat-iface [interface] change to the specified batman-adv interface\n"); printf(" -S, --server-status request server status info such as mode & interfaces\n"); printf(" -E, --event-monitor monitor alfred data record update events\n"); printf("\n"); printf("server mode options:\n"); printf(" -i, --interface specify the interface (or comma separated list of interfaces) to listen on\n"); printf(" -f, --force start server even when batman-adv or interface(s) are not yet available.\n"); printf(" -b specify the batman-adv interface\n"); printf(" configured on the system (default: bat0)\n"); printf(" use 'none' to disable the batman-adv\n"); printf(" based best server selection\n"); printf(" -m, --primary start up the daemon in primary mode, which\n"); printf(" accepts data from secondaries and syncs it with\n"); printf(" other primaries\n"); printf(" -p, --sync-period [period] set synchronization period, in seconds\n"); printf(" fractional seconds are supported (i.e. 0.2 = 5 Hz)\n"); printf(" -4 [group-address] specify IPv4 multicast address and operate in IPv4 mode"); printf("\n"); printf(" -u, --unix-path [path] path to unix socket used for client-server\n"); printf(" communication (default: \""ALFRED_SOCK_PATH_DEFAULT"\")\n"); printf(" -c, --update-command command to call on data change\n"); printf(" -v, --version print the version\n"); printf(" -h, --help this help\n"); printf("\n"); } static int reduce_capabilities(void) { int ret = 0; #ifdef CONFIG_ALFRED_CAPABILITIES cap_t cap_cur; cap_t cap_new; cap_flag_value_t cap_flag; cap_value_t cap_net_raw = CAP_NET_RAW; cap_value_t cap_net_admin = CAP_NET_ADMIN; /* get current process capabilities */ cap_cur = cap_get_proc(); if (!cap_cur) { perror("cap_get_proc"); return -1; } /* create new capabilities */ cap_new = cap_init(); if (!cap_new) { perror("cap_init"); cap_free(cap_new); return -1; } /* copy capability as non-effictive but permitted */ cap_flag = CAP_CLEAR; cap_get_flag(cap_cur, CAP_NET_RAW, CAP_PERMITTED, &cap_flag); if (cap_flag != CAP_CLEAR) { ret = cap_set_flag(cap_new, CAP_PERMITTED, 1, &cap_net_raw, CAP_SET); if (ret < 0) { perror("cap_set_flag"); goto out; } } cap_flag = CAP_CLEAR; cap_get_flag(cap_cur, CAP_NET_ADMIN, CAP_PERMITTED, &cap_flag); if (cap_flag != CAP_CLEAR) { ret = cap_set_flag(cap_new, CAP_PERMITTED, 1, &cap_net_admin, CAP_SET); if (ret < 0) { perror("cap_set_flag"); goto out; } } /* set minimal capabilities field */ ret = cap_set_proc(cap_new); if (ret < 0) { perror("cap_set_proc"); goto out; } /* don't drop capabilities with setuid */ ret = prctl(PR_SET_KEEPCAPS, 1); if (ret < 0) { perror("prctl PR_SET_KEEPCAPS(1)"); goto out; } /* drop euid */ ret = setuid(getuid()); if (ret < 0) { perror("setuid"); goto out; } /* drop capabilities with setuid */ ret = prctl(PR_SET_KEEPCAPS, 0); if (ret < 0) { perror("prctl PR_SET_KEEPCAPS(0)"); goto out; } out: cap_free(cap_new); cap_free(cap_cur); #endif return ret; } static struct globals *alfred_init(int argc, char *argv[]) { int opt, opt_ind, i, ret; double sync_period = 0.0; struct globals *globals; struct option long_options[] = { {"set-data", required_argument, NULL, 's'}, {"request", required_argument, NULL, 'r'}, {"interface", required_argument, NULL, 'i'}, {"master", no_argument, NULL, 'm'}, {"primary", no_argument, NULL, 'm'}, {"help", no_argument, NULL, 'h'}, {"req-version", required_argument, NULL, 'V'}, {"modeswitch", required_argument, NULL, 'M'}, {"change-interface", required_argument, NULL, 'I'}, {"change-bat-iface", required_argument, NULL, 'B'}, {"server-status", no_argument, NULL, 'S'}, {"event-monitor", no_argument, NULL, 'E'}, {"unix-path", required_argument, NULL, 'u'}, {"update-command", required_argument, NULL, 'c'}, {"version", no_argument, NULL, 'v'}, {"verbose", no_argument, NULL, 'd'}, {"sync-period", required_argument, NULL, 'p'}, {"force", no_argument, NULL, 'f'}, {NULL, 0, NULL, 0}, }; ret = reduce_capabilities(); if (ret < 0) return NULL; globals = &alfred_globals; memset(globals, 0, sizeof(*globals)); INIT_LIST_HEAD(&globals->interfaces); INIT_LIST_HEAD(&globals->event_listeners); globals->net_iface = NULL; globals->opmode = OPMODE_SECONDARY; globals->clientmode = CLIENT_NONE; globals->best_server = NULL; globals->clientmode_version = 0; globals->mesh_iface = "bat0"; globals->unix_path = ALFRED_SOCK_PATH_DEFAULT; globals->verbose = false; globals->ipv4mode = false; globals->force = false; globals->update_command = NULL; globals->sync_period.tv_sec = ALFRED_INTERVAL; globals->sync_period.tv_nsec = 0; bitmap_zero(globals->changed_data_types, ALFRED_NUM_TYPES); time_random_seed(); while ((opt = getopt_long(argc, argv, "ms:r:hi:b:vV:M:I:B:SEu:dc:p:4:f", long_options, &opt_ind)) != -1) { switch (opt) { case 'r': globals->clientmode = CLIENT_REQUEST_DATA; i = atoi(optarg); if (i < ALFRED_MAX_RESERVED_TYPE || i >= ALFRED_NUM_TYPES) { fprintf(stderr, "bad data type argument\n"); return NULL; } globals->clientmode_arg = i; break; case 's': globals->clientmode = CLIENT_SET_DATA; i = atoi(optarg); if (i < ALFRED_MAX_RESERVED_TYPE || i >= ALFRED_NUM_TYPES) { fprintf(stderr, "bad data type argument\n"); return NULL; } globals->clientmode_arg = i; break; case 'm': globals->opmode = OPMODE_PRIMARY; break; case 'i': globals->net_iface = strdup(optarg); break; case 'b': globals->mesh_iface = strdup(optarg); break; case 'V': i = atoi(optarg); if (i < 0 || i > 255) { fprintf(stderr, "bad data version argument\n"); return NULL; } globals->clientmode_version = atoi(optarg); break; case 'M': if (strcmp(optarg, "master") == 0 || strcmp(optarg, "primary") == 0) { globals->opmode = OPMODE_PRIMARY; } else if (strcmp(optarg, "slave") == 0 || strcmp(optarg, "secondary") == 0) { globals->opmode = OPMODE_SECONDARY; } else { fprintf(stderr, "bad modeswitch argument\n"); return NULL; } globals->clientmode = CLIENT_MODESWITCH; break; case 'I': globals->clientmode = CLIENT_CHANGE_INTERFACE; globals->net_iface = strdup(optarg); break; case 'B': globals->clientmode = CLIENT_CHANGE_BAT_IFACE; globals->mesh_iface = strdup(optarg); break; case 'S': globals->clientmode = CLIENT_SERVER_STATUS; break; case 'E': globals->clientmode = CLIENT_EVENT_MONITOR; break; case 'u': globals->unix_path = optarg; break; case 'd': globals->verbose = true; break; case 'c': globals->update_command = optarg; break; case 'v': printf("%s %s\n", argv[0], SOURCE_VERSION); printf("A.L.F.R.E.D. - Almighty Lightweight Remote Fact Exchange Daemon\n"); return NULL; case 'p': sync_period = strtod(optarg, NULL); globals->sync_period.tv_sec = (int)sync_period; globals->sync_period.tv_nsec = (double)(sync_period - (int)sync_period) * 1e9; printf(" ** Setting sync interval to: %.9f seconds (%ld.%09ld)\n", sync_period, globals->sync_period.tv_sec, globals->sync_period.tv_nsec); break; case '4': globals->ipv4mode = true; inet_pton(AF_INET, optarg, &alfred_mcast.ipv4); printf(" ** IPv4 Multicast Mode: %x\n", alfred_mcast.ipv4.s_addr); break; case 'f': globals->force = true; break; case 'h': default: alfred_usage(); return NULL; } } if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) perror("could not register SIGPIPE handler"); if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) perror("could not register SIGCHLD handler"); return globals; } int main(int argc, char *argv[]) { struct globals *globals; globals = alfred_init(argc, argv); if (!globals) return 1; switch (globals->clientmode) { case CLIENT_NONE: return alfred_server(globals); case CLIENT_REQUEST_DATA: return alfred_client_request_data(globals); case CLIENT_SET_DATA: return alfred_client_set_data(globals); case CLIENT_MODESWITCH: return alfred_client_modeswitch(globals); case CLIENT_CHANGE_INTERFACE: return alfred_client_change_interface(globals); case CLIENT_CHANGE_BAT_IFACE: return alfred_client_change_bat_iface(globals); case CLIENT_SERVER_STATUS: return alfred_client_server_status(globals); case CLIENT_EVENT_MONITOR: return alfred_client_event_monitor(globals); } return 0; } alfred-2025.1/man/000077500000000000000000000000001500012154500136045ustar00rootroot00000000000000alfred-2025.1/man/alfred.8000066400000000000000000000155321500012154500151400ustar00rootroot00000000000000.\" 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 "ALFRED" "8" "Sep 21, 2013" "Linux" "Almighty Lightweight Fact Remote Exchange Daemon" .\" 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 alfred.8 -Tutf8 .\" Retrieve format warnings with .\" man --warnings alfred.8 > /dev/null .\" -------------------------------------------------------------------------- .ad l .SH NAME alfred \- Almighty Lightweight Fact Remote Exchange Daemon .SH SYNOPSIS .B alfred [\fIoptions\fP] .br .SH DESCRIPTION alfred is a user space daemon for distributing arbitrary local information over the mesh/network in a decentralized fashion. This data can be anything which appears to be useful - originally designed to replace the batman-adv visualization (vis), you may distribute hostnames, phone books, administration information, DNS information, the local weather forecast ... .PP alfred runs as daemon in the background of the system. A user may insert information by using the alfred binary on the command line, or use special programs to communicate with alfred (done via unix sockets). alfred then takes care of distributing the local information to other alfred servers on other nodes. This is done via IPv6 link-local multicast, and does not require any configuration. A user can request data from alfred, and will receive the information available from all alfred servers in the network. .PP .PP .SH OPTIONS .TP \fB\-v\fP, \fB\-\-version\fP Print the version .TP \fB\-h\fP, \fB\-\-help\fP Display a brief help message. .TP \fB\-u\fP, \fB\-\-unix-path\fP \fIpath\fP path to unix socket used for client-server communication. . .SH CLIENT OPTIONS .TP \fB\-s\fP, \fB\-\-set\-data\fP \fIdata\-type\fP Sets new data to distribute from stdin for the supplied data type (0\-255). Note that 0 - 63 are reserved (please send an e-mail to the authors if you want to register a datatype), and can not be used on the commandline. Information must be periodically written again to alfred, otherwise it will timeout and alfred will forget about it (after 10 minutes). .TP \fB\-r\fP, \fB\-\-request\fP \fIdata\-type\fP Collect data from the network and print on stdout .TP \fB\-d\fP, \fB\-\-verbose\fP Show extra information in the data output .TP \fB\-d\fP, \fB\-\-force\fP Start server even when batman-adv or interface(s) are not yet available. .TP \fB\-V\fP, \fB\-\-req\-version\fP \fIversion\fP Specify the data version set for \fB\-s\fP Skipping the parameter entirely has the same effect as setting the parameter to 0 ('\fB\-V\fP 0'). .TP \fB\-M\fP, \fB\-\-modeswitch\fP \fImode\fP .nf \fBprimary\fP Switch daemon to mode primary \fBsecondary\fP Switch daemon to mode secondary .fi .TP \fB\-I\fP, \fB\-\-change\-interface\fP \fIinterface\fP Change the alfred server to use the new \fBinterface\fP(s) .TP \fB\-B\fP, \fB\-\-change\-bat\-iface\fP \fIinterface\fP Change the alfred server to use the new \fBbatman-adv interface\fP .TP \fB\-S\fP, \fB\-\-server\-status\fP Request server status information such as mode & interfaces\fP .TP \fB\-E\fP, \fB\-\-event\-monitor\fP Start alfred event monitor connecting to the alfred server and reporting update events\fP . .SH SERVER OPTIONS .TP \fB\-i\fP, \fB\-\-interface\fP \fIiface\fP Specify the interface (or comma separated list of interfaces) to listen on. Use 'none' to disable interface operations. .TP \fB\-b\fP \fIbatmanif\fP Specify the batman-adv interface configured on the system (default: bat0). Use 'none' to disable the batman-adv based best server selection. The interface option \fB\-i\fP is optional. If interface 'none' is specified, the alfred daemon will not communicate with other alfred instances on the network unless the interface list is modified at runtime via the unix socket. The \fB\-b\fP option is optional, and only needed if you run alfred on a batman-adv interface not called bat0, or if you don't use batman-adv at all (use '\fB\-b\fP none'). In this case, alfred will still work but will not be able to find the best next primary server based on metrics. .TP \fB\-m\fP, \fB\-\-primary\fP Start up the daemon in primary mode, which accepts data from secondaries and syncs it with other primaries. alfred servers may either run as primary or secondary in the network. Primaries will announce their status via broadcast, so that secondaries can find them. Secondaries will then send their data to their nearest primary (based on TQ). Primaries will exchange their data (which they have received from secondaries or got on their own) with other primaries. By using primaries and secondaries, overhead can be reduced while still keeping redundancy (by having multiple primaries). Obviously, at least one primary must be present in the network to let any data exchange happen. Also having all nodes in primary mode is possible (for maximum decentrality and overhead). .TP \fB\-c\fP, \fB\-\-update-command\fP \fIcommand\fP Specify command to execute on data change. It will be called with data-type list as arguments. .TP \fB\-p\fP, \fB\-\-sync-period\fP \fIperiod\fP Specify alfred synchronization period, in seconds. If not specified, the default ALFRED_INTERVAL setting of 10 seconds will be used. Fractional seconds are supported. .TP \fB\-4 \fIgroup-address\fP \fP Specify the IPv4 multicast group address and enable IPv4 data-sharing (disabling IPv6 communication). This option is required on systems that do not support IPv6 addressing. . .SH EXAMPLES Start an alfred server listening on bridge br0 (assuming that this bridge includes the batman interface bat0): .br \fB alfred \-i br0 \-m\fP .br Send data to the alfred server and store it under id 65: .br \fB cat /etc/hostname | alfred -s 65\fP Retrieve all data stored as id 65 from all connected alfred servers: .br \fB alfred -r 65\fP .nf { "fe:f1:00:00:01:01", "OpenWRT\-node\-1\\x0a" }, { "fe:f1:00:00:02:01", "OpenWRT\-node\-2\\x0a" }, { "fe:f1:00:00:03:01", "OpenWRT\-node\-3\\x0a" }, .fi .br . .SH SEE ALSO .BR batadv-vis (8), .BR batctl (8) .SH AUTHOR alfred was written by Simon Wunderlich and Sven Eckelmann . .PP This manual page was written by Simon Wunderlich and Sven Eckelmann . alfred-2025.1/netlink.c000066400000000000000000000144601500012154500146460ustar00rootroot00000000000000// 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 #include #include #include #include #include #include #include #include #include #include #include "batman_adv.h" #ifndef __unused #define __unused __attribute__((unused)) #endif 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_MULTICAST_FANOUT] = { .type = NLA_U32 }, [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 }, }; int missing_mandatory_attrs(struct nlattr *attrs[], const int mandatory[], size_t num) { size_t i; for (i = 0; i < num; i++) if (!attrs[mandatory[i]]) return -EINVAL; return 0; } static int nlquery_error_cb(struct sockaddr_nl *nla __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(const char *mesh_iface, uint8_t nl_cmd, nl_recvmsg_msg_cb_t callback, struct nlquery_opts *query_opts) { struct nl_sock *sock; struct nl_msg *msg; struct nl_cb *cb; int ifindex; int family; int ret; query_opts->err = 0; sock = nl_socket_alloc(); if (!sock) return -ENOMEM; ret = genl_connect(sock); if (ret < 0) { query_opts->err = ret; goto err_free_sock; } family = genl_ctrl_resolve(sock, BATADV_NL_NAME); if (family < 0) { query_opts->err = -EOPNOTSUPP; goto err_free_sock; } ifindex = if_nametoindex(mesh_iface); if (!ifindex) { query_opts->err = -ENODEV; goto err_free_sock; } cb = nl_cb_alloc(NL_CB_DEFAULT); if (!cb) { query_opts->err = -ENOMEM; goto err_free_sock; } 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, family, 0, NLM_F_DUMP, nl_cmd, 1); nla_put_u32(msg, BATADV_ATTR_MESH_IFINDEX, ifindex); nl_send_auto_complete(sock, msg); nlmsg_free(msg); nl_recvmsgs(sock, cb); err_free_cb: nl_cb_put(cb); err_free_sock: nl_socket_free(sock); return query_opts->err; } alfred-2025.1/netlink.h000066400000000000000000000017021500012154500146460ustar00rootroot00000000000000/* 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 _ALFRED_NETLINK_H #define _ALFRED_NETLINK_H #include #include #include struct nlquery_opts { int err; }; #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 int netlink_query_common(const char *mesh_iface, uint8_t nl_cmd, nl_recvmsg_msg_cb_t callback, struct nlquery_opts *query_opts); int missing_mandatory_attrs(struct nlattr *attrs[], const int mandatory[], size_t num); extern struct nla_policy batadv_netlink_policy[]; #endif /* _ALFRED_NETLINK_H */ alfred-2025.1/netsock.c000066400000000000000000000333071500012154500146510ustar00rootroot00000000000000// 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 #include #include #include #include #include #include #include #include #ifdef CONFIG_ALFRED_CAPABILITIES #include #endif #include "alfred.h" #include "batadv_query.h" #include "packet.h" #include "list.h" #include "hash.h" alfred_addr alfred_mcast = { .ipv6 = {{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } } }; static int server_compare(void *d1, void *d2) { struct server *s1 = d1, *s2 = d2; /* compare source and type */ if (memcmp(&s1->hwaddr, &s2->hwaddr, sizeof(s1->hwaddr)) == 0) return 1; else return 0; } static int server_choose(void *d1, int size) { struct server *s1 = d1; uint32_t hash = 0; size_t i; for (i = 0; i < sizeof(s1->hwaddr); i++) { hash += s1->hwaddr.ether_addr_octet[i]; hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash % size; } void netsock_close_all(struct globals *globals) { struct interface *interface, *is; list_for_each_entry_safe(interface, is, &globals->interfaces, list) { if (interface->netsock >= 0) close(interface->netsock); if (interface->netsock_mcast >= 0) close(interface->netsock_mcast); list_del(&interface->list); hash_delete(interface->server_hash, free); free(interface->interface); free(interface); } globals->best_server = NULL; } struct interface *netsock_first_interface(struct globals *globals) { struct interface *interface; list_for_each_entry(interface, &globals->interfaces, list) { if (interface->netsock >= 0) return interface; } return NULL; } static struct interface *netsock_find_interface(struct globals *globals, const char *name) { struct interface *interface; list_for_each_entry(interface, &globals->interfaces, list) { if (strcmp(name, interface->interface) == 0) return interface; } return NULL; } int netsock_set_interfaces(struct globals *globals, char *interfaces) { char *input, *saveptr, *token; struct interface *interface; netsock_close_all(globals); /* interface 'none' disables all interface operations */ if (is_iface_disabled(interfaces)) return 0; input = interfaces; while ((token = strtok_r(input, ",", &saveptr))) { input = NULL; interface = netsock_find_interface(globals, token); if (interface) continue; interface = malloc(sizeof(*interface)); if (!interface) { netsock_close_all(globals); return -ENOMEM; } memset(&interface->hwaddr, 0, sizeof(interface->hwaddr)); memset(&interface->address, 0, sizeof(interface->address)); interface->scope_id = 0; interface->interface = NULL; interface->netsock = -1; interface->netsock_mcast = -1; interface->server_hash = NULL; interface->interface = strdup(token); if (!interface->interface) { free(interface); netsock_close_all(globals); return -ENOMEM; } interface->server_hash = hash_new(64, server_compare, server_choose); if (!interface->server_hash) { free(interface->interface); free(interface); netsock_close_all(globals); return -ENOMEM; } list_add_tail(&interface->list, &globals->interfaces); } return 0; } static int enable_raw_bind_capability(int enable) { int ret = 0; #ifdef CONFIG_ALFRED_CAPABILITIES cap_t cap_cur; cap_flag_value_t cap_flag; cap_value_t cap_net_raw = CAP_NET_RAW; if (enable) cap_flag = CAP_SET; else cap_flag = CAP_CLEAR; cap_cur = cap_get_proc(); if (!cap_cur) { perror("cap_get_proc"); return -1; } ret = cap_set_flag(cap_cur, CAP_EFFECTIVE, 1, &cap_net_raw, cap_flag); if (ret < 0) { perror("cap_set_flag"); goto out; } ret = cap_set_proc(cap_cur); if (ret < 0) { perror("cap_set_proc"); goto out; } out: cap_free(cap_cur); #endif return ret; } static void netsock_close_error(struct interface *interface) { fprintf(stderr, "Error on netsock detected\n"); if (interface->netsock >= 0) close(interface->netsock); if (interface->netsock_mcast >= 0) close(interface->netsock_mcast); interface->netsock = -1; interface->netsock_mcast = -1; } static void netsock_handle_event(struct globals *globals, struct epoll_handle *handle, struct epoll_event *ev) { struct interface *interface; interface = container_of(handle, struct interface, netsock_epoll); if (ev->events & EPOLLERR) { netsock_close_error(interface); return; } recv_alfred_packet(globals, interface, interface->netsock); } static void netsock_mcast_handle_event(struct globals *globals, struct epoll_handle *handle, struct epoll_event *ev) { struct interface *interface; interface = container_of(handle, struct interface, netsock_mcast_epoll); if (ev->events & EPOLLERR) { netsock_close_error(interface); return; } recv_alfred_packet(globals, interface, interface->netsock_mcast); } static int netsock_open(struct globals *globals, struct interface *interface) { int sock; int sock_mc; struct sockaddr_in6 sin6, sin6_mc; struct ipv6_mreq mreq; struct epoll_event ev; struct ifreq ifr; int ret; interface->netsock = -1; interface->netsock_mcast = -1; sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (sock < 0) { perror("can't open socket"); return -1; } sock_mc = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (sock_mc < 0) { close(sock); perror("can't open socket"); return -1; } memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, interface->interface, IFNAMSIZ); ifr.ifr_name[IFNAMSIZ - 1] = '\0'; if (ioctl(sock, SIOCGIFINDEX, &ifr) == -1) { perror("can't get interface"); goto err; } interface->scope_id = ifr.ifr_ifindex; if (ioctl(sock, SIOCGIFHWADDR, &ifr) == -1) { perror("can't get MAC address"); goto err; } memcpy(&interface->hwaddr, &ifr.ifr_hwaddr.sa_data, 6); mac_to_ipv6(&interface->hwaddr, &interface->address); memset(&sin6, 0, sizeof(sin6)); sin6.sin6_port = htons(ALFRED_PORT); sin6.sin6_family = AF_INET6; memcpy(&sin6.sin6_addr, &interface->address, sizeof(sin6.sin6_addr)); sin6.sin6_scope_id = interface->scope_id; memset(&sin6_mc, 0, sizeof(sin6_mc)); sin6_mc.sin6_port = htons(ALFRED_PORT); sin6_mc.sin6_family = AF_INET6; memcpy(&sin6_mc.sin6_addr, &alfred_mcast, sizeof(sin6_mc.sin6_addr)); sin6_mc.sin6_scope_id = interface->scope_id; enable_raw_bind_capability(1); if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, interface->interface, strlen(interface->interface) + 1)) { perror("can't bind to device"); goto err; } if (setsockopt(sock_mc, SOL_SOCKET, SO_BINDTODEVICE, interface->interface, strlen(interface->interface) + 1)) { perror("can't bind to device"); goto err; } enable_raw_bind_capability(0); if (bind(sock, (struct sockaddr *)&sin6, sizeof(sin6)) < 0) { char ipstr_buf[INET6_ADDRSTRLEN]; const char *ipstr; ipstr = inet_ntop(AF_INET6, &interface->address.ipv6.s6_addr, ipstr_buf, INET6_ADDRSTRLEN); if (errno == EADDRNOTAVAIL) fprintf(stderr, "can't bind to interface %s; " "expected ipv6 address not found: %s\n", interface->interface, ipstr); else perror("can't bind"); goto err; } if (bind(sock_mc, (struct sockaddr *)&sin6_mc, sizeof(sin6_mc)) < 0) { perror("can't bind"); goto err; } memcpy(&mreq.ipv6mr_multiaddr, &alfred_mcast, sizeof(mreq.ipv6mr_multiaddr)); mreq.ipv6mr_interface = interface->scope_id; if (setsockopt(sock_mc, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq))) { perror("can't add multicast membership"); goto err; } ret = fcntl(sock, F_GETFL, 0); if (ret < 0) { perror("failed to get file status flags"); goto err; } ret = fcntl(sock, F_SETFL, ret | O_NONBLOCK); if (ret < 0) { perror("failed to set file status flags"); goto err; } ret = fcntl(sock_mc, F_GETFL, 0); if (ret < 0) { perror("failed to get file status flags"); goto err; } ret = fcntl(sock_mc, F_SETFL, ret | O_NONBLOCK); if (ret < 0) { perror("failed to set file status flags"); goto err; } ev.events = EPOLLIN; ev.data.ptr = &interface->netsock_epoll; interface->netsock_epoll.handler = netsock_handle_event; if (epoll_ctl(globals->epollfd, EPOLL_CTL_ADD, sock, &ev) == -1) { perror("Failed to add epoll for netsock"); goto err; } ev.events = EPOLLIN; ev.data.ptr = &interface->netsock_mcast_epoll; interface->netsock_mcast_epoll.handler = netsock_mcast_handle_event; if (epoll_ctl(globals->epollfd, EPOLL_CTL_ADD, sock_mc, &ev) == -1) { perror("Failed to add epoll for netsock_mcast"); goto err; } interface->netsock = sock; interface->netsock_mcast = sock_mc; return 0; err: close(sock); close(sock_mc); return -1; } static int netsock_open4(struct globals *globals, struct interface *interface) { int sock; int sock_mc; struct sockaddr_in sin4, sin_mc; struct epoll_event ev; struct ip_mreq mreq; struct ifreq ifr; int ret; interface->netsock = -1; interface->netsock_mcast = -1; sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock < 0) { perror("ipv4: can't open socket"); return -1; } sock_mc = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock_mc < 0) { close(sock); perror("ipv4: can't open mc socket"); return -1; } memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, interface->interface, IFNAMSIZ); ifr.ifr_name[IFNAMSIZ - 1] = '\0'; if (ioctl(sock_mc, SIOCGIFHWADDR, &ifr) == -1) { perror("ipv4: can't get MAC address"); goto err; } memcpy(&interface->hwaddr, &ifr.ifr_hwaddr.sa_data, 6); memset(&sin4, 0, sizeof(sin4)); sin4.sin_port = htons(ALFRED_PORT); sin4.sin_family = AF_INET; sin4.sin_addr.s_addr = INADDR_ANY; memset(&sin_mc, 0, sizeof(sin_mc)); sin_mc.sin_port = htons(ALFRED_PORT); sin_mc.sin_family = AF_INET; memcpy(&sin_mc.sin_addr, &alfred_mcast, sizeof(sin_mc.sin_addr)); enable_raw_bind_capability(1); if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, interface->interface, strlen(interface->interface) + 1)) { perror("ipv4: can't bind to device"); goto err; } if (setsockopt(sock_mc, SOL_SOCKET, SO_BINDTODEVICE, interface->interface, strlen(interface->interface) + 1)) { perror("ipv4: can't bind to device"); goto err; } enable_raw_bind_capability(0); ret = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &ret, sizeof(ret)) < 0) { perror("ipv4: can't set reuse flag"); goto err; } if (setsockopt(sock_mc, SOL_SOCKET, SO_REUSEADDR, &ret, sizeof(ret)) < 0) { perror("ipv4: can't set mc reuse flag"); goto err; } if (bind(sock, (struct sockaddr *)&sin4, sizeof(sin4)) < 0) { perror("ipv4: can't bind"); goto err; } if (bind(sock_mc, (struct sockaddr *)&sin_mc, sizeof(sin_mc)) < 0) { perror("ipv4: can't bind mc"); goto err; } memcpy(&mreq.imr_multiaddr, &alfred_mcast.ipv4, sizeof(mreq.imr_multiaddr)); if (ioctl(sock_mc, SIOCGIFADDR, &ifr) < 0) { perror("ipv4: can't get IP address"); goto err; } mreq.imr_interface = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr; memcpy(&interface->address.ipv4, &((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr, sizeof(struct in_addr)); if (setsockopt(sock_mc, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq))) { perror("ipv4: can't add multicast membership"); goto err; } ret = fcntl(sock, F_GETFL, 0); if (ret < 0) { perror("failed to get file status flags"); goto err; } ret = fcntl(sock, F_SETFL, ret | O_NONBLOCK); if (ret < 0) { perror("failed to set file status flags"); goto err; } ret = fcntl(sock_mc, F_GETFL, 0); if (ret < 0) { perror("ipv4: failed to get file status flags"); goto err; } ret = fcntl(sock_mc, F_SETFL, ret | O_NONBLOCK); if (ret < 0) { perror("ipv4: failed to set file status flags"); goto err; } ev.events = EPOLLIN; ev.data.ptr = &interface->netsock_epoll; interface->netsock_epoll.handler = netsock_handle_event; if (epoll_ctl(globals->epollfd, EPOLL_CTL_ADD, sock, &ev) == -1) { perror("Failed to add epoll for netsock"); goto err; } ev.events = EPOLLIN; ev.data.ptr = &interface->netsock_mcast_epoll; interface->netsock_mcast_epoll.handler = netsock_mcast_handle_event; if (epoll_ctl(globals->epollfd, EPOLL_CTL_ADD, sock_mc, &ev) == -1) { perror("Failed to add epoll for netsock_mcast"); goto err; } interface->netsock = sock; interface->netsock_mcast = sock_mc; return 0; err: close(sock); close(sock_mc); return -1; } int netsock_open_all(struct globals *globals) { int num_socks = 0; int ret; struct interface *interface; list_for_each_entry(interface, &globals->interfaces, list) { if (globals->ipv4mode) ret = netsock_open4(globals, interface); else ret = netsock_open(globals, interface); if (ret >= 0) num_socks++; } return num_socks; } size_t netsocket_count_interfaces(struct globals *globals) { struct interface *interface; size_t count = 0; list_for_each_entry(interface, &globals->interfaces, list) count++; return count; } void netsock_reopen(struct globals *globals) { struct interface *interface; list_for_each_entry(interface, &globals->interfaces, list) { if (interface->netsock < 0) { if (globals->ipv4mode) netsock_open4(globals, interface); else netsock_open(globals, interface); } } } int netsock_own_address(const struct globals *globals, const alfred_addr *address) { struct interface *interface; list_for_each_entry(interface, &globals->interfaces, list) { if (0 == memcmp(address, &interface->address, sizeof(*address))) return 1; } return 0; } alfred-2025.1/packet.h000066400000000000000000000173141500012154500144570ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright (C) B.A.T.M.A.N. contributors: * * Simon Wunderlich, Sven Eckelmann * * License-Filename: LICENSES/preferred/MIT */ #ifndef _ALFRED_PACKET_H #define _ALFRED_PACKET_H #include /* IFNAMSIZ */ #define __packed __attribute__ ((packed)) /* basic blocks */ /** * struct alfred_tlv - Type (Version) Length part of a TLV * @type: Type of the data * @version: Version of the data * @length: Length of the data without the alfred_tlv header */ struct alfred_tlv { uint8_t type; uint8_t version; uint16_t length; } __packed; /** * struct alfred_data - Data block header * @source: Mac address of the original source of the data * @header: TLV-header for the data * @data: "length" number of bytes followed by the header */ struct alfred_data { uint8_t source[ETH_ALEN]; struct alfred_tlv header; /* flexible data block */ __extension__ uint8_t data[0]; } __packed; /** * struct alfred_transaction_mgmt - Transaction Mgmt block for multiple packets * @id: random identificator used for this transaction * @seqno: Number of packet inside a transaction */ struct alfred_transaction_mgmt { uint16_t id; uint16_t seqno; } __packed; /** * enum alfred_packet_type - Types of packet stored in the main alfred_tlv * @ALFRED_PUSH_DATA: Packet is an alfred_push_data_v* * @ALFRED_ANNOUNCE_PRIMARY: Packet is an alfred_announce_primary_v* * @ALFRED_REQUEST: Packet is an alfred_request_v* * @ALFRED_STATUS_TXEND: Transaction was finished by sender * @ALFRED_STATUS_ERROR: Error was detected during the transaction * @ALFRED_MODESWITCH: Switch between different operation modes * @ALFRED_CHANGE_INTERFACE: Change the listening interface * @ALFRED_EVENT_REGISTER: Request to be notified about alfred update events * @ALFRED_EVENT_NOTIFY: Data record update has been received */ enum alfred_packet_type { ALFRED_PUSH_DATA = 0, ALFRED_ANNOUNCE_PRIMARY = 1, ALFRED_REQUEST = 2, ALFRED_STATUS_TXEND = 3, ALFRED_STATUS_ERROR = 4, ALFRED_MODESWITCH = 5, ALFRED_CHANGE_INTERFACE = 6, ALFRED_CHANGE_BAT_IFACE = 7, ALFRED_SERVER_STATUS = 8, ALFRED_EVENT_REGISTER = 9, ALFRED_EVENT_NOTIFY = 10, }; /* packets */ /** * struct alfred_push_data_v0 - Packet to push data blocks to another * @header: TLV header describing the complete packet * @tx: Transaction identificator and sequence number of packet * @data: multiple "alfred_data" blocks of arbitrary size (accumulated size * stored in "header.length") * * alfred_push_data_v0 packets are always sent using unicast */ struct alfred_push_data_v0 { struct alfred_tlv header; struct alfred_transaction_mgmt tx; /* flexible data block */ __extension__ struct alfred_data data[0]; } __packed; /** * struct alfred_announce_primary_v0 - Hello packet sent by an alfred primary * @header: TLV header describing the complete packet * * Each alfred daemon running in primary mode sends it using multicast. The * receiver has to calculate the source using the network header */ struct alfred_announce_primary_v0 { struct alfred_tlv header; } __packed; /** * struct alfred_request_v0 - Request for a specific type * @header: TLV header describing the complete packet * @requested_type: data type which is requested * @tx_id: random identificator used for this transaction * * Sent as unicast to the node storing it */ struct alfred_request_v0 { struct alfred_tlv header; uint8_t requested_type; uint16_t tx_id; } __packed; /** * enum alfred_modeswitch_type - Mode of the daemon * @ALFRED_MODESWITCH_SECONDARY: see OPMODE_SECONDARY * @ALFRED_MODESWITCH_PRIMARY: see OPMODE_PRIMARY */ enum alfred_modeswitch_type { ALFRED_MODESWITCH_SECONDARY = 0, ALFRED_MODESWITCH_PRIMARY = 1, }; /** * struct alfred_modeswitch_v0 - Request for a specific type * @header: TLV header describing the complete packet * @mode: data type which is requested * * Sent to the daemon by client */ struct alfred_modeswitch_v0 { struct alfred_tlv header; uint8_t mode; } __packed; /** * struct alfred_change_interface_v0 - Request to change the interface * @header: TLV header describing the complete packet * @ifaces: interface list (comma separated) to be changed to * * Sent to the daemon by client */ struct alfred_change_interface_v0 { struct alfred_tlv header; char ifaces[IFNAMSIZ * 16]; } __packed; /** * struct alfred_change_bat_iface_v0 - Request to change the * batman-adv interface * @header: TLV header describing the complete packet * @bat_iface: interface to be changed to * * Sent to the daemon by client */ struct alfred_change_bat_iface_v0 { struct alfred_tlv header; char bat_iface[IFNAMSIZ]; }; /** * enum alfred_packet_type - Types of packet stored in the main alfred_tlv * @ALFRED_SERVER_MODE: Contains alfred mode information* * @ALFRED_SERVER_NET_IFACE: Contains alfred network interface information* * @ALFRED_SERVER_BAT_IFACE: Contains alfred batman interface information* */ enum alfred_server_status_type { ALFRED_SERVER_OP_MODE = 0, ALFRED_SERVER_NET_IFACE = 1, ALFRED_SERVER_BAT_IFACE = 2, }; /** * struct alfred_server_status_req_v0 - server status request * @header: TLV header describing the complete packet * * Sent to the daemon by client */ struct alfred_server_status_req_v0 { struct alfred_tlv header; } __packed; /** * struct alfred_server_status_op_mode_v0 - server op mode status information * @op_mode: active op mode * * Sent to the client by daemon in response to status request */ struct alfred_server_status_op_mode_v0 { struct alfred_tlv header; uint8_t mode; } __packed; /** * struct alfred_server_status_net_iface_v0 - server net iface status information * @net_iface: configured network interface * @active: network interface active/inactive status info * * Sent to the client by daemon in response to status request */ struct alfred_server_status_net_iface_v0 { struct alfred_tlv header; char net_iface[IFNAMSIZ]; uint8_t active; } __packed; /** * struct alfred_server_status_bat_iface_v0 - server bat iface status information * @op_mode: configured batman interface * * Sent to the client by daemon in response to status request */ struct alfred_server_status_bat_iface_v0 { struct alfred_tlv header; char bat_iface[IFNAMSIZ]; } __packed; /** * struct alfred_server_status_rep_v0 - server status reply * @header: TLV header describing the complete packet * * Sent by the daemon to client in response to status request */ struct alfred_server_status_rep_v0 { struct alfred_tlv header; } __packed; /** * struct alfred_event_register_v0 - event registration message * @header: TLV header describing the complete packet * * Sent by the client to daemon to register for data record updates */ struct alfred_event_register_v0 { struct alfred_tlv header; } __packed; /** * struct alfred_event_notify_v0 - event notification message * @header: TLV header describing the complete packet * @type: Type of the data triggering the event * @source: Mac address of the original source of the data * * Sent by the daemon to client on data record update */ struct alfred_event_notify_v0 { struct alfred_tlv header; uint8_t type; uint8_t source[ETH_ALEN]; } __packed; /** * struct alfred_status_v0 - Status info of a transaction * @header: TLV header describing the complete packet * @tx: Transaction identificator and sequence number of packet * * The sequence number has a special meaning. Failure status packets use * it to store the error code. Success status packets store the number of * transferred packets in it. * * Sent as unicast to the node requesting the data */ struct alfred_status_v0 { struct alfred_tlv header; struct alfred_transaction_mgmt tx; } __packed; #define ALFRED_VERSION 0 #define ALFRED_PORT 0x4242 #define ALFRED_MAX_RESERVED_TYPE 64 #define ALFRED_NUM_TYPES 256 #endif alfred-2025.1/recv.c000066400000000000000000000252331500012154500141410ustar00rootroot00000000000000// 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 #include #include #include #include #include "alfred.h" #include "batadv_query.h" #include "hash.h" #include "list.h" #include "packet.h" static int finish_alfred_push_data(struct globals *globals, struct ether_addr mac, struct alfred_push_data_v0 *push) { unsigned int len, data_len; bool new_entry_created; struct alfred_data *data; struct dataset *dataset; uint8_t *pos; /* test already done in process_alfred_push_data */ len = ntohs(push->header.length); if (len < sizeof(*push) - sizeof(push->header)) return -1; len -= sizeof(*push) - sizeof(push->header); pos = (uint8_t *)push->data; while (len >= sizeof(*data)) { data = (struct alfred_data *)pos; data_len = ntohs(data->header.length); /* check if enough data is available */ if (data_len + sizeof(*data) > len) break; new_entry_created = false; dataset = hash_find(globals->data_hash, data); if (!dataset) { dataset = malloc(sizeof(*dataset)); if (!dataset) goto err; dataset->buf = NULL; dataset->data_source = SOURCE_SYNCED; memcpy(&dataset->data, data, sizeof(*data)); if (hash_add(globals->data_hash, dataset)) { free(dataset); goto err; } new_entry_created = true; } /* don't overwrite our own data */ if (dataset->data_source == SOURCE_LOCAL) goto skip_data; clock_gettime(CLOCK_MONOTONIC, &dataset->last_seen); /* check that data was changed */ if (new_entry_created || dataset->data.header.length != data_len || memcmp(dataset->buf, data->data, data_len) != 0) { changed_data_type(globals, data->header.type); unix_sock_event_notify(globals, data->header.type, data->source); } /* free old buffer */ if (dataset->buf) { free(dataset->buf); dataset->data.header.length = 0; } dataset->buf = malloc(data_len); /* that's not good */ if (!dataset->buf) goto err; dataset->data.header.length = data_len; dataset->data.header.version = data->header.version; memcpy(dataset->buf, data->data, data_len); /* if the sender is also the the source of the dataset, we * got a first hand dataset. */ if (memcmp(&mac, data->source, ETH_ALEN) == 0) dataset->data_source = SOURCE_FIRST_HAND; else dataset->data_source = SOURCE_SYNCED; skip_data: pos += (sizeof(*data) + data_len); len -= (sizeof(*data) + data_len); } return 0; err: return -1; } struct transaction_head * transaction_add(struct globals *globals, struct ether_addr mac, uint16_t id) { struct transaction_head *head; head = malloc(sizeof(*head)); if (!head) return NULL; head->server_addr = mac; head->id = id; head->requested_type = 0; head->txend_packets = 0; head->num_packet = 0; head->client_socket = -1; clock_gettime(CLOCK_MONOTONIC, &head->last_rx_time); INIT_LIST_HEAD(&head->packet_list); if (hash_add(globals->transaction_hash, head)) { free(head); return NULL; } return head; } struct transaction_head *transaction_clean(struct globals *globals, struct transaction_head *head) { struct transaction_packet *transaction_packet, *safe; list_for_each_entry_safe(transaction_packet, safe, &head->packet_list, list) { list_del(&transaction_packet->list); free(transaction_packet->push); free(transaction_packet); } hash_remove(globals->transaction_hash, head); return head; } static int finish_alfred_transaction(struct globals *globals, struct transaction_head *head, struct ether_addr mac) { struct transaction_packet *transaction_packet, *safe; /* finish when all packets received */ if (!transaction_finished(head)) return 0; list_for_each_entry_safe(transaction_packet, safe, &head->packet_list, list) { finish_alfred_push_data(globals, mac, transaction_packet->push); list_del(&transaction_packet->list); free(transaction_packet->push); free(transaction_packet); } transaction_clean(globals, head); if (head->client_socket < 0) free(head); else unix_sock_req_data_finish(globals, head); return 1; } static int process_alfred_push_data(struct globals *globals, struct interface *interface, alfred_addr *source, struct alfred_push_data_v0 *push) { unsigned int len; struct ether_addr mac; int ret; struct transaction_head search, *head; struct transaction_packet *transaction_packet; int found; if (globals->ipv4mode) ret = ipv4_to_mac(interface, source, &mac); else ret = ipv6_to_mac(source, &mac); if (ret < 0) goto err; len = ntohs(push->header.length); if (len < sizeof(*push) - sizeof(push->header)) goto err; search.server_addr = mac; search.id = ntohs(push->tx.id); head = hash_find(globals->transaction_hash, &search); if (!head) { /* secondary must create the transactions to be able to * correctly wait for it */ if (globals->opmode != OPMODE_PRIMARY) goto err; head = transaction_add(globals, mac, ntohs(push->tx.id)); if (!head) goto err; } clock_gettime(CLOCK_MONOTONIC, &head->last_rx_time); found = 0; list_for_each_entry(transaction_packet, &head->packet_list, list) { if (transaction_packet->push->tx.seqno == push->tx.seqno) { found = 1; break; } } /* it seems the packet was duplicated */ if (found) return 0; transaction_packet = malloc(sizeof(*transaction_packet)); if (!transaction_packet) goto err; transaction_packet->push = malloc(len + sizeof(push->header)); if (!transaction_packet->push) { free(transaction_packet); goto err; } memcpy(transaction_packet->push, push, len + sizeof(push->header)); list_add_tail(&transaction_packet->list, &head->packet_list); head->num_packet++; finish_alfred_transaction(globals, head, mac); return 0; err: return -1; } static int process_alfred_announce_primary(struct globals *globals, struct interface *interface, alfred_addr *source, struct alfred_announce_primary_v0 *announce) { struct server *server; struct ether_addr mac; int ret; if (globals->ipv4mode) ret = ipv4_to_mac(interface, source, &mac); else ret = ipv6_to_mac(source, &mac); if (ret < 0) return -1; if (announce->header.version != ALFRED_VERSION) return -1; /* skip header.length check because "announce" has no extra fields */ BUILD_BUG_ON(sizeof(*announce) - sizeof(announce->header) != 0); server = hash_find(interface->server_hash, &mac); if (!server) { server = malloc(sizeof(*server)); if (!server) return -1; memcpy(&server->hwaddr, &mac, ETH_ALEN); memcpy(&server->address, source, sizeof(*source)); server->tq = 0; if (hash_add(interface->server_hash, server)) { free(server); return -1; } } clock_gettime(CLOCK_MONOTONIC, &server->last_seen); return 0; } static int process_alfred_request(struct globals *globals, struct interface *interface, alfred_addr *source, struct alfred_request_v0 *request) { unsigned int len; len = ntohs(request->header.length); if (request->header.version != ALFRED_VERSION) return -1; if (len < (sizeof(*request) - sizeof(request->header))) return -1; push_data(globals, interface, source, SOURCE_SYNCED, request->requested_type, request->tx_id); return 0; } static int process_alfred_status_txend(struct globals *globals, struct interface *interface, alfred_addr *source, struct alfred_status_v0 *request) { struct transaction_head search, *head; struct ether_addr mac; unsigned int len; int ret; len = ntohs(request->header.length); if (request->header.version != ALFRED_VERSION) return -1; if (len < (sizeof(*request) - sizeof(request->header))) return -1; if (globals->ipv4mode) ret = ipv4_to_mac(interface, source, &mac); else ret = ipv6_to_mac(source, &mac); if (ret < 0) return -1; search.server_addr = mac; search.id = ntohs(request->tx.id); head = hash_find(globals->transaction_hash, &search); if (!head) { /* secondary must create the transactions to be able to * correctly wait for it */ if (globals->opmode != OPMODE_PRIMARY) goto err; /* 0-packet txend for unknown transaction */ if (ntohs(request->tx.seqno) == 0) goto err; head = transaction_add(globals, mac, ntohs(request->tx.id)); if (!head) goto err; } clock_gettime(CLOCK_MONOTONIC, &head->last_rx_time); head->txend_packets = ntohs(request->tx.seqno); finish_alfred_transaction(globals, head, mac); return 0; err: return -1; } int recv_alfred_packet(struct globals *globals, struct interface *interface, int recv_sock) { uint8_t buf[MAX_PAYLOAD]; ssize_t length; struct alfred_tlv *packet; struct sockaddr_in *source; struct sockaddr_in source4; struct sockaddr_in6 source6; socklen_t sourcelen; alfred_addr alfred_source; if (interface->netsock < 0) return -1; if (globals->ipv4mode) { source = (struct sockaddr_in *)&source4; sourcelen = sizeof(source4); } else { source = (struct sockaddr_in *)&source6; sourcelen = sizeof(source6); } length = recvfrom(recv_sock, buf, sizeof(buf), 0, (struct sockaddr *)source, &sourcelen); if (length <= 0) { perror("read from network socket failed"); return -1; } packet = (struct alfred_tlv *)buf; memset(&alfred_source, 0, sizeof(alfred_source)); if (globals->ipv4mode) { memcpy(&alfred_source, &source4.sin_addr, sizeof(source4.sin_addr)); } else { memcpy(&alfred_source, &source6.sin6_addr, sizeof(source6.sin6_addr)); /* drop packets not sent over link-local ipv6 */ if (!is_ipv6_eui64(&alfred_source.ipv6)) return -1; } /* drop packets from ourselves */ if (netsock_own_address(globals, &alfred_source)) return -1; /* drop truncated packets */ if (length < (int)sizeof(*packet) || length < (int)(ntohs(packet->length) + sizeof(*packet))) return -1; /* drop incompatible packet */ if (packet->version != ALFRED_VERSION) return -1; switch (packet->type) { case ALFRED_PUSH_DATA: process_alfred_push_data(globals, interface, &alfred_source, (struct alfred_push_data_v0 *)packet); break; case ALFRED_ANNOUNCE_PRIMARY: process_alfred_announce_primary(globals, interface, &alfred_source, (struct alfred_announce_primary_v0 *)packet); break; case ALFRED_REQUEST: process_alfred_request(globals, interface, &alfred_source, (struct alfred_request_v0 *)packet); break; case ALFRED_STATUS_TXEND: process_alfred_status_txend(globals, interface, &alfred_source, (struct alfred_status_v0 *)packet); break; default: /* unknown packet type */ return -1; } return 0; } alfred-2025.1/send.c000066400000000000000000000126551500012154500141370ustar00rootroot00000000000000// 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 #include "alfred.h" #include "hash.h" #include "packet.h" #include "list.h" int announce_primary(struct globals *globals) { struct alfred_announce_primary_v0 announcement; struct interface *interface; list_for_each_entry(interface, &globals->interfaces, list) { announcement.header.type = ALFRED_ANNOUNCE_PRIMARY; announcement.header.version = ALFRED_VERSION; announcement.header.length = htons(0); send_alfred_packet(globals, interface, &alfred_mcast, &announcement, sizeof(announcement)); } return 0; } int push_data(struct globals *globals, struct interface *interface, alfred_addr *destination, enum data_source max_source_level, int type_filter, uint16_t tx_id) { struct hash_it_t *hashit = NULL; uint8_t buf[MAX_PAYLOAD]; struct alfred_push_data_v0 *push; struct alfred_data *data; uint16_t total_length = 0; size_t tlv_length; uint16_t seqno = 0; uint16_t length; struct alfred_status_v0 status_end; push = (struct alfred_push_data_v0 *)buf; push->header.type = ALFRED_PUSH_DATA; push->header.version = ALFRED_VERSION; push->tx.id = tx_id; while (NULL != (hashit = hash_iterate(globals->data_hash, hashit))) { struct dataset *dataset = hashit->bucket->data; if (dataset->data_source > max_source_level) continue; if (type_filter >= 0 && dataset->data.header.type != type_filter) continue; /* would the packet be too big? send so far aggregated data * first */ if (total_length + dataset->data.header.length + sizeof(*data) > MAX_PAYLOAD - sizeof(*push)) { /* is there any data to send? */ if (total_length == 0) continue; tlv_length = total_length; tlv_length += sizeof(*push) - sizeof(push->header); push->header.length = htons(tlv_length); push->tx.seqno = htons(seqno++); send_alfred_packet(globals, interface, destination, push, sizeof(*push) + total_length); total_length = 0; } /* still too large? - should never happen */ if (total_length + dataset->data.header.length + sizeof(*data) > MAX_PAYLOAD - sizeof(*push)) continue; data = (struct alfred_data *) (buf + sizeof(*push) + total_length); memcpy(data, &dataset->data, sizeof(*data)); data->header.length = htons(data->header.length); memcpy(data->data, dataset->buf, dataset->data.header.length); total_length += dataset->data.header.length + sizeof(*data); } /* send the final packet */ if (total_length) { tlv_length = total_length; tlv_length += sizeof(*push) - sizeof(push->header); push->header.length = htons(tlv_length); push->tx.seqno = htons(seqno++); send_alfred_packet(globals, interface, destination, push, sizeof(*push) + total_length); } /* send transaction txend packet */ if (seqno > 0 || type_filter != NO_FILTER) { status_end.header.type = ALFRED_STATUS_TXEND; status_end.header.version = ALFRED_VERSION; length = sizeof(status_end) - sizeof(status_end.header); status_end.header.length = htons(length); status_end.tx.id = tx_id; status_end.tx.seqno = htons(seqno); send_alfred_packet(globals, interface, destination, &status_end, sizeof(status_end)); } return 0; } int sync_data(struct globals *globals) { struct hash_it_t *hashit = NULL; struct interface *interface; /* send local data and data from our clients to (all) other servers */ list_for_each_entry(interface, &globals->interfaces, list) { while (NULL != (hashit = hash_iterate(interface->server_hash, hashit))) { struct server *server = hashit->bucket->data; push_data(globals, interface, &server->address, SOURCE_FIRST_HAND, NO_FILTER, get_random_id()); } } return 0; } int push_local_data(struct globals *globals) { struct interface *interface; /* no server - yet */ if (!globals->best_server) return -1; list_for_each_entry(interface, &globals->interfaces, list) { push_data(globals, interface, &globals->best_server->address, SOURCE_LOCAL, NO_FILTER, get_random_id()); } return 0; } ssize_t send_alfred_packet(struct globals *globals, struct interface *interface, const alfred_addr *dest, void *buf, int length) { ssize_t ret; struct sockaddr *dest_addr; struct sockaddr_in6 dest_addr6; struct sockaddr_in dest_addr4; socklen_t slen; if (globals->ipv4mode) { memset(&dest_addr4, 0, sizeof(dest_addr4)); dest_addr4.sin_family = AF_INET; dest_addr4.sin_port = htons(ALFRED_PORT); memcpy(&dest_addr4.sin_addr, &dest->ipv4, sizeof(dest->ipv4)); slen = sizeof(struct sockaddr_in); dest_addr = (struct sockaddr *)&dest_addr4; } else { memset(&dest_addr6, 0, sizeof(dest_addr6)); dest_addr6.sin6_family = AF_INET6; dest_addr6.sin6_port = htons(ALFRED_PORT); dest_addr6.sin6_scope_id = interface->scope_id; memcpy(&dest_addr6.sin6_addr, &dest->ipv6, sizeof(dest->ipv6)); slen = sizeof(struct sockaddr_in6); dest_addr = (struct sockaddr *)&dest_addr6; } if (interface->netsock < 0) return 0; ret = sendto(interface->netsock, buf, length, 0, dest_addr, slen); if (ret == -EPERM) { perror("Error during sent"); close(interface->netsock); close(interface->netsock_mcast); interface->netsock = -1; interface->netsock_mcast = -1; } return ret; } alfred-2025.1/server.c000066400000000000000000000273651500012154500145200ustar00rootroot00000000000000// 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 #include #include #include #include #include #include #include #include #include #include #include "alfred.h" #include "bitops.h" #include "batadv_query.h" #include "hash.h" #include "list.h" static int data_compare(void *d1, void *d2) { /* compare source and type */ return ((memcmp(d1, d2, ETH_ALEN + 1) == 0) ? 1 : 0); } static int data_choose(void *d1, int size) { unsigned char *key = d1; uint32_t hash = 0; size_t i; for (i = 0; i < ETH_ALEN + 1; i++) { hash += key[i]; hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash % size; } static int tx_compare(void *d1, void *d2) { struct transaction_head *txh1 = d1; struct transaction_head *txh2 = d2; if (memcmp(&txh1->server_addr, &txh2->server_addr, sizeof(txh1->server_addr)) == 0 && txh1->id == txh2->id) return 1; else return 0; } static int tx_choose(void *d1, int size) { struct transaction_head *txh1 = d1; unsigned char *key = (unsigned char *)&txh1->server_addr; uint32_t hash = 0; size_t i; for (i = 0; i < ETH_ALEN; i++) { hash += key[i]; hash += (hash << 10); hash ^= (hash >> 6); } hash += txh1->id; hash += (hash << 10); hash ^= (hash >> 6); hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash % size; } static int create_hashes(struct globals *globals) { globals->data_hash = hash_new(128, data_compare, data_choose); globals->transaction_hash = hash_new(64, tx_compare, tx_choose); if (!globals->data_hash || !globals->transaction_hash) return -1; return 0; } static int set_best_server(struct globals *globals) { struct hash_it_t *hashit = NULL; struct server *best_server = NULL; int best_tq = -1; struct interface *interface; list_for_each_entry(interface, &globals->interfaces, list) { while (NULL != (hashit = hash_iterate(interface->server_hash, hashit))) { struct server *server = hashit->bucket->data; if (server->tq > best_tq) { best_tq = server->tq; best_server = server; } } } globals->best_server = best_server; return 0; } void changed_data_type(struct globals *globals, uint8_t arg) { if (!globals->update_command) return; set_bit(arg, globals->changed_data_types); } static int purge_data(struct globals *globals) { struct hash_it_t *hashit = NULL; struct timespec now, diff; struct interface *interface; clock_gettime(CLOCK_MONOTONIC, &now); while (NULL != (hashit = hash_iterate(globals->data_hash, hashit))) { struct dataset *dataset = hashit->bucket->data; time_diff(&now, &dataset->last_seen, &diff); if (diff.tv_sec < ALFRED_DATA_TIMEOUT) continue; changed_data_type(globals, dataset->data.header.type); hash_remove_bucket(globals->data_hash, hashit); free(dataset->buf); free(dataset); } list_for_each_entry(interface, &globals->interfaces, list) { while (NULL != (hashit = hash_iterate(interface->server_hash, hashit))) { struct server *server = hashit->bucket->data; time_diff(&now, &server->last_seen, &diff); if (diff.tv_sec < ALFRED_SERVER_TIMEOUT) continue; if (globals->best_server == server) globals->best_server = NULL; hash_remove_bucket(interface->server_hash, hashit); free(server); } } if (!globals->best_server) set_best_server(globals); while ((hashit = hash_iterate(globals->transaction_hash, hashit))) { struct transaction_head *head = hashit->bucket->data; time_diff(&now, &head->last_rx_time, &diff); if (diff.tv_sec < ALFRED_REQUEST_TIMEOUT) continue; hash_remove_bucket(globals->transaction_hash, hashit); transaction_clean(globals, head); if (head->client_socket < 0) free(head); else unix_sock_req_data_finish(globals, head); } return 0; } static void update_server_info(struct globals *globals) { struct hash_it_t *hashit = NULL; struct interface *interface; struct ether_addr *macaddr; struct hashtable_t *tg_hash = NULL; struct hashtable_t *orig_hash = NULL; /* TQ is not used for primary sync mode */ if (globals->opmode == OPMODE_PRIMARY) return; if (!is_iface_disabled(globals->mesh_iface)) { tg_hash = tg_hash_new(globals->mesh_iface); if (!tg_hash) { fprintf(stderr, "Failed to create translation hash\n"); return; } orig_hash = orig_hash_new(globals->mesh_iface); if (!orig_hash) { fprintf(stderr, "Failed to create originator hash\n"); goto free_tg_hash; } } list_for_each_entry(interface, &globals->interfaces, list) { while (NULL != (hashit = hash_iterate(interface->server_hash, hashit))) { struct server *server = hashit->bucket->data; if (!orig_hash) { server->tq = 255; continue; } macaddr = translate_mac(tg_hash, &server->hwaddr); if (macaddr) server->tq = get_tq(orig_hash, macaddr); else server->tq = 0; } } set_best_server(globals); if (orig_hash) orig_hash_free(orig_hash); free_tg_hash: if (tg_hash) tg_hash_free(tg_hash); } static void check_if_socket(struct interface *interface, struct globals *globals) { int sock; struct ifreq ifr; if (interface->netsock < 0) return; sock = socket(PF_INET, SOCK_DGRAM, 0); if (sock < 0) { perror("can't open socket"); return; } memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, interface->interface, IFNAMSIZ); ifr.ifr_name[IFNAMSIZ - 1] = '\0'; if (ioctl(sock, SIOCGIFINDEX, &ifr) == -1) { perror("can't get interface, closing netsock"); goto close; } if (!globals->ipv4mode && (interface->scope_id != (uint32_t)ifr.ifr_ifindex)) { fprintf(stderr, "iface index changed from %"PRIu32" to %d, closing netsock\n", interface->scope_id, ifr.ifr_ifindex); goto close; } if (ioctl(sock, SIOCGIFHWADDR, &ifr) == -1) { perror("can't get MAC address, closing netsock"); goto close; } if (memcmp(&interface->hwaddr, &ifr.ifr_hwaddr.sa_data, 6) != 0) { fprintf(stderr, "iface mac changed, closing netsock\n"); goto close; } close(sock); return; close: close(interface->netsock); close(interface->netsock_mcast); interface->netsock = -1; interface->netsock_mcast = -1; close(sock); } static void check_if_sockets(struct globals *globals) { struct timespec now, diff; struct interface *interface; clock_gettime(CLOCK_MONOTONIC, &now); time_diff(&now, &globals->if_check, &diff); if (diff.tv_sec < ALFRED_IF_CHECK_INTERVAL) return; globals->if_check = now; list_for_each_entry(interface, &globals->interfaces, list) check_if_socket(interface, globals); } static void execute_update_command(struct globals *globals) { pid_t script_pid; size_t command_len; char *command; size_t data_type; size_t changed_data_type_count; /* data type is limited by ALFRED_NUM_TYPES to 255 (3 chars), plus * 1x space for appending + terminating null byte */ char buf[5]; if (!globals->update_command) return; if (bitmap_empty(globals->changed_data_types, ALFRED_NUM_TYPES)) return; changed_data_type_count = bitmap_weight(globals->changed_data_types, ALFRED_NUM_TYPES); /* length of script + 4 bytes per data type (space +3 chars) * + 1 for terminating null byte */ command_len = strlen(globals->update_command); command_len += 4 * changed_data_type_count + 1; command = malloc(command_len); if (!command) return; strncpy(command, globals->update_command, command_len - 1); command[command_len - 1] = '\0'; for_each_set_bit (data_type, globals->changed_data_types, ALFRED_NUM_TYPES) { /* append the datatype to command line */ snprintf(buf, sizeof(buf), " %zu", data_type); strncat(command, buf, command_len - strlen(command) - 1); } bitmap_zero(globals->changed_data_types, ALFRED_NUM_TYPES); printf("executing: %s\n", command); script_pid = fork(); if (script_pid == 0) { system(command); exit(0); } free(command); } static void process_events(struct globals *globals) { /* WARNING only processing one event because it could be that * netsock + their fds are getting deleted */ struct epoll_event events[1]; struct epoll_handle *handle; int nfds; nfds = epoll_wait(globals->epollfd, events, sizeof(events) / sizeof(*events), -1); if (nfds == -1) { if (errno == EINTR) return; perror("main loop select failed ..."); return; } for (int i = 0; i < nfds; i++) { handle = (struct epoll_handle *)events[i].data.ptr; handle->handler(globals, handle, &events[i]); } } static void sync_period_timer(struct globals *globals, struct epoll_handle *handle __unused, struct epoll_event *ev __unused) { struct timespec now; uint64_t timer_exp; read(globals->check_timerfd, &timer_exp, sizeof(timer_exp)); clock_gettime(CLOCK_MONOTONIC, &now); if (globals->opmode == OPMODE_PRIMARY) { /* we are a primary */ printf("[%ld.%09ld] announce primary ...\n", now.tv_sec, now.tv_nsec); announce_primary(globals); sync_data(globals); } else { /* send local data to server */ update_server_info(globals); push_local_data(globals); } purge_data(globals); check_if_sockets(globals); execute_update_command(globals); } static int create_sync_period_timer(struct globals *globals) { struct itimerspec sync_timer; struct epoll_event ev; int ret; globals->check_timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); if (globals->check_timerfd < 0) { perror("Failed to create periodic timer"); return -1; } sync_timer.it_value = globals->sync_period; sync_timer.it_interval = globals->sync_period; ret = timerfd_settime(globals->check_timerfd, 0, &sync_timer, NULL); if (ret < 0) { perror("Failed to arm synchronization timer"); return -1; } ev.events = EPOLLIN; ev.data.ptr = &globals->check_epoll; globals->check_epoll.handler = sync_period_timer; if (epoll_ctl(globals->epollfd, EPOLL_CTL_ADD, globals->check_timerfd, &ev) == -1) { perror("Failed to add epoll for check_timer"); return -1; } return 0; } int alfred_server(struct globals *globals) { size_t num_interfaces; struct timespec now; int num_socks; if (create_hashes(globals)) return -1; globals->epollfd = epoll_create1(0); if (globals->epollfd == -1) { perror("Could not create epoll for main thread"); return -1; } if (create_sync_period_timer(globals)) return -1; if (unix_sock_open_daemon(globals)) return -1; if (!is_iface_disabled(globals->net_iface)) { if (!globals->net_iface) { fprintf(stderr, "Can't start server: interface missing\n"); return -1; } netsock_set_interfaces(globals, globals->net_iface); if (list_empty(&globals->interfaces) && !globals->force) { fprintf(stderr, "Can't start server: valid interface missing\n"); return -1; } num_socks = netsock_open_all(globals); if (num_socks <= 0 && !globals->force) { fprintf(stderr, "Failed to open interfaces\n"); return -1; } num_interfaces = netsocket_count_interfaces(globals); if (num_interfaces > 1 && globals->opmode == OPMODE_SECONDARY) { fprintf(stderr, "More than one interface specified in secondary mode\n"); return -1; } } if (!is_iface_disabled(globals->mesh_iface) && batadv_interface_check(globals->mesh_iface) < 0 && !globals->force) { fprintf(stderr, "Can't start server: batman-adv interface %s not found\n", globals->mesh_iface); return -1; } clock_gettime(CLOCK_MONOTONIC, &now); globals->if_check = now; while (1) { netsock_reopen(globals); process_events(globals); } netsock_close_all(globals); unix_sock_close(globals); unix_sock_events_close_all(globals); return 0; } alfred-2025.1/unix_sock.c000066400000000000000000000430351500012154500152040ustar00rootroot00000000000000// 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 #include #include #include #include #include #include #include "alfred.h" #include "hash.h" #include "packet.h" static void unix_sock_read(struct globals *globals, struct epoll_handle *handle __unused, struct epoll_event *ev __unused); static int unix_sock_register_listener(struct globals *globals, int client_sock); int unix_sock_open_daemon(struct globals *globals) { struct sockaddr_un addr; struct epoll_event ev; unlink(globals->unix_path); globals->unix_sock = socket(AF_LOCAL, SOCK_STREAM, 0); if (globals->unix_sock < 0) { perror("can't create unix socket"); return -1; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_LOCAL; strncpy(addr.sun_path, globals->unix_path, sizeof(addr.sun_path)); addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; if (bind(globals->unix_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("can't bind unix socket"); return -1; } if (listen(globals->unix_sock, 10) < 0) { perror("can't listen on unix socket"); return -1; } ev.events = EPOLLIN; ev.data.ptr = &globals->unix_epoll; globals->unix_epoll.handler = unix_sock_read; if (epoll_ctl(globals->epollfd, EPOLL_CTL_ADD, globals->unix_sock, &ev) == -1) { perror("Failed to add epoll for check_timer"); return -1; } return 0; } int unix_sock_open_client(struct globals *globals) { struct sockaddr_un addr; globals->unix_sock = socket(AF_LOCAL, SOCK_STREAM, 0); if (globals->unix_sock < 0) { perror("can't create unix socket"); return -1; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_LOCAL; strncpy(addr.sun_path, globals->unix_path, sizeof(addr.sun_path)); addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; if (connect(globals->unix_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { close(globals->unix_sock); globals->unix_sock = -1; perror("can't connect to unix socket"); return -1; } return 0; } static int unix_sock_add_data(struct globals *globals, struct alfred_push_data_v0 *push, int client_sock) { bool new_entry_created = false; struct alfred_data *data; struct dataset *dataset; int len, data_len, ret = -1; struct interface *interface; len = ntohs(push->header.length); if (len < (int)(sizeof(*push) - sizeof(push->header))) goto err; /* subtract rest of push header */ len -= sizeof(*push) - sizeof(push->header); if (len < (int)(sizeof(*data))) goto err; data = push->data; data_len = ntohs(data->header.length); /* clients should set the source mac to 00:00:00:00:00:00 * to make the server set the source for them * * Only alfred in primary mode can accept a user defined * source addresses. Otherwise the data would not be * synced between primary servers. */ if (is_iface_disabled(globals->net_iface)) { if (!is_valid_ether_addr(data->source)) goto err; if (globals->opmode != OPMODE_PRIMARY) goto err; } else if (is_valid_ether_addr(data->source)) { interface = netsock_first_interface(globals); if (!interface) goto err; if (memcmp(data->source, &interface->hwaddr, ETH_ALEN) != 0 && globals->opmode != OPMODE_PRIMARY) goto err; } else { interface = netsock_first_interface(globals); if (!interface) goto err; memcpy(data->source, &interface->hwaddr, ETH_ALEN); } if ((int)(data_len + sizeof(*data)) > len) goto err; dataset = hash_find(globals->data_hash, data); if (!dataset) { dataset = malloc(sizeof(*dataset)); if (!dataset) goto err; dataset->buf = NULL; memcpy(&dataset->data, data, sizeof(*data)); if (hash_add(globals->data_hash, dataset)) { free(dataset); goto err; } new_entry_created = true; } dataset->data_source = SOURCE_LOCAL; clock_gettime(CLOCK_MONOTONIC, &dataset->last_seen); /* check that data was changed */ if (new_entry_created || dataset->data.header.length != data_len || memcmp(dataset->buf, data->data, data_len) != 0) unix_sock_event_notify(globals, data->header.type, data->source); /* free old buffer */ free(dataset->buf); dataset->buf = malloc(data_len); /* that's not good */ if (!dataset->buf) goto err; dataset->data.header.length = data_len; dataset->data.header.version = data->header.version; memcpy(dataset->buf, data->data, data_len); ret = 0; err: close(client_sock); return ret; } static int unix_sock_req_data_reply(struct globals *globals, int client_sock, uint16_t id, uint8_t requested_type) { int len; struct alfred_push_data_v0 *push; struct hash_it_t *hashit = NULL; uint8_t buf[MAX_PAYLOAD]; uint16_t seqno = 0, ret = 0; /* send some data back through the unix socket */ push = (struct alfred_push_data_v0 *)buf; push->header.type = ALFRED_PUSH_DATA; push->header.version = ALFRED_VERSION; push->tx.id = htons(id); while (NULL != (hashit = hash_iterate(globals->data_hash, hashit))) { struct dataset *dataset = hashit->bucket->data; struct alfred_data *data; if (dataset->data.header.type != requested_type) continue; /* too large? - should never happen */ if (dataset->data.header.length + sizeof(*data) > MAX_PAYLOAD - sizeof(*push)) continue; data = push->data; memcpy(data, &dataset->data, sizeof(*data)); data->header.length = htons(data->header.length); memcpy(data->data, dataset->buf, dataset->data.header.length); len = dataset->data.header.length + sizeof(*data); len += sizeof(*push) - sizeof(push->header); push->header.length = htons(len); push->tx.seqno = htons(seqno++); if (write(client_sock, buf, sizeof(push->header) + len) < 0) { ret = -1; hash_iterate_free(hashit); break; } } close(client_sock); return ret; } static int unix_sock_req_data(struct globals *globals, struct alfred_request_v0 *request, int client_sock) { int len; uint16_t id; struct transaction_head *head = NULL; struct interface *interface; len = ntohs(request->header.length); if (len != (sizeof(*request) - sizeof(request->header))) return -1; id = ntohs(request->tx_id); interface = netsock_first_interface(globals); /* no server to send the request to, only give back what we have now. */ if (!globals->best_server || !interface) return unix_sock_req_data_reply(globals, client_sock, id, request->requested_type); /* a primary already has data to respond with */ if (globals->opmode == OPMODE_PRIMARY) return unix_sock_req_data_reply(globals, client_sock, id, request->requested_type); head = transaction_add(globals, globals->best_server->hwaddr, id); if (!head) return -1; head->client_socket = client_sock; head->requested_type = request->requested_type; send_alfred_packet(globals, interface, &globals->best_server->address, request, sizeof(*request)); return 0; } int unix_sock_req_data_finish(struct globals *globals, struct transaction_head *head) { struct alfred_status_v0 status; int ret = 0, send_data = 1; int client_sock; uint16_t id; uint8_t requested_type; requested_type = head->requested_type; id = head->id; client_sock = head->client_socket; if (!transaction_finished(head)) send_data = 0; free(head); if (send_data) { unix_sock_req_data_reply(globals, client_sock, id, requested_type); return 0; } status.header.type = ALFRED_STATUS_ERROR; status.header.version = ALFRED_VERSION; status.header.length = htons(sizeof(status) - sizeof(status.header)); status.tx.id = htons(id); status.tx.seqno = 1; if (write(client_sock, &status, sizeof(status)) < 0) ret = -1; close(client_sock); return ret; } static int unix_sock_modesw(struct globals *globals, struct alfred_modeswitch_v0 *modeswitch, int client_sock) { int len, ret = -1; len = ntohs(modeswitch->header.length); if (len < (int)(sizeof(*modeswitch) - sizeof(modeswitch->header))) goto err; switch (modeswitch->mode) { case ALFRED_MODESWITCH_SECONDARY: if (!list_is_singular(&globals->interfaces)) goto err; globals->opmode = OPMODE_SECONDARY; break; case ALFRED_MODESWITCH_PRIMARY: globals->opmode = OPMODE_PRIMARY; break; default: goto err; } ret = 0; err: close(client_sock); return ret; } static int unix_sock_change_iface(struct globals *globals, struct alfred_change_interface_v0 *change_iface, int client_sock) { int len, ret = -1; len = ntohs(change_iface->header.length); if (len < (int)(sizeof(*change_iface) - sizeof(change_iface->header))) goto err; change_iface->ifaces[sizeof(change_iface->ifaces) - 1] = '\0'; if (globals->opmode == OPMODE_SECONDARY) { if (strstr(change_iface->ifaces, ",") != NULL) { ret = -EINVAL; fprintf(stderr, "Tried to set multiple interfaces in secondary mode\n"); goto err; } } netsock_set_interfaces(globals, change_iface->ifaces); ret = 0; err: close(client_sock); return ret; } static int unix_sock_change_bat_iface(struct globals *globals, struct alfred_change_bat_iface_v0 *change_bat_iface, int client_sock) { int len, ret = -1; len = ntohs(change_bat_iface->header.length); if (len < (int)(sizeof(*change_bat_iface) - sizeof(change_bat_iface->header))) goto err; free(globals->mesh_iface); change_bat_iface->bat_iface[sizeof(change_bat_iface->bat_iface) - 1] = '\0'; globals->mesh_iface = strdup(change_bat_iface->bat_iface); ret = 0; err: close(client_sock); return ret; } static int unix_sock_server_status(struct globals *globals, int client_sock) { struct alfred_server_status_net_iface_v0 *status_net_iface; struct alfred_server_status_bat_iface_v0 *status_bat_iface; struct alfred_server_status_op_mode_v0 *status_op_mode; struct alfred_server_status_rep_v0 *status_rep; struct interface *interface; uint8_t buf[MAX_PAYLOAD]; int ret = -1; int len = 0; /* too large? - should never happen */ if (sizeof(*status_rep) + len > sizeof(buf)) { fprintf(stderr, "ERROR: send buffer too small for server_status\n"); goto err; } status_rep = (struct alfred_server_status_rep_v0 *)buf; status_rep->header.type = ALFRED_SERVER_STATUS; status_rep->header.version = ALFRED_VERSION; len += sizeof(*status_rep); /* too large? - should never happen */ if (sizeof(*status_op_mode) + len > sizeof(buf)) { fprintf(stderr, "ERROR: send buffer too small for server_status op_mode\n"); goto err; } status_op_mode = (struct alfred_server_status_op_mode_v0 *)(buf + len); status_op_mode->header.type = ALFRED_SERVER_OP_MODE; status_op_mode->header.version = ALFRED_VERSION; status_op_mode->header.length = FIXED_TLV_LEN(*status_op_mode); switch (globals->opmode) { case OPMODE_SECONDARY: status_op_mode->mode = ALFRED_MODESWITCH_SECONDARY; break; case OPMODE_PRIMARY: status_op_mode->mode = ALFRED_MODESWITCH_PRIMARY; break; default: break; } len += sizeof(*status_op_mode); list_for_each_entry(interface, &globals->interfaces, list) { /* too large? - should never happen */ if (sizeof(*status_net_iface) + len > sizeof(buf)) { fprintf(stderr, "ERROR: send buffer too small for server_status iface\n"); goto err; } status_net_iface = (struct alfred_server_status_net_iface_v0 *)(buf + len); status_net_iface->header.type = ALFRED_SERVER_NET_IFACE; status_net_iface->header.version = ALFRED_VERSION; status_net_iface->header.length = FIXED_TLV_LEN(*status_net_iface); strncpy(status_net_iface->net_iface, interface->interface, sizeof(status_net_iface->net_iface)); status_net_iface->net_iface[sizeof(status_net_iface->net_iface) - 1] = '\0'; if (interface->netsock > -1) status_net_iface->active = 1; else status_net_iface->active = 0; len += sizeof(*status_net_iface); } /* too large? - should never happen */ if (sizeof(*status_bat_iface) + len > sizeof(buf)) { fprintf(stderr, "ERROR: send buffer too small for server_status bat_iface\n"); goto err; } status_bat_iface = (struct alfred_server_status_bat_iface_v0 *)(buf + len); status_bat_iface->header.type = ALFRED_SERVER_BAT_IFACE; status_bat_iface->header.version = ALFRED_VERSION; status_bat_iface->header.length = FIXED_TLV_LEN(*status_bat_iface); strncpy(status_bat_iface->bat_iface, globals->mesh_iface, sizeof(status_bat_iface->bat_iface)); status_bat_iface->bat_iface[sizeof(status_bat_iface->bat_iface) - 1] = '\0'; len += sizeof(*status_bat_iface); status_rep->header.length = htons(len - sizeof(status_rep->header)); ret = write(client_sock, buf, len); if (ret != len) { fprintf(stderr, "%s: only wrote %d of %d bytes: %s\n", __func__, ret, len, strerror(errno)); ret = -1; goto err; } ret = 0; err: close(client_sock); return ret; } static void unix_sock_read(struct globals *globals, struct epoll_handle *handle __unused, struct epoll_event *ev __unused) { int client_sock; struct sockaddr_un sun_addr; socklen_t sun_size = sizeof(sun_addr); struct alfred_tlv *packet; uint8_t buf[MAX_PAYLOAD]; int length, headsize; client_sock = accept(globals->unix_sock, (struct sockaddr *)&sun_addr, &sun_size); if (client_sock < 0) { perror("can't accept unix connection"); return; } /* we assume that we can instantly read here. */ length = read(client_sock, buf, sizeof(buf)); if (length <= 0) { perror("read from unix socket failed"); goto err; } /* drop too small packets */ headsize = sizeof(*packet); if (length < headsize) goto err; packet = (struct alfred_tlv *)buf; if ((length - headsize) < ((int)ntohs(packet->length))) goto err; if (packet->version != ALFRED_VERSION) goto err; switch (packet->type) { case ALFRED_PUSH_DATA: unix_sock_add_data(globals, (struct alfred_push_data_v0 *)packet, client_sock); break; case ALFRED_REQUEST: unix_sock_req_data(globals, (struct alfred_request_v0 *)packet, client_sock); break; case ALFRED_MODESWITCH: unix_sock_modesw(globals, (struct alfred_modeswitch_v0 *)packet, client_sock); break; case ALFRED_CHANGE_INTERFACE: unix_sock_change_iface(globals, (struct alfred_change_interface_v0 *)packet, client_sock); break; case ALFRED_CHANGE_BAT_IFACE: unix_sock_change_bat_iface(globals, (struct alfred_change_bat_iface_v0 *)packet, client_sock); break; case ALFRED_SERVER_STATUS: unix_sock_server_status(globals, client_sock); break; case ALFRED_EVENT_REGISTER: unix_sock_register_listener(globals, client_sock); break; default: /* unknown packet type */ goto err; } return; err: close(client_sock); } int unix_sock_close(struct globals *globals) { close(globals->unix_sock); return 0; } static void unix_sock_event_listener_free(struct event_listener *listener) { list_del(&listener->list); close(listener->fd); free(listener); } static void unix_sock_event_listener_handle(struct globals *globals __unused, struct epoll_handle *handle, struct epoll_event *ev) { struct event_listener *listener; char buff[4]; int ret; listener = container_of(handle, struct event_listener, epoll); if (ev->events & EPOLLERR) { fprintf(stderr, "Error on event listener detected: %d\n", listener->fd); unix_sock_event_listener_free(listener); return; } if (ev->events & EPOLLIN) { ret = recv(listener->fd, buff, sizeof(buff), MSG_PEEK | MSG_DONTWAIT); /* listener has hung up */ if (ret == 0) unix_sock_event_listener_free(listener); else if (ret > 0) { fprintf(stderr, "Event listener has written to socket: %d - closing\n", listener->fd); unix_sock_event_listener_free(listener); } } } static int unix_sock_register_listener(struct globals *globals, int client_sock) { struct event_listener *listener; struct epoll_event ev; int ret; ret = fcntl(client_sock, F_GETFL, 0); if (ret < 0) { perror("failed to get file status flags"); goto err; } ret = fcntl(client_sock, F_SETFL, ret | O_NONBLOCK); if (ret < 0) { perror("failed to set file status flags"); goto err; } listener = malloc(sizeof(*listener)); if (!listener) goto err; ev.events = EPOLLIN; ev.data.ptr = &listener->epoll; listener->epoll.handler = unix_sock_event_listener_handle; if (epoll_ctl(globals->epollfd, EPOLL_CTL_ADD, client_sock, &ev) == -1) { perror("Failed to add epoll for event listener"); goto err; } listener->fd = client_sock; list_add_tail(&listener->list, &globals->event_listeners); return 0; err: close(client_sock); return -1; } static void unix_sock_event_notify_listener(struct event_listener *listener, uint8_t type, const uint8_t source[ETH_ALEN]) { struct alfred_event_notify_v0 notify; int ret; notify.header.type = ALFRED_EVENT_NOTIFY; notify.header.version = ALFRED_VERSION; notify.header.length = FIXED_TLV_LEN(notify); notify.type = type; memcpy(¬ify.source, source, ETH_ALEN); ret = write(listener->fd, ¬ify, sizeof(notify)); if (ret == sizeof(notify)) return; unix_sock_event_listener_free(listener); } void unix_sock_events_close_all(struct globals *globals) { struct event_listener *listener, *tmp; list_for_each_entry_safe(listener, tmp, &globals->event_listeners, list) { unix_sock_event_listener_free(listener); } } void unix_sock_event_notify(struct globals *globals, uint8_t type, const uint8_t source[ETH_ALEN]) { struct event_listener *listener, *tmp; /* if event notify is unsuccessful, listener socket is closed */ list_for_each_entry_safe(listener, tmp, &globals->event_listeners, list) { unix_sock_event_notify_listener(listener, type, source); } } alfred-2025.1/util.c000066400000000000000000000053461500012154500141620ustar00rootroot00000000000000// 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 #include #include #include #include "alfred.h" int time_diff(struct timespec *tv1, struct timespec *tv2, struct timespec *tvdiff) { tvdiff->tv_sec = tv1->tv_sec - tv2->tv_sec; if (tv1->tv_nsec < tv2->tv_nsec) { tvdiff->tv_nsec = 1000000000 + tv1->tv_nsec - tv2->tv_nsec; tvdiff->tv_sec -= 1; } else { tvdiff->tv_nsec = tv1->tv_nsec - tv2->tv_nsec; } return (tvdiff->tv_sec >= 0); } void time_random_seed(void) { struct timespec now; uint8_t *c = (uint8_t *)&now; size_t i; unsigned int s = 0; clock_gettime(CLOCK_REALTIME, &now); for (i = 0; i < sizeof(now); i++) { s *= 127u; s += c[i]; } srand(s); } uint16_t get_random_id(void) { return random(); } bool is_valid_ether_addr(uint8_t addr[ETH_ALEN]) { /* multicast address */ if (addr[0] & 0x01) return false; /* 00:00:00:00:00:00 */ if ((addr[0] | addr[1] | addr[2] | addr[3] | addr[4] | addr[5]) == 0) return false; return true; } bool is_iface_disabled(char *iface) { if (!iface) return false; if (strcmp(iface, "none") != 0) return false; return true; } static void ipv4_request_mac_resolve(const alfred_addr *addr) { const struct sockaddr *sockaddr; struct sockaddr_in inet4; size_t sockaddr_len; int sock; char t = 0; sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock < 0) return; memset(&inet4, 0, sizeof(inet4)); inet4.sin_family = AF_INET; inet4.sin_port = htons(9); inet4.sin_addr.s_addr = addr->ipv4.s_addr; sockaddr = (const struct sockaddr *)&inet4; sockaddr_len = sizeof(inet4); sendto(sock, &t, sizeof(t), 0, sockaddr, sockaddr_len); close(sock); } int ipv4_arp_request(struct interface *interface, const alfred_addr *addr, struct ether_addr *mac) { struct arpreq arpreq; struct sockaddr_in *sin; int retries = 1; memset(&arpreq, 0, sizeof(arpreq)); memset(mac, 0, ETH_ALEN); sin = (struct sockaddr_in *)&arpreq.arp_pa; sin->sin_family = AF_INET; sin->sin_addr.s_addr = addr->ipv4.s_addr; strncpy(arpreq.arp_dev, interface->interface, sizeof(arpreq.arp_dev)); arpreq.arp_dev[sizeof(arpreq.arp_dev) - 1] = '\0'; while ((ioctl(interface->netsock, SIOCGARP, &arpreq) < 0) || !(arpreq.arp_flags & ATF_COM)) { ipv4_request_mac_resolve(addr); usleep(200000); if (retries-- == 0) break; } if (arpreq.arp_flags & ATF_COM) { memcpy(mac, arpreq.arp_ha.sa_data, sizeof(*mac)); } else { perror("arp: incomplete"); return -1; } return 0; } alfred-2025.1/vis/000077500000000000000000000000001500012154500136325ustar00rootroot00000000000000alfred-2025.1/vis/Makefile000077500000000000000000000052241500012154500153000ustar00rootroot00000000000000#!/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 # batadv-vis build BINARY_NAME = batadv-vis OBJ += netlink.o OBJ += vis.o MANPAGE = man/batadv-vis.8 # batadv-vis flags and options CFLAGS += -pedantic -Wall -W -std=gnu99 -fno-strict-aliasing -MD -MP CPPFLAGS += -D_GNU_SOURCE LDLIBS += -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 # 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 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) # default target all: $(BINARY_NAME) # standard build rules .SUFFIXES: .o .c .c.o: $(COMPILE.c) -o $@ $< $(BINARY_NAME): $(OBJ) $(LINK.o) $^ $(LDLIBS) -o $@ clean: $(RM) $(BINARY_NAME) $(OBJ) $(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:.o=.d) -include $(DEP) .PHONY: all clean install alfred-2025.1/vis/batman_adv.h000077700000000000000000000000001500012154500205522../batman_adv.hustar00rootroot00000000000000alfred-2025.1/vis/man/000077500000000000000000000000001500012154500144055ustar00rootroot00000000000000alfred-2025.1/vis/man/batadv-vis.8000066400000000000000000000136371500012154500165500ustar00rootroot00000000000000.\" 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 "BATADV-VIS" "8" "Sep 21, 2013" "Linux" "batman-adv visualization server" .\" 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 batadv-vis.8 -Tutf8 .\" Retrieve format warnings with .\" man --warnings batadv-vis.8 > /dev/null .\" -------------------------------------------------------------------------- .ad l .SH NAME batadv\-vis \- batman-adv visualization server .SH SYNOPSIS .B batadv\-vis [\fIoptions\fP] .br .SH DESCRIPTION batadv\-vis can be used to visualize your batman-adv mesh network. It read the neighbor information and local client table and distributes this information via alfred in the network. By gathering this local information, any vis node can get the whole picture of the network. .PP .PP .SH OPTIONS .TP \fB\-v\fP, \fB\-\-version\fP Print the version .TP \fB\-h\fP, \fB\-\-help\fP Display a brief help message. .TP \fB\-u\fP, \fB\-\-unix-path\fP \fIpath\fP path to unix socket used for alfred server communication. .TP \fB\-i\fP, \fB\-\-interface\fP \fIiface\fP Specify the batman-adv interface configured on the system (default: bat0) .TP \fB\-s\fP, \fB\-\-server\fP Start up in server mode. This server will read the neighbor and client information from batman-adv every 10 seconds and set it in alfred via unix socket. The alfred server must run too to get this information set. .TP \fB\-f\fP, \fB\-\-format\fP \fIformat\fP Specify the output format for client mode (either "json", "jsondoc" or "dot") . .SH EXAMPLES Start an batadv\-vis server which is fetching the information for bat0: .br \fB batadv\-vis \-i bat0 \-s\fP .br To get a graphviz-compatible vis output: .br \fB batadv-vis\fP .nf digraph { subgraph "cluster_fe:f0:00:00:04:01" { "fe:f0:00:00:04:01" } "fe:f0:00:00:04:01" -> "fe:f0:00:00:05:01" [label="1.000"] "fe:f0:00:00:04:01" -> "fe:f0:00:00:03:01" [label="1.004"] "fe:f0:00:00:04:01" -> "00:00:43:05:00:04" [label="TT"] "fe:f0:00:00:04:01" -> "fe:f1:00:00:04:01" [label="TT"] subgraph "cluster_fe:f0:00:00:02:01" { "fe:f0:00:00:02:01" } "fe:f0:00:00:02:01" -> "fe:f0:00:00:03:01" [label="1.000"] "fe:f0:00:00:02:01" -> "fe:f0:00:00:01:01" [label="1.008"] "fe:f0:00:00:02:01" -> "fe:f0:00:00:08:01" [label="1.000"] "fe:f0:00:00:02:01" -> "fe:f1:00:00:02:01" [label="TT"] "fe:f0:00:00:02:01" -> "00:00:43:05:00:02" [label="TT"] subgraph "cluster_fe:f0:00:00:08:01" { "fe:f0:00:00:08:01" } [...] } .fi .br To get a json vis output: .br \fB batadv-vis -f json\fP .nf { "primary" : "fe:f0:00:00:04:01" } { "router" : "fe:f0:00:00:04:01", "neighbor" : "fe:f0:00:00:05:01", "label" : "1.000" } { "router" : "fe:f0:00:00:04:01", "neighbor" : "fe:f0:00:00:03:01", "label" : "1.008" } { "router" : "fe:f0:00:00:04:01", "gateway" : "00:00:43:05:00:04", "label" : "TT" } { "router" : "fe:f0:00:00:04:01", "gateway" : "fe:f1:00:00:04:01", "label" : "TT" } { "primary" : "fe:f0:00:00:02:01" } { "router" : "fe:f0:00:00:02:01", "neighbor" : "fe:f0:00:00:03:01", "label" : "1.000" } { "router" : "fe:f0:00:00:02:01", "neighbor" : "fe:f0:00:00:01:01", "label" : "1.016" } { "router" : "fe:f0:00:00:02:01", "neighbor" : "fe:f0:00:00:08:01", "label" : "1.000" } { "router" : "fe:f0:00:00:02:01", "gateway" : "fe:f1:00:00:02:01", "label" : "TT" } { "router" : "fe:f0:00:00:02:01", "gateway" : "00:00:43:05:00:02", "label" : "TT" } { "primary" : "fe:f0:00:00:08:01" } [...] .fi .br To get output where the complete document is json: .br \fB batadv-vis -f jsondoc\fP .nf { "source_version" : "2013.3.0-14-gcd34783", "algorithm" : 4, "vis" : [ { "primary" : "fe:f0:00:00:04:01", "neighbors" : [ { "router" : "fe:f0:00:00:04:01", "neighbor" : "fe:f0:00:00:05:01", "metric" : "1.000" }, { "router" : "fe:f0:00:00:04:01", "neighbor" : "fe:f0:00:00:03:01", "metric" : "1.008" } ], "clients" : [ "00:00:43:05:00:04", "fe:f1:00:00:04:01" ] }, { "primary" : "fe:f0:00:00:02:01", "neighbors" : [ { "router" : "fe:f0:00:00:02:01", "neighbor" : "fe:f0:00:00:03:01", "metric" : "1.000" }, { "router" : "fe:f0:00:00:02:01", "neighbor" : "fe:f0:00:00:01:01", "metric" : "1.016" }, { "router" : "fe:f0:00:00:02:01", "neighbor" : "fe:f0:00:00:08:01", "metric" : "1.000" } ], "clients" : [ "fe:f1:00:00:02:01", "00:00:43:05:00:02" ] }, { "primary" : "fe:f0:00:00:08:01", [...] . .SH SEE ALSO .BR alfred (8), .BR batctl (8) .SH AUTHOR batadv\-vis was written by Simon Wunderlich . .PP This manual page was written by Simon Wunderlich and Sven Eckelmann . alfred-2025.1/vis/netlink.c000077700000000000000000000000001500012154500174602../netlink.custar00rootroot00000000000000alfred-2025.1/vis/netlink.h000077700000000000000000000000001500012154500174722../netlink.hustar00rootroot00000000000000alfred-2025.1/vis/vis.c000066400000000000000000000616031500012154500146050ustar00rootroot00000000000000// 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 "vis.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "batman_adv.h" #include "netlink.h" #define IFACE_STATUS_LEN 256 static struct globals vis_globals; struct vis_netlink_opts { struct globals *globals; struct nlquery_opts query_opts; }; static char *mac_to_str(uint8_t *mac) { static char macstr[20]; snprintf(macstr, sizeof(macstr), "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); return macstr; } static int get_if_mac(char *ifname, uint8_t *mac) { struct ifreq ifr; int sock, ret; strncpy(ifr.ifr_name, ifname, IFNAMSIZ); ifr.ifr_name[IFNAMSIZ - 1] = '\0'; if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("can't get interface"); return -1; } ret = ioctl(sock, SIOCGIFHWADDR, &ifr); close(sock); if (ret == -1) { perror("can't get MAC address"); return -1; } memcpy(mac, &ifr.ifr_hwaddr.sa_data, ETH_ALEN); return 0; } static int get_if_index_byname(struct globals *globals, char *ifname) { struct iface_list_entry *i_entry; int devindex; int i; if (!ifname) return -1; i = 0; list_for_each_entry(i_entry, &globals->iface_list, list) { if (strncmp(ifname, i_entry->name, sizeof(i_entry->name)) == 0) return i; i++; } devindex = if_nametoindex(ifname); if (!devindex) return -1; i_entry = malloc(sizeof(*i_entry)); if (!i_entry) return -1; if (get_if_mac(ifname, i_entry->mac)) { free(i_entry); return -1; } i_entry->devindex = devindex; strncpy(i_entry->name, ifname, sizeof(i_entry->name)); /* just to be safe ... */ i_entry->name[sizeof(i_entry->name) - 1] = 0; list_add_tail(&i_entry->list, &globals->iface_list); return i; } static int get_if_index_devindex(struct globals *globals, int devindex) { struct iface_list_entry *i_entry; char *ifname; char ifnamebuf[IF_NAMESIZE]; int i; if (!devindex) return -1; i = 0; list_for_each_entry(i_entry, &globals->iface_list, list) { if (i_entry->devindex == devindex) return i; i++; } ifname = if_indextoname(devindex, ifnamebuf); if (!ifname) return -1; i_entry = malloc(sizeof(*i_entry)); if (!i_entry) return -1; if (get_if_mac(ifname, i_entry->mac)) { free(i_entry); return -1; } i_entry->devindex = devindex; strncpy(i_entry->name, ifname, sizeof(i_entry->name)); /* just to be safe ... */ i_entry->name[sizeof(i_entry->name) - 1] = 0; list_add_tail(&i_entry->list, &globals->iface_list); return i; } static int alfred_open_sock(struct globals *globals) { struct sockaddr_un addr; globals->unix_sock = socket(AF_LOCAL, SOCK_STREAM, 0); if (globals->unix_sock < 0) { perror("can't create unix socket"); return -1; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_LOCAL; strncpy(addr.sun_path, globals->unix_path, sizeof(addr.sun_path)); addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; if (connect(globals->unix_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { close(globals->unix_sock); globals->unix_sock = -1; perror("can't connect to unix socket"); return -1; } return 0; } static const int parse_transtable_local_mandatory[] = { BATADV_ATTR_TT_ADDRESS, }; static int parse_transtable_local_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 vis_netlink_opts *opts; struct genlmsghdr *ghdr; struct vis_list_entry *v_entry; uint8_t *addr; opts = container_of(query_opts, struct vis_netlink_opts, query_opts); if (!genlmsg_valid_hdr(nlh, 0)) return NL_OK; ghdr = nlmsg_data(nlh); if (ghdr->cmd != BATADV_CMD_GET_TRANSTABLE_LOCAL) 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, parse_transtable_local_mandatory, ARRAY_SIZE(parse_transtable_local_mandatory))) return NL_OK; addr = nla_data(attrs[BATADV_ATTR_TT_ADDRESS]); v_entry = malloc(sizeof(*v_entry)); if (!v_entry) return NL_OK; memcpy(v_entry->v.mac, addr, ETH_ALEN); v_entry->v.ifindex = 255; v_entry->v.qual = 0; list_add_tail(&v_entry->list, &opts->globals->entry_list); return NL_OK; } static int parse_transtable_local(struct globals *globals) { struct vis_netlink_opts opts = { .globals = globals, .query_opts = { .err = 0, }, }; int ret; ret = netlink_query_common(globals->interface, BATADV_CMD_GET_TRANSTABLE_LOCAL, parse_transtable_local_netlink_cb, &opts.query_opts); if (ret < 0) return ret; return 0; } static void clear_lists(struct globals *globals) { struct vis_list_entry *v_entry, *v_entry_safe; struct iface_list_entry *i_entry, *i_entry_safe; list_for_each_entry_safe(v_entry, v_entry_safe, &globals->entry_list, list) { list_del(&v_entry->list); free(v_entry); } list_for_each_entry_safe(i_entry, i_entry_safe, &globals->iface_list, list) { list_del(&i_entry->list); free(i_entry); } } static 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 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_STOP; } static char *get_iface_status_netlink(unsigned int meshif, unsigned int hardif, char *iface_status) { char *ret_status = NULL; struct nl_sock *sock; struct nl_msg *msg; int batadv_family; struct nl_cb *cb; int ret; iface_status[0] = '\0'; sock = nl_socket_alloc(); if (!sock) return NULL; ret = genl_connect(sock); if (ret < 0) goto err_free_sock; batadv_family = genl_ctrl_resolve(sock, BATADV_NL_NAME); if (batadv_family < 0) goto err_free_sock; cb = nl_cb_alloc(NL_CB_DEFAULT); if (!cb) goto err_free_sock; nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, get_iface_status_netlink_parse, iface_status); msg = nlmsg_alloc(); if (!msg) goto err_free_cb; genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, batadv_family, 0, 0, BATADV_CMD_GET_HARDIF, 1); nla_put_u32(msg, BATADV_ATTR_MESH_IFINDEX, meshif); nla_put_u32(msg, BATADV_ATTR_HARD_IFINDEX, hardif); ret = nl_send_auto_complete(sock, msg); if (ret < 0) goto err_free_msg; nl_recvmsgs(sock, cb); if (strlen(iface_status) > 0) ret_status = iface_status; err_free_msg: nlmsg_free(msg); err_free_cb: nl_cb_put(cb); err_free_sock: nl_socket_free(sock); return ret_status; } static bool interface_active(unsigned int meshif, unsigned int hardif) { char iface_status[IFACE_STATUS_LEN]; char *file_content = NULL; char *content_newline; bool active = false; char *status; status = get_iface_status_netlink(meshif, hardif, iface_status); if (!status) return false; content_newline = strstr(status, "\n"); if (content_newline) *content_newline = '\0'; if (strcmp(status, "active") != 0) goto free_file; active = true; free_file: free(file_content); file_content = NULL; return active; } struct register_interfaces_rtnl_arg { struct globals *globals; int ifindex; }; static struct nla_policy link_policy[IFLA_MAX + 1] = { [IFLA_IFNAME] = { .type = NLA_STRING, .maxlen = IFNAMSIZ }, [IFLA_MASTER] = { .type = NLA_U32 }, }; static int register_interfaces_rtnl_parse(struct nl_msg *msg, void *arg) { struct register_interfaces_rtnl_arg *register_arg = arg; struct nlattr *attrs[IFLA_MAX + 1]; struct ifinfomsg *ifm; char *ifname; 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; 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 != register_arg->ifindex) goto err; if (!interface_active(master, ifm->ifi_index)) goto err; get_if_index_byname(register_arg->globals, ifname); err: return NL_OK; } static int register_interfaces(struct globals *globals) { struct register_interfaces_rtnl_arg register_arg = { .globals = globals, }; register_arg.ifindex = if_nametoindex(globals->interface); if (!register_arg.ifindex) return EXIT_FAILURE; query_rtnl_link(register_arg.ifindex, register_interfaces_rtnl_parse, ®ister_arg); return EXIT_SUCCESS; } static const int parse_orig_list_mandatory[] = { BATADV_ATTR_ORIG_ADDRESS, BATADV_ATTR_NEIGH_ADDRESS, BATADV_ATTR_TQ, BATADV_ATTR_HARD_IFINDEX, }; static int parse_orig_list_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 vis_netlink_opts *opts; struct genlmsghdr *ghdr; struct vis_list_entry *v_entry; uint8_t *orig; uint8_t *neigh; uint8_t tq; uint32_t hardif; opts = container_of(query_opts, struct vis_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, parse_orig_list_mandatory, ARRAY_SIZE(parse_orig_list_mandatory))) return NL_OK; if (!attrs[BATADV_ATTR_FLAG_BEST]) return NL_OK; orig = nla_data(attrs[BATADV_ATTR_ORIG_ADDRESS]); neigh = nla_data(attrs[BATADV_ATTR_NEIGH_ADDRESS]); tq = nla_get_u8(attrs[BATADV_ATTR_TQ]); hardif = nla_get_u32(attrs[BATADV_ATTR_HARD_IFINDEX]); if (tq < 1) return NL_OK; if (memcmp(orig, neigh, ETH_ALEN) != 0) return NL_OK; v_entry = malloc(sizeof(*v_entry)); if (!v_entry) return NL_OK; memcpy(v_entry->v.mac, orig, ETH_ALEN); v_entry->v.ifindex = get_if_index_devindex(opts->globals, hardif); v_entry->v.qual = tq; list_add_tail(&v_entry->list, &opts->globals->entry_list); return NL_OK; } static int parse_orig_list(struct globals *globals) { struct vis_netlink_opts opts = { .globals = globals, .query_opts = { .err = 0, }, }; int ret; ret = netlink_query_common(globals->interface, BATADV_CMD_GET_ORIGINATORS, parse_orig_list_netlink_cb, &opts.query_opts); if (ret < 0) return ret; return 0; } static int vis_publish_data(struct globals *globals) { int len, ret; /* to push data we have to add a push header, the header for the data * and our own data type. */ globals->push->tx.id = htons(ntohs(globals->push->tx.id) + 1); len = VIS_DATA_SIZE(globals->vis_data); globals->push->data->header.length = htons(len); len += sizeof(*globals->push) - sizeof(globals->push->header); len += sizeof(*globals->push->data); globals->push->header.length = htons(len); len += sizeof(globals->push->header); alfred_open_sock(globals); if (globals->unix_sock < 0) return globals->unix_sock; ret = write(globals->unix_sock, globals->buf, len); close(globals->unix_sock); if (ret < len) return -1; return 0; } static int compile_vis_data(struct globals *globals) { struct iface_list_entry *i_entry; struct vis_list_entry *v_entry; struct vis_entry *vis_entries; int iface_n = 0, entries_n = 0; list_for_each_entry(i_entry, &globals->iface_list, list) { memcpy(&globals->vis_data->ifaces[iface_n], i_entry->mac, ETH_ALEN); iface_n++; if (iface_n == 254) break; } globals->vis_data->iface_n = iface_n; vis_entries = (struct vis_entry *) &globals->vis_data->ifaces[globals->vis_data->iface_n]; list_for_each_entry(v_entry, &globals->entry_list, list) { memcpy(&vis_entries[entries_n], &v_entry->v, sizeof(v_entry->v)); entries_n++; if (entries_n == 255) break; } globals->vis_data->entries_n = entries_n; return 0; } static int vis_update_data(struct globals *globals) { clear_lists(globals); register_interfaces(globals); parse_orig_list(globals); parse_transtable_local(globals); compile_vis_data(globals); vis_publish_data(globals); return 0; } static int vis_request_data(struct globals *globals) { int ret; globals->request = (struct alfred_request_v0 *) globals->buf; globals->request->header.type = ALFRED_REQUEST; globals->request->header.version = ALFRED_VERSION; globals->request->header.length = htons(sizeof(*globals->request) - sizeof(globals->request->header)); globals->request->requested_type = VIS_PACKETTYPE; globals->request->tx_id = htons(random()); alfred_open_sock(globals); if (globals->unix_sock < 0) return globals->unix_sock; ret = write(globals->unix_sock, globals->request, sizeof(*globals->request)); if (ret < (int)sizeof(*globals->request)) { close(globals->unix_sock); return -1; } return globals->unix_sock; } static struct vis_v1 *vis_receive_answer_packet(int sock, uint16_t *len) { static uint8_t buf[65536]; struct alfred_tlv *tlv; struct alfred_push_data_v0 *push; struct alfred_data *data; int l, ret; ret = read(sock, buf, sizeof(*tlv)); if (ret < 0) return NULL; if (ret < (int)sizeof(*tlv)) return NULL; tlv = (struct alfred_tlv *)buf; /* TODO: might return an ALFRED_STATUS_ERROR too, handle it */ if (tlv->type != ALFRED_PUSH_DATA) return NULL; l = ntohs(tlv->length); /* exceed the buffer? don't read */ if (l > (int)(sizeof(buf) - sizeof(push->header))) return NULL; /* not enough for even the push packet and header? don't bother. */ if (l < (int)(sizeof(*push) - sizeof(push->header) + sizeof(*data))) return NULL; /* read the rest of the packet */ ret = read(sock, buf + sizeof(*tlv), l); if (ret < l) return NULL; push = (struct alfred_push_data_v0 *)buf; data = push->data; *len = ntohs(data->header.length); if (data->header.type != VIS_PACKETTYPE) return NULL; if (data->header.version != VIS_PACKETVERSION) return NULL; return (struct vis_v1 *) data->data; } static void vis_dot_preamble(void) { printf("digraph {\n"); } static void vis_dot_interfaces(uint8_t iface_n, struct vis_iface *ifaces) { int i; printf("\tsubgraph \"cluster_%s\" {\n", mac_to_str(ifaces[0].mac)); for (i = 0; i < iface_n; i++) printf("\t\t\"%s\"%s\n", mac_to_str(ifaces[i].mac), i ? " [peripheries=2]":""); printf("\t}\n"); } static void vis_dot_entries(uint8_t entries_n, struct vis_entry *vis_entries, uint8_t iface_n, struct vis_iface *ifaces) { int i; for (i = 0; i < entries_n; i++) { if (vis_entries[i].ifindex == 255) { printf("\t\"%s\" ", mac_to_str(ifaces[0].mac)); printf("-> \"%s\" [label=\"TT\"]\n", mac_to_str(vis_entries[i].mac)); } else { if (vis_entries[i].ifindex >= iface_n) { fprintf(stderr, "ERROR: bad ifindex ...\n"); continue; } if (vis_entries[i].qual == 0) { fprintf(stderr, "ERROR: quality = 0?\n"); continue; } printf("\t\"%s\" ", mac_to_str(ifaces[vis_entries[i].ifindex].mac)); printf("-> \"%s\" [label=\"%3.3f\"]\n", mac_to_str(vis_entries[i].mac), 255.0 / ((float)vis_entries[i].qual)); } } } static void vis_dot_postamble(void) { printf("}\n"); } static void vis_json_preamble(void) { } static void vis_json_interfaces(uint8_t iface_n, struct vis_iface *ifaces) { int i; printf("{ \"primary\" : \"%s\" }\n", mac_to_str(ifaces[0].mac)); for (i = 1; i < iface_n; i++) { printf("{ \"secondary\" : \"%s\"", mac_to_str(ifaces[i].mac)); printf(", \"of\" : \"%s\" }\n", mac_to_str(ifaces[0].mac)); } } static void vis_json_entries(uint8_t entries_n, struct vis_entry *vis_entries, uint8_t iface_n, struct vis_iface *ifaces) { int i; for (i = 0; i < entries_n; i++) { if (vis_entries[i].ifindex == 255) { printf("{ \"router\" : \"%s\"", mac_to_str(ifaces[0].mac)); printf(", \"gateway\" : \"%s\", \"label\" : \"TT\" }\n", mac_to_str(vis_entries[i].mac)); } else { if (vis_entries[i].ifindex >= iface_n) { fprintf(stderr, "ERROR: bad ifindex ...\n"); continue; } if (vis_entries[i].qual == 0) { fprintf(stderr, "ERROR: quality = 0?\n"); continue; } printf("{ \"router\" : \"%s\"", mac_to_str(ifaces[vis_entries[i].ifindex].mac)); printf(", \"neighbor\" : \"%s\", \"label\" : \"%3.3f\" }\n", mac_to_str(vis_entries[i].mac), 255.0 / ((float)vis_entries[i].qual)); } } } static void vis_json_postamble(void) { } static void vis_jsondoc_preamble(void) { printf("{\n"); printf(" \"source_version\" : \"%s\",\n", SOURCE_VERSION); printf(" \"algorithm\" : 4,\n"); printf(" \"vis\" : [\n"); } static void vis_jsondoc_interfaces(uint8_t iface_n, struct vis_iface *ifaces) { int i; static bool first_interface = true; if (first_interface) first_interface = false; else printf(",\n"); printf(" { \"primary\" : \"%s\",\n", mac_to_str(ifaces[0].mac)); if (iface_n > 1) { printf(" \"secondary\" : [ "); for (i = 1; i < iface_n; i++) { printf("\"%s\"", mac_to_str(ifaces[i].mac)); if ( i < iface_n - 1) printf(","); } printf("\n ],\n"); } } static void vis_jsondoc_entries(uint8_t entries_n, struct vis_entry *vis_entries, uint8_t iface_n, struct vis_iface *ifaces) { bool first_neighbor = true; bool first_tt = true; int i; printf(" \"neighbors\" : [\n"); for (i = 0; i < entries_n; i++) { if (vis_entries[i].ifindex == 255) { continue; } if (vis_entries[i].ifindex >= iface_n) { fprintf(stderr, "ERROR: bad ifindex ...\n"); continue; } if (vis_entries[i].qual == 0) { fprintf(stderr, "ERROR: quality = 0?\n"); continue; } if (first_neighbor) first_neighbor = false; else printf(",\n"); printf(" { \"router\" : \"%s\",\n", mac_to_str(ifaces[vis_entries[i].ifindex].mac)); printf(" \"neighbor\" : \"%s\",\n", mac_to_str(vis_entries[i].mac)); printf(" \"metric\" : \"%3.3f\" }", 255.0 / ((float)vis_entries[i].qual)); } printf("\n ],\n"); printf(" \"clients\" : [\n"); for (i = 0; i < entries_n; i++) { if (vis_entries[i].ifindex == 255) { if (first_tt) first_tt = false; else printf(",\n"); printf(" \"%s\"", mac_to_str(vis_entries[i].mac)); } } printf("\n ]\n"); printf(" }"); } static void vis_jsondoc_postamble(void) { printf("\n ]\n"); printf("}\n"); } struct vis_print_ops { void (*preamble)(void); void (*interfaces)(uint8_t iface_n, struct vis_iface *ifaces); void (*entries)(uint8_t entries_n, struct vis_entry *vis_entries, uint8_t iface_n, struct vis_iface *ifaces); void (*postamble)(void); }; static const struct vis_print_ops vis_dot_ops = { vis_dot_preamble, vis_dot_interfaces, vis_dot_entries, vis_dot_postamble }; static const struct vis_print_ops vis_json_ops = { vis_json_preamble, vis_json_interfaces, vis_json_entries, vis_json_postamble }; static const struct vis_print_ops vis_jsondoc_ops = { vis_jsondoc_preamble, vis_jsondoc_interfaces, vis_jsondoc_entries, vis_jsondoc_postamble }; static int vis_read_answer(struct globals *globals) { const struct vis_print_ops *ops; struct vis_v1 *vis_data; uint16_t len; struct vis_iface *ifaces; struct vis_entry *vis_entries; switch (globals->vis_format) { case FORMAT_DOT: ops = &vis_dot_ops; break; case FORMAT_JSON: ops = &vis_json_ops; break; case FORMAT_JSONDOC: ops = &vis_jsondoc_ops; break; default: return -1; } ops->preamble(); while ((vis_data = vis_receive_answer_packet(globals->unix_sock, &len)) != NULL) { if (len < sizeof(*vis_data)) return -1; /* check size and skip bogus packets */ if (len != VIS_DATA_SIZE(vis_data)) continue; if (vis_data->iface_n == 0) continue; ifaces = vis_data->ifaces; vis_entries = (struct vis_entry *) &ifaces[vis_data->iface_n]; ops->interfaces(vis_data->iface_n, ifaces); if (vis_data->entries_n == 0) continue; ops->entries(vis_data->entries_n, vis_entries, vis_data->iface_n, ifaces); } ops->postamble(); return 0; } static int vis_get_data(struct globals *globals) { globals->unix_sock = vis_request_data(globals); if (globals->unix_sock < 0) return -1; vis_read_answer(globals); close(globals->unix_sock); return 0; } static void vis_usage(void) { printf("Usage: batadv-vis [options]\n"); printf(" -i, --interface specify the batman-adv interface configured on the system (default: bat0)\n"); printf(" -s, --server start up in server mode, which regularly updates vis data from batman-adv\n"); printf(" -f, --format specify the output format for client mode (either \"json\", \"jsondoc\" or \"dot\")\n"); printf(" -u, --unix-path path to unix socket used for alfred server communication (default: \""ALFRED_SOCK_PATH_DEFAULT"\")\n"); printf(" -v, --version print the version\n"); printf(" -h, --help this help\n"); printf("\n"); } static struct globals *vis_init(int argc, char *argv[]) { int opt, opt_ind; struct globals *globals; struct option long_options[] = { {"server", no_argument, NULL, 's'}, {"interface", required_argument, NULL, 'i'}, {"format", required_argument, NULL, 'f'}, {"unix-path", required_argument, NULL, 'u'}, {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'v'}, {NULL, 0, NULL, 0}, }; globals = &vis_globals; memset(globals, 0, sizeof(*globals)); globals->opmode = OPMODE_CLIENT; globals->interface = "bat0"; globals->vis_format = FORMAT_DOT; globals->unix_path = ALFRED_SOCK_PATH_DEFAULT; while ((opt = getopt_long(argc, argv, "shf:i:vu:", long_options, &opt_ind)) != -1) { switch (opt) { case 's': globals->opmode = OPMODE_SERVER; break; case 'f': if (strncmp(optarg, "dot", 3) == 0) globals->vis_format = FORMAT_DOT; else if (strncmp(optarg, "jsondoc", 7) == 0) globals->vis_format = FORMAT_JSONDOC; else if (strncmp(optarg, "json", 4) == 0) globals->vis_format = FORMAT_JSON; else { vis_usage(); return NULL; } break; case 'i': globals->interface = strdup(optarg); break; case 'u': globals->unix_path = optarg; break; case 'v': printf("%s %s\n", argv[0], SOURCE_VERSION); printf("VIS alfred client\n"); return NULL; case 'h': default: vis_usage(); return NULL; } } if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) perror("could not register SIGPIPE handler"); return globals; } static int vis_server(struct globals *globals) { globals->push = (struct alfred_push_data_v0 *) globals->buf; globals->vis_data = (struct vis_v1 *) (globals->buf + sizeof(*globals->push) + sizeof(struct alfred_data)); globals->push->header.type = ALFRED_PUSH_DATA; globals->push->header.version = ALFRED_VERSION; globals->push->tx.id = 0; globals->push->tx.seqno = 0; globals->push->data->header.type = VIS_PACKETTYPE; globals->push->data->header.version = VIS_PACKETVERSION; INIT_LIST_HEAD(&globals->iface_list); INIT_LIST_HEAD(&globals->entry_list); while (1) { vis_update_data(globals); sleep(UPDATE_INTERVAL); } return 0; } int main(int argc, char *argv[]) { struct globals *globals; globals = vis_init(argc, argv); if (!globals) return 1; switch (globals->opmode) { case OPMODE_SERVER: return vis_server(globals); break; case OPMODE_CLIENT: return vis_get_data(globals); break; } return 0; } alfred-2025.1/vis/vis.h000066400000000000000000000034411500012154500146060ustar00rootroot00000000000000/* 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 "../list.h" #include "../packet.h" #ifndef SOURCE_VERSION #define SOURCE_VERSION "2025.1" #endif #define ALFRED_SOCK_PATH_DEFAULT "/var/run/alfred.sock" #define PATH_BUFF_LEN 200 #define VIS_PACKETTYPE 1 #define VIS_PACKETVERSION 1 #define UPDATE_INTERVAL 10 enum opmode { OPMODE_SERVER, OPMODE_CLIENT }; enum vis_format { FORMAT_DOT, FORMAT_JSON, FORMAT_JSONDOC, }; struct vis_iface { uint8_t mac[ETH_ALEN]; }; struct vis_entry { uint8_t mac[ETH_ALEN]; uint8_t ifindex; /* 255 = TT */ uint8_t qual; /* zero for TT (maybe flags in the future?) * TQ for batman-adv */ }__packed; struct vis_v1 { uint8_t mac[ETH_ALEN]; uint8_t iface_n; uint8_t entries_n; __extension__ struct vis_iface ifaces[0]; /* #iface_n of this */ /* following: * #vis_entries of vis_entry structs */ }__packed; struct iface_list_entry { char name[256]; uint8_t mac[ETH_ALEN]; int devindex; struct list_head list; }; struct vis_list_entry { struct vis_entry v; struct list_head list; }; #define VIS_DATA_SIZE(vis_data) \ (sizeof(*vis_data) + (vis_data)->iface_n * sizeof(struct vis_iface) + \ (vis_data)->entries_n * sizeof(struct vis_entry)) struct globals { const char *interface; enum opmode opmode; enum vis_format vis_format; uint8_t buf[65536]; /* internal pointers into buf */ struct alfred_request_v0 *request; struct alfred_push_data_v0 *push; struct vis_v1 *vis_data; /* lists for parsed information used in server only */ struct list_head iface_list; struct list_head entry_list; int unix_sock; const char *unix_path; };