commit 051b584746487890d96b0893c30e32af15b69202 Author: David Herrmann Date: Thu Feb 6 16:43:11 2014 +0100 Initial MiracleCast Implementation This initial commit contains the main "miracled" daemon that does link-management and peer-discovery/control. The "miraclectl" tool can be used to control this daemon during runtime. Note that this implementation is still missing a lot of stuff. All it currently does is provide link-management and basic peer-discovery. Following commits will hook everything else up. The actual Miracast/Wifi-Display related runtime control is not being worked on, yet. Feel free to use the proof-of-concept from the OpenWFD repository. The MiracleCast implementation will not get any such functionality unless the basic link-management is properly working. Signed-off-by: David Herrmann diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92fa618 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +*.la +*.lo +*.log +*.o +*.swp +*.tar.xz +*.trs +.deps/ +.dirstamp +.libs/ +Makefile +Makefile.in +aclocal.m4 +autom4te.cache/ +build-aux/ +config.h +config.h.in +config.h.in~ +config.log +config.status +configure +libtool +m4/ +miracle-dhcp +miraclectl +miracled +stamp-h1 +test-suite.log diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..8bbf932 --- /dev/null +++ b/COPYING @@ -0,0 +1,41 @@ += Authors = + +This software was written by: + David Herrmann + += Copyright Notice = + +This software is licensed under the terms of the MIT license. Please see each +source file for the related copyright notice and license. + +If a file does not contain a copright notice, the following license shall +apply: + + Copyright (c) 2013-2014 David Herrmann + + 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. + +== Third-Party Source == + + src/shl_htable.[ch]: Released under the conditions of the LGPLv2+. + Please see LICENSE_htable for more. + + src/gdhcp/*: Released under the conditions of the GPLv2. + Please see LICENSE_gdhcp for more. diff --git a/LICENSE_gdhcp b/LICENSE_gdhcp new file mode 100644 index 0000000..a26d897 --- /dev/null +++ b/LICENSE_gdhcp @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, 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 Library 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 St, 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 Library General +Public License instead of this License. diff --git a/LICENSE_htable b/LICENSE_htable new file mode 100644 index 0000000..2d2d780 --- /dev/null +++ b/LICENSE_htable @@ -0,0 +1,510 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, 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. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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 with +this License. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + + To apply these terms, attach the following notices to the library. +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 library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the library, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James + Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..cc2f380 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,200 @@ +# +# MiracleCast - Global Makefile +# Copyright (c) 2013-2014 David Herrmann +# + +# +# Global Configurations and Initializations +# + +ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} +AM_MAKEFLAGS = --no-print-directory +AUTOMAKE_OPTIONS = color-tests + +SUBDIRS = . + +.DELETE_ON_ERROR: + +include_HEADERS = +EXTRA_DIST = \ + README \ + COPYING \ + NEWS +CLEANFILES = +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = +TPHONY = + +TESTS = +bin_PROGRAMS = +check_PROGRAMS = +lib_LTLIBRARIES = +noinst_LTLIBRARIES = + +# +# Default CFlags +# Make all files include "config.h" by default. This shouldn't cause any +# problems and we cannot forget to include it anymore. +# Also make the linker discard all unused symbols. +# + +AM_CFLAGS = \ + -Wall \ + -pipe \ + -fno-common \ + -ffast-math \ + -fdiagnostics-show-option \ + -fno-strict-aliasing \ + -fvisibility=hidden \ + -ffunction-sections \ + -fdata-sections +AM_CPPFLAGS = \ + -include $(top_builddir)/config.h \ + -I $(srcdir)/src \ + -I /usr/local/include \ + -DBUILD_ENABLE_DEBUG \ + '-DBUILD_BINDIR="$(bindir)"' +AM_LDFLAGS = \ + -Wl,--as-needed \ + -Wl,--gc-sections \ + -Wl,-z,relro \ + -Wl,-z,now +AM_LIBADD = \ + -lsystemd + +# +# SHL - Static Helper Library +# The SHL subsystem contains several small code pieces used all over libwfd and +# other applications. +# + +noinst_LTLIBRARIES += libshl.la + +libshl_la_SOURCES = \ + src/shl_dlist.h \ + src/shl_htable.h \ + src/shl_htable.c \ + src/shl_log.h \ + src/shl_log.c \ + src/shl_macro.h \ + src/shl_util.h \ + src/shl_util.c +libshl_la_CPPFLAGS = $(AM_CPPFLAGS) +libshl_la_LDFLAGS = $(AM_LDFLAGS) +libshl_la_LIBADD = $(AM_LIBADD) + +# +# gdhcp - dhcp implementation +# + +noinst_LTLIBRARIES += libgdhcp.la + +libgdhcp_la_SOURCES = \ + src/gdhcp/gdhcp.h \ + src/gdhcp/unaligned.h \ + src/gdhcp/common.h \ + src/gdhcp/common.c \ + src/gdhcp/ipv4ll.h \ + src/gdhcp/ipv4ll.c \ + src/gdhcp/client.c \ + src/gdhcp/server.c +libgdhcp_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(GDHCP_CFLAGS) +libgdhcp_la_LDFLAGS = $(AM_LDFLAGS) +libgdhcp_la_LIBADD = \ + $(AM_LIBADD) \ + $(GDHCP_LIBS) + +# +# miraclectl +# + +bin_PROGRAMS += miraclectl + +miraclectl_SOURCES = \ + src/miraclectl.c +miraclectl_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(DEPS_CFLAGS) +miraclectl_LDADD = \ + libshl.la \ + $(DEPS_LIBS) +miraclectl_LDFLAGS = $(AM_LDFLAGS) + +# +# miracle-dhcp +# + +bin_PROGRAMS += miracle-dhcp + +miracle_dhcp_SOURCES = \ + src/miracle-dhcp.c +miracle_dhcp_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(DEPS_CFLAGS) \ + $(GDHCP_CFLAGS) +miracle_dhcp_LDADD = \ + libshl.la \ + libgdhcp.la \ + $(DEPS_LIBS) \ + $(GDHCP_LIBS) +miracle_dhcp_LDFLAGS = $(AM_LDFLAGS) + +# +# miracled +# + +bin_PROGRAMS += miracled + +miracled_SOURCES = \ + src/miracled.h \ + src/miracled.c \ + src/miracled-dbus.c \ + src/miracled-link.c \ + src/miracled-peer.c \ + src/miracled-wifi.c +miracled_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(DEPS_CFLAGS) +miracled_LDADD = \ + libshl.la \ + $(DEPS_LIBS) +miracled_LDFLAGS = $(AM_LDFLAGS) + +# +# Tests +# + +tests = + +if BUILD_HAVE_CHECK +check_PROGRAMS += $(tests) +TESTS += $(tests) +endif + +test_sources = \ + test/test_common.h +test_libs = \ + libshl.la \ + $(DEPS_LIBS) \ + $(CHECK_LIBS) +test_cflags = \ + $(AM_CPPFLAGS) \ + $(DEPS_CFLAGS) \ + $(CHECK_CFLAGS) +test_lflags = \ + $(AM_LDFLAGS) + +# +# Phony targets +# + +.PHONY: $(TPHONY) + +# +# Empty .SECONDARY target causes alle intermediate files to be treated as +# secondary files. That is, they don't get deleted after make finished. +# + +.SECONDARY: diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..b9a0b23 --- /dev/null +++ b/NEWS @@ -0,0 +1,4 @@ += MiracleCast Release News = + +CHANGES WITH 1: + * TODO diff --git a/README b/README new file mode 100644 index 0000000..d6af44f --- /dev/null +++ b/README @@ -0,0 +1,40 @@ += MiracleCast - Wifi-Display/Miracast Implementation = + +TODO + +Website: + http://www.freedesktop.org/wiki/Software/miraclecast + +== Requirements == + +TODO + +== Download == + +Released tarballs can be found at: + http://www.freedesktop.org/software/miraclecast/releases + +== Install == + + To compile MiracleCast, run the standard autotools commands: + $ test -f ./configure || NOCONFIGURE=1 ./autogen.sh + $ ./configure --prefix=/usr + $ make + $ sudo make install + To compile and run the test applications, use: + $ make check + +== Documentation == + +WIP + +== License == + + This software is licensed under the terms of an MIT-like license. Please see + ./COPYING for further information. + +== Contact == + + This software is maintained by: + David Herrmann + If you have any questions, do not hesitate to contact one of the maintainers. diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..65a6fe1 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,17 @@ +#!/bin/sh +set -e + +srcdir=`dirname $0` +test -z "$srcdir" && srcdir=. + +origdir=`pwd` +cd $srcdir + +mkdir -p m4 +autoreconf -is --force + +cd $origdir + +if test -z "$NOCONFIGURE" ; then + exec $srcdir/configure "$@" +fi diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..524720b --- /dev/null +++ b/configure.ac @@ -0,0 +1,80 @@ +# +# MiracleCast - build configuration script +# Copyright (c) 2013-2014 David Herrmann +# + +AC_PREREQ(2.68) + +AC_INIT([miraclecast], + [1], + [http://www.freedesktop.org/wiki/Software/miraclecast], + [miraclecast], + [http://www.freedesktop.org/wiki/Software/miraclecast]) +AC_CONFIG_SRCDIR([src/miraclectl.c]) +AC_CONFIG_AUX_DIR([build-aux]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_HEADER(config.h) +AC_USE_SYSTEM_EXTENSIONS +AC_SYS_LARGEFILE +AC_CANONICAL_HOST + +AM_INIT_AUTOMAKE([foreign 1.11 subdir-objects dist-xz no-dist-gzip tar-pax -Wall -Werror -Wno-portability]) +AM_SILENT_RULES([yes]) + +AC_SUBST(PACKAGE_DESCRIPTION, ["Wifi-Display/Miracast Implementation"]) + +AC_PROG_CC +AC_PROG_CC_C99 +AM_PROG_CC_C_O +m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) +AC_PROG_SED +AC_PROG_MKDIR_P +AC_PROG_LN_S +AC_PROG_GREP +AC_PROG_AWK + +LT_PREREQ(2.2) +LT_INIT + +# +# Mandatory dependencies +# + +PKG_CHECK_MODULES([DEPS], [libwfd libsystemd-daemon >= 208]) +PKG_CHECK_MODULES([GDHCP], [glib-2.0]) + +# +# Test for "check" which we use for our test-suite. If not found, we disable +# all tests. +# + +PKG_CHECK_MODULES([CHECK], [check], + [have_check=yes], [have_check=no]) +AM_CONDITIONAL([BUILD_HAVE_CHECK], [test "x$have_check" = "xyes"]) + +# +# Makefile vars +# After everything is configured, we create all makefiles. +# + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT + +# +# Configuration output +# Show configuration to the user so they can check whether everything was +# configured as expected. +# + +AC_MSG_NOTICE([Build configuration: + + prefix: $prefix + exec-prefix: $exec_prefix + bindir: $bindir + libdir: $libdir + includedir: $includedir + + Miscellaneous Options: + building tests: $have_check + + Run "${MAKE-make}" to start compilation process]) diff --git a/src/gdhcp/client.c b/src/gdhcp/client.c new file mode 100644 index 0000000..30ad0e7 --- /dev/null +++ b/src/gdhcp/client.c @@ -0,0 +1,3154 @@ +/* + * + * DHCP client library with GLib integration + * + * Copyright (C) 2009-2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include "gdhcp.h" +#include "common.h" +#include "ipv4ll.h" + +#define DISCOVER_TIMEOUT 3 +#define DISCOVER_RETRIES 10 + +#define REQUEST_TIMEOUT 3 +#define REQUEST_RETRIES 5 + +typedef enum _listen_mode { + L_NONE, + L2, + L3, + L_ARP, +} ListenMode; + +typedef enum _dhcp_client_state { + INIT_SELECTING, + REQUESTING, + BOUND, + RENEWING, + REBINDING, + RELEASED, + IPV4LL_PROBE, + IPV4LL_ANNOUNCE, + IPV4LL_MONITOR, + IPV4LL_DEFEND, + INFORMATION_REQ, + SOLICITATION, + REQUEST, + CONFIRM, + RENEW, + REBIND, + RELEASE, + DECLINE, +} ClientState; + +struct _GDHCPClient { + int ref_count; + GDHCPType type; + ClientState state; + int ifindex; + char *interface; + uint8_t mac_address[6]; + uint32_t xid; + uint32_t server_ip; + uint32_t requested_ip; + char *assigned_ip; + time_t start; + uint32_t lease_seconds; + ListenMode listen_mode; + int listener_sockfd; + uint8_t retry_times; + uint8_t ack_retry_times; + uint8_t conflicts; + guint timeout; + guint listener_watch; + GIOChannel *listener_channel; + GList *require_list; + GList *request_list; + GHashTable *code_value_hash; + GHashTable *send_value_hash; + GDHCPClientEventFunc lease_available_cb; + gpointer lease_available_data; + GDHCPClientEventFunc ipv4ll_available_cb; + gpointer ipv4ll_available_data; + GDHCPClientEventFunc no_lease_cb; + gpointer no_lease_data; + GDHCPClientEventFunc lease_lost_cb; + gpointer lease_lost_data; + GDHCPClientEventFunc ipv4ll_lost_cb; + gpointer ipv4ll_lost_data; + GDHCPClientEventFunc address_conflict_cb; + gpointer address_conflict_data; + GDHCPDebugFunc debug_func; + gpointer debug_data; + GDHCPClientEventFunc information_req_cb; + gpointer information_req_data; + GDHCPClientEventFunc solicitation_cb; + gpointer solicitation_data; + GDHCPClientEventFunc advertise_cb; + gpointer advertise_data; + GDHCPClientEventFunc request_cb; + gpointer request_data; + GDHCPClientEventFunc renew_cb; + gpointer renew_data; + GDHCPClientEventFunc rebind_cb; + gpointer rebind_data; + GDHCPClientEventFunc release_cb; + gpointer release_data; + GDHCPClientEventFunc confirm_cb; + gpointer confirm_data; + GDHCPClientEventFunc decline_cb; + gpointer decline_data; + char *last_address; + unsigned char *duid; + int duid_len; + unsigned char *server_duid; + int server_duid_len; + uint16_t status_code; + uint32_t iaid; + uint32_t T1, T2; + struct in6_addr ia_na; + struct in6_addr ia_ta; + time_t last_request; + uint32_t expire; + bool retransmit; + struct timeval start_time; +}; + +static inline void debug(GDHCPClient *client, const char *format, ...) +{ + char str[256]; + va_list ap; + + if (!client->debug_func) + return; + + va_start(ap, format); + + if (vsnprintf(str, sizeof(str), format, ap) > 0) + client->debug_func(str, client->debug_data); + + va_end(ap); +} + +/* Initialize the packet with the proper defaults */ +static void init_packet(GDHCPClient *dhcp_client, gpointer pkt, char type) +{ + if (dhcp_client->type == G_DHCP_IPV6) + dhcpv6_init_header(pkt, type); + else { + struct dhcp_packet *packet = pkt; + + dhcp_init_header(packet, type); + memcpy(packet->chaddr, dhcp_client->mac_address, 6); + } +} + +static void add_request_options(GDHCPClient *dhcp_client, + struct dhcp_packet *packet) +{ + int len = 0; + GList *list; + uint8_t code; + int end = dhcp_end_option(packet->options); + + for (list = dhcp_client->request_list; list; list = list->next) { + code = (uint8_t) GPOINTER_TO_INT(list->data); + + packet->options[end + OPT_DATA + len] = code; + len++; + } + + if (len) { + packet->options[end + OPT_CODE] = DHCP_PARAM_REQ; + packet->options[end + OPT_LEN] = len; + packet->options[end + OPT_DATA + len] = DHCP_END; + } +} + +struct hash_params { + unsigned char *buf; + int max_buf; + unsigned char **ptr_buf; +}; + +static void add_dhcpv6_binary_option(gpointer key, gpointer value, + gpointer user_data) +{ + uint8_t *option = value; + uint16_t len; + struct hash_params *params = user_data; + + /* option[0][1] contains option code */ + len = option[2] << 8 | option[3]; + + if ((*params->ptr_buf + len + 2 + 2) > (params->buf + params->max_buf)) + return; + + memcpy(*params->ptr_buf, option, len + 2 + 2); + (*params->ptr_buf) += len + 2 + 2; +} + +static void add_dhcpv6_send_options(GDHCPClient *dhcp_client, + unsigned char *buf, int max_buf, + unsigned char **ptr_buf) +{ + struct hash_params params = { + .buf = buf, + .max_buf = max_buf, + .ptr_buf = ptr_buf + }; + + if (dhcp_client->type != G_DHCP_IPV6) + return; + + g_hash_table_foreach(dhcp_client->send_value_hash, + add_dhcpv6_binary_option, ¶ms); + + *ptr_buf = *params.ptr_buf; +} + +static void copy_option(uint8_t *buf, uint16_t code, uint16_t len, + uint8_t *msg) +{ + buf[0] = code >> 8; + buf[1] = code & 0xff; + buf[2] = len >> 8; + buf[3] = len & 0xff; + if (len > 0 && msg) + memcpy(&buf[4], msg, len); +} + +static int32_t get_time_diff(struct timeval *tv) +{ + struct timeval now; + int32_t hsec; + + gettimeofday(&now, NULL); + + hsec = (now.tv_sec - tv->tv_sec) * 100; + hsec += (now.tv_usec - tv->tv_usec) / 10000; + + return hsec; +} + +static void add_dhcpv6_request_options(GDHCPClient *dhcp_client, + struct dhcpv6_packet *packet, + unsigned char *buf, int max_buf, + unsigned char **ptr_buf) +{ + GList *list; + uint16_t code, value; + bool added; + int32_t diff; + int len; + + if (dhcp_client->type != G_DHCP_IPV6) + return; + + for (list = dhcp_client->request_list; list; list = list->next) { + code = (uint16_t) GPOINTER_TO_INT(list->data); + added = false; + + switch (code) { + case G_DHCPV6_CLIENTID: + if (!dhcp_client->duid) + return; + + len = 2 + 2 + dhcp_client->duid_len; + if ((*ptr_buf + len) > (buf + max_buf)) { + debug(dhcp_client, "Too long dhcpv6 message " + "when writing client id option"); + return; + } + + copy_option(*ptr_buf, G_DHCPV6_CLIENTID, + dhcp_client->duid_len, dhcp_client->duid); + (*ptr_buf) += len; + added = true; + break; + + case G_DHCPV6_SERVERID: + if (!dhcp_client->server_duid) + break; + + len = 2 + 2 + dhcp_client->server_duid_len; + if ((*ptr_buf + len) > (buf + max_buf)) { + debug(dhcp_client, "Too long dhcpv6 message " + "when writing server id option"); + return; + } + + copy_option(*ptr_buf, G_DHCPV6_SERVERID, + dhcp_client->server_duid_len, + dhcp_client->server_duid); + (*ptr_buf) += len; + added = true; + break; + + case G_DHCPV6_RAPID_COMMIT: + len = 2 + 2; + if ((*ptr_buf + len) > (buf + max_buf)) { + debug(dhcp_client, "Too long dhcpv6 message " + "when writing rapid commit option"); + return; + } + + copy_option(*ptr_buf, G_DHCPV6_RAPID_COMMIT, 0, 0); + (*ptr_buf) += len; + added = true; + break; + + case G_DHCPV6_ORO: + break; + + case G_DHCPV6_ELAPSED_TIME: + if (!dhcp_client->retransmit) { + /* + * Initial message, elapsed time is 0. + */ + diff = 0; + } else { + diff = get_time_diff(&dhcp_client->start_time); + if (diff < 0 || diff > 0xffff) + diff = 0xffff; + } + + len = 2 + 2 + 2; + if ((*ptr_buf + len) > (buf + max_buf)) { + debug(dhcp_client, "Too long dhcpv6 message " + "when writing elapsed time option"); + return; + } + + value = htons((uint16_t)diff); + copy_option(*ptr_buf, G_DHCPV6_ELAPSED_TIME, + 2, (uint8_t *)&value); + (*ptr_buf) += len; + added = true; + break; + + case G_DHCPV6_DNS_SERVERS: + break; + + case G_DHCPV6_DOMAIN_LIST: + break; + + case G_DHCPV6_SNTP_SERVERS: + break; + + default: + break; + } + + if (added) + debug(dhcp_client, "option %d len %d added", code, len); + } +} + +static void add_binary_option(gpointer key, gpointer value, gpointer user_data) +{ + uint8_t *option = value; + struct dhcp_packet *packet = user_data; + + dhcp_add_binary_option(packet, option); +} + +static void add_send_options(GDHCPClient *dhcp_client, + struct dhcp_packet *packet) +{ + g_hash_table_foreach(dhcp_client->send_value_hash, + add_binary_option, packet); +} + +/* + * Return an RFC 951- and 2131-complaint BOOTP 'secs' value that + * represents the number of seconds elapsed from the start of + * attempting DHCP to satisfy some DHCP servers that allow for an + * "authoritative" reply before responding. + */ +static uint16_t dhcp_attempt_secs(GDHCPClient *dhcp_client) +{ + return htons(MIN(time(NULL) - dhcp_client->start, UINT16_MAX)); +} + +static int send_discover(GDHCPClient *dhcp_client, uint32_t requested) +{ + struct dhcp_packet packet; + + debug(dhcp_client, "sending DHCP discover request"); + + init_packet(dhcp_client, &packet, DHCPDISCOVER); + + packet.xid = dhcp_client->xid; + packet.secs = dhcp_attempt_secs(dhcp_client); + + if (requested) + dhcp_add_option_uint32(&packet, DHCP_REQUESTED_IP, requested); + + /* Explicitly saying that we want RFC-compliant packets helps + * some buggy DHCP servers to NOT send bigger packets */ + dhcp_add_option_uint16(&packet, DHCP_MAX_SIZE, 576); + + add_request_options(dhcp_client, &packet); + + add_send_options(dhcp_client, &packet); + + return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, + INADDR_BROADCAST, SERVER_PORT, + MAC_BCAST_ADDR, dhcp_client->ifindex); +} + +static int send_select(GDHCPClient *dhcp_client) +{ + struct dhcp_packet packet; + + debug(dhcp_client, "sending DHCP select request"); + + init_packet(dhcp_client, &packet, DHCPREQUEST); + + packet.xid = dhcp_client->xid; + packet.secs = dhcp_attempt_secs(dhcp_client); + + dhcp_add_option_uint32(&packet, DHCP_REQUESTED_IP, + dhcp_client->requested_ip); + dhcp_add_option_uint32(&packet, DHCP_SERVER_ID, + dhcp_client->server_ip); + + add_request_options(dhcp_client, &packet); + + add_send_options(dhcp_client, &packet); + + return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, + INADDR_BROADCAST, SERVER_PORT, + MAC_BCAST_ADDR, dhcp_client->ifindex); +} + +static int send_renew(GDHCPClient *dhcp_client) +{ + struct dhcp_packet packet; + + debug(dhcp_client, "sending DHCP renew request"); + + init_packet(dhcp_client , &packet, DHCPREQUEST); + packet.xid = dhcp_client->xid; + packet.ciaddr = htonl(dhcp_client->requested_ip); + + add_request_options(dhcp_client, &packet); + + add_send_options(dhcp_client, &packet); + + return dhcp_send_kernel_packet(&packet, + dhcp_client->requested_ip, CLIENT_PORT, + dhcp_client->server_ip, SERVER_PORT); +} + +static int send_rebound(GDHCPClient *dhcp_client) +{ + struct dhcp_packet packet; + + debug(dhcp_client, "sending DHCP rebound request"); + + init_packet(dhcp_client , &packet, DHCPREQUEST); + packet.xid = dhcp_client->xid; + packet.ciaddr = htonl(dhcp_client->requested_ip); + + add_request_options(dhcp_client, &packet); + + add_send_options(dhcp_client, &packet); + + return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, + INADDR_BROADCAST, SERVER_PORT, + MAC_BCAST_ADDR, dhcp_client->ifindex); +} + +static int send_release(GDHCPClient *dhcp_client, + uint32_t server, uint32_t ciaddr) +{ + struct dhcp_packet packet; + + debug(dhcp_client, "sending DHCP release request"); + + init_packet(dhcp_client, &packet, DHCPRELEASE); + packet.xid = rand(); + packet.ciaddr = htonl(ciaddr); + + dhcp_add_option_uint32(&packet, DHCP_SERVER_ID, server); + + return dhcp_send_kernel_packet(&packet, ciaddr, CLIENT_PORT, + server, SERVER_PORT); +} + +static gboolean ipv4ll_probe_timeout(gpointer dhcp_data); +static int switch_listening_mode(GDHCPClient *dhcp_client, + ListenMode listen_mode); + +static gboolean send_probe_packet(gpointer dhcp_data) +{ + GDHCPClient *dhcp_client; + guint timeout; + + dhcp_client = dhcp_data; + /* if requested_ip is not valid, pick a new address*/ + if (dhcp_client->requested_ip == 0) { + debug(dhcp_client, "pick a new random address"); + dhcp_client->requested_ip = ipv4ll_random_ip(0); + } + + debug(dhcp_client, "sending IPV4LL probe request"); + + if (dhcp_client->retry_times == 1) { + dhcp_client->state = IPV4LL_PROBE; + switch_listening_mode(dhcp_client, L_ARP); + } + ipv4ll_send_arp_packet(dhcp_client->mac_address, 0, + dhcp_client->requested_ip, dhcp_client->ifindex); + + if (dhcp_client->retry_times < PROBE_NUM) { + /*add a random timeout in range of PROBE_MIN to PROBE_MAX*/ + timeout = ipv4ll_random_delay_ms(PROBE_MAX-PROBE_MIN); + timeout += PROBE_MIN*1000; + } else + timeout = (ANNOUNCE_WAIT * 1000); + + dhcp_client->timeout = g_timeout_add_full(G_PRIORITY_HIGH, + timeout, + ipv4ll_probe_timeout, + dhcp_client, + NULL); + return FALSE; +} + +static gboolean ipv4ll_announce_timeout(gpointer dhcp_data); +static gboolean ipv4ll_defend_timeout(gpointer dhcp_data); + +static gboolean send_announce_packet(gpointer dhcp_data) +{ + GDHCPClient *dhcp_client; + + dhcp_client = dhcp_data; + + debug(dhcp_client, "sending IPV4LL announce request"); + + ipv4ll_send_arp_packet(dhcp_client->mac_address, + dhcp_client->requested_ip, + dhcp_client->requested_ip, + dhcp_client->ifindex); + + if (dhcp_client->timeout > 0) + g_source_remove(dhcp_client->timeout); + dhcp_client->timeout = 0; + + if (dhcp_client->state == IPV4LL_DEFEND) { + dhcp_client->timeout = + g_timeout_add_seconds_full(G_PRIORITY_HIGH, + DEFEND_INTERVAL, + ipv4ll_defend_timeout, + dhcp_client, + NULL); + return TRUE; + } else + dhcp_client->timeout = + g_timeout_add_seconds_full(G_PRIORITY_HIGH, + ANNOUNCE_INTERVAL, + ipv4ll_announce_timeout, + dhcp_client, + NULL); + return TRUE; +} + +static void get_interface_mac_address(int index, uint8_t *mac_address) +{ + struct ifreq ifr; + int sk, err; + + sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (sk < 0) { + perror("Open socket error"); + return; + } + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_ifindex = index; + + err = ioctl(sk, SIOCGIFNAME, &ifr); + if (err < 0) { + perror("Get interface name error"); + goto done; + } + + err = ioctl(sk, SIOCGIFHWADDR, &ifr); + if (err < 0) { + perror("Get mac address error"); + goto done; + } + + memcpy(mac_address, ifr.ifr_hwaddr.sa_data, 6); + +done: + close(sk); +} + +void g_dhcpv6_client_set_retransmit(GDHCPClient *dhcp_client) +{ + if (!dhcp_client) + return; + + dhcp_client->retransmit = true; +} + +void g_dhcpv6_client_clear_retransmit(GDHCPClient *dhcp_client) +{ + if (!dhcp_client) + return; + + dhcp_client->retransmit = false; +} + +int g_dhcpv6_create_duid(GDHCPDuidType duid_type, int index, int type, + unsigned char **duid, int *duid_len) +{ + time_t duid_time; + + switch (duid_type) { + case G_DHCPV6_DUID_LLT: + *duid_len = 2 + 2 + 4 + ETH_ALEN; + *duid = g_try_malloc(*duid_len); + if (!*duid) + return -ENOMEM; + + (*duid)[0] = 0; + (*duid)[1] = 1; + get_interface_mac_address(index, &(*duid)[2 + 2 + 4]); + (*duid)[2] = 0; + (*duid)[3] = type; + duid_time = time(NULL) - DUID_TIME_EPOCH; + (*duid)[4] = duid_time >> 24; + (*duid)[5] = duid_time >> 16; + (*duid)[6] = duid_time >> 8; + (*duid)[7] = duid_time & 0xff; + break; + case G_DHCPV6_DUID_EN: + return -EINVAL; + case G_DHCPV6_DUID_LL: + *duid_len = 2 + 2 + ETH_ALEN; + *duid = g_try_malloc(*duid_len); + if (!*duid) + return -ENOMEM; + + (*duid)[0] = 0; + (*duid)[1] = 3; + get_interface_mac_address(index, &(*duid)[2 + 2]); + (*duid)[2] = 0; + (*duid)[3] = type; + break; + } + + return 0; +} + +static gchar *convert_to_hex(unsigned char *buf, int len) +{ + gchar *ret = g_try_malloc(len * 2 + 1); + int i; + + for (i = 0; ret && i < len; i++) + g_snprintf(ret + i * 2, 3, "%02x", buf[i]); + + return ret; +} + +int g_dhcpv6_client_set_duid(GDHCPClient *dhcp_client, unsigned char *duid, + int duid_len) +{ + if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6) + return -EINVAL; + + g_free(dhcp_client->duid); + + dhcp_client->duid = duid; + dhcp_client->duid_len = duid_len; + + if (dhcp_client->debug_func) { + gchar *hex = convert_to_hex(duid, duid_len); + debug(dhcp_client, "DUID(%d) %s", duid_len, hex); + g_free(hex); + } + + return 0; +} + +int g_dhcpv6_client_set_pd(GDHCPClient *dhcp_client, uint32_t *T1, + uint32_t *T2, GSList *prefixes) +{ + uint8_t options[1452]; + unsigned int max_buf = sizeof(options); + int len, count = g_slist_length(prefixes); + + if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6) + return -EINVAL; + + g_dhcp_client_set_request(dhcp_client, G_DHCPV6_IA_PD); + + memset(options, 0, sizeof(options)); + + options[0] = dhcp_client->iaid >> 24; + options[1] = dhcp_client->iaid >> 16; + options[2] = dhcp_client->iaid >> 8; + options[3] = dhcp_client->iaid; + + if (T1) { + uint32_t t = htonl(*T1); + memcpy(&options[4], &t, 4); + } + + if (T2) { + uint32_t t = htonl(*T2); + memcpy(&options[8], &t, 4); + } + + len = 12; + + if (count > 0) { + GSList *list; + + for (list = prefixes; list; list = list->next) { + GDHCPIAPrefix *prefix = list->data; + uint8_t sub_option[4+4+1+16]; + + if ((len + 2 + 2 + sizeof(sub_option)) >= max_buf) { + debug(dhcp_client, + "Too long dhcpv6 message " + "when writing IA prefix option"); + return -EINVAL; + } + + memset(&sub_option, 0, sizeof(sub_option)); + + /* preferred and validity time are left zero */ + + sub_option[8] = prefix->prefixlen; + memcpy(&sub_option[9], &prefix->prefix, 16); + + copy_option(&options[len], G_DHCPV6_IA_PREFIX, + sizeof(sub_option), sub_option); + len += 2 + 2 + sizeof(sub_option); + } + } + + g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_IA_PD, + options, len); + + return 0; +} + +uint32_t g_dhcpv6_client_get_iaid(GDHCPClient *dhcp_client) +{ + if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6) + return 0; + + return dhcp_client->iaid; +} + +void g_dhcpv6_client_set_iaid(GDHCPClient *dhcp_client, uint32_t iaid) +{ + if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6) + return; + + dhcp_client->iaid = iaid; +} + +void g_dhcpv6_client_create_iaid(GDHCPClient *dhcp_client, int index, + unsigned char *iaid) +{ + uint8_t buf[6]; + + get_interface_mac_address(index, buf); + + memcpy(iaid, &buf[2], 4); + dhcp_client->iaid = iaid[0] << 24 | + iaid[1] << 16 | iaid[2] << 8 | iaid[3]; +} + +int g_dhcpv6_client_get_timeouts(GDHCPClient *dhcp_client, + uint32_t *T1, uint32_t *T2, + time_t *started, + time_t *expire) +{ + if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6) + return -EINVAL; + + if (T1) + *T1 = dhcp_client->T1; + + if (T2) + *T2 = dhcp_client->T2; + + if (started) + *started = dhcp_client->last_request; + + if (expire) + *expire = dhcp_client->last_request + dhcp_client->expire; + + return 0; +} + +static uint8_t *create_iaaddr(GDHCPClient *dhcp_client, uint8_t *buf, + uint16_t len) +{ + buf[0] = 0; + buf[1] = G_DHCPV6_IAADDR; + buf[2] = 0; + buf[3] = len; + memcpy(&buf[4], &dhcp_client->ia_na, 16); + memset(&buf[20], 0, 4); /* preferred */ + memset(&buf[24], 0, 4); /* valid */ + return buf; +} + +static uint8_t *append_iaaddr(GDHCPClient *dhcp_client, uint8_t *buf, + const char *address) +{ + struct in6_addr addr; + + if (inet_pton(AF_INET6, address, &addr) != 1) + return NULL; + + buf[0] = 0; + buf[1] = G_DHCPV6_IAADDR; + buf[2] = 0; + buf[3] = 24; + memcpy(&buf[4], &addr, 16); + memset(&buf[20], 0, 4); /* preferred */ + memset(&buf[24], 0, 4); /* valid */ + return &buf[28]; +} + +static void put_iaid(GDHCPClient *dhcp_client, int index, uint8_t *buf) +{ + uint32_t iaid; + + iaid = g_dhcpv6_client_get_iaid(dhcp_client); + if (iaid == 0) { + g_dhcpv6_client_create_iaid(dhcp_client, index, buf); + return; + } + + buf[0] = iaid >> 24; + buf[1] = iaid >> 16; + buf[2] = iaid >> 8; + buf[3] = iaid; +} + +int g_dhcpv6_client_set_ia(GDHCPClient *dhcp_client, int index, + int code, uint32_t *T1, uint32_t *T2, + bool add_iaaddr, const char *ia_na) +{ + if (code == G_DHCPV6_IA_TA) { + uint8_t ia_options[4]; + + put_iaid(dhcp_client, index, ia_options); + + g_dhcp_client_set_request(dhcp_client, G_DHCPV6_IA_TA); + g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_IA_TA, + ia_options, sizeof(ia_options)); + + } else if (code == G_DHCPV6_IA_NA) { + struct in6_addr addr; + + g_dhcp_client_set_request(dhcp_client, G_DHCPV6_IA_NA); + + /* + * If caller has specified the IPv6 address it wishes to + * to use (ia_na != NULL and address is valid), then send + * the address to server. + * If caller did not specify the address (ia_na == NULL) and + * if the current address is not set, then we should not send + * the address sub-option. + */ + if (add_iaaddr && ((!ia_na && + !IN6_IS_ADDR_UNSPECIFIED(&dhcp_client->ia_na)) + || (ia_na && + inet_pton(AF_INET6, ia_na, &addr) == 1))) { +#define IAADDR_LEN (16+4+4) + uint8_t ia_options[4+4+4+2+2+IAADDR_LEN]; + + if (ia_na) + memcpy(&dhcp_client->ia_na, &addr, + sizeof(struct in6_addr)); + + put_iaid(dhcp_client, index, ia_options); + + if (T1) { + ia_options[4] = *T1 >> 24; + ia_options[5] = *T1 >> 16; + ia_options[6] = *T1 >> 8; + ia_options[7] = *T1; + } else + memset(&ia_options[4], 0x00, 4); + + if (T2) { + ia_options[8] = *T2 >> 24; + ia_options[9] = *T2 >> 16; + ia_options[10] = *T2 >> 8; + ia_options[11] = *T2; + } else + memset(&ia_options[8], 0x00, 4); + + create_iaaddr(dhcp_client, &ia_options[12], + IAADDR_LEN); + + g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_IA_NA, + ia_options, sizeof(ia_options)); + } else { + uint8_t ia_options[4+4+4]; + + put_iaid(dhcp_client, index, ia_options); + + memset(&ia_options[4], 0x00, 4); /* T1 (4 bytes) */ + memset(&ia_options[8], 0x00, 4); /* T2 (4 bytes) */ + + g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_IA_NA, + ia_options, sizeof(ia_options)); + } + + } else + return -EINVAL; + + return 0; +} + +int g_dhcpv6_client_set_ias(GDHCPClient *dhcp_client, int index, + int code, uint32_t *T1, uint32_t *T2, + GSList *addresses) +{ + GSList *list; + uint8_t *ia_options, *pos; + int len, count, total_len; + + count = g_slist_length(addresses); + if (count == 0) + return -EINVAL; + + g_dhcp_client_set_request(dhcp_client, code); + + if (code == G_DHCPV6_IA_TA) + len = 4; /* IAID */ + else if (code == G_DHCPV6_IA_NA) + len = 4 + 4 + 4; /* IAID + T1 + T2 */ + else + return -EINVAL; + + total_len = len + count * (2 + 2 + 16 + 4 + 4); + ia_options = g_try_malloc0(total_len); + if (!ia_options) + return -ENOMEM; + + put_iaid(dhcp_client, index, ia_options); + + pos = &ia_options[len]; /* skip the IA_NA or IA_TA */ + + for (list = addresses; list; list = list->next) { + pos = append_iaaddr(dhcp_client, pos, list->data); + if (!pos) + break; + } + + if (code == G_DHCPV6_IA_NA) { + if (T1) { + ia_options[4] = *T1 >> 24; + ia_options[5] = *T1 >> 16; + ia_options[6] = *T1 >> 8; + ia_options[7] = *T1; + } else + memset(&ia_options[4], 0x00, 4); + + if (T2) { + ia_options[8] = *T2 >> 24; + ia_options[9] = *T2 >> 16; + ia_options[10] = *T2 >> 8; + ia_options[11] = *T2; + } else + memset(&ia_options[8], 0x00, 4); + } + + g_dhcpv6_client_set_send(dhcp_client, code, ia_options, total_len); + + g_free(ia_options); + + return 0; +} + +int g_dhcpv6_client_set_oro(GDHCPClient *dhcp_client, int args, ...) +{ + va_list va; + int i, j, len = sizeof(uint16_t) * args; + uint8_t *values; + + values = g_try_malloc(len); + if (!values) + return -ENOMEM; + + va_start(va, args); + for (i = 0, j = 0; i < args; i++) { + uint16_t value = va_arg(va, int); + values[j++] = value >> 8; + values[j++] = value & 0xff; + } + va_end(va); + + g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_ORO, values, len); + + g_free(values); + + return 0; +} + +static int send_dhcpv6_msg(GDHCPClient *dhcp_client, int type, char *msg) +{ + struct dhcpv6_packet *packet; + uint8_t buf[MAX_DHCPV6_PKT_SIZE]; + unsigned char *ptr; + int ret, max_buf; + + memset(buf, 0, sizeof(buf)); + packet = (struct dhcpv6_packet *)&buf[0]; + ptr = buf + sizeof(struct dhcpv6_packet); + + init_packet(dhcp_client, packet, type); + + if (!dhcp_client->retransmit) { + dhcp_client->xid = packet->transaction_id[0] << 16 | + packet->transaction_id[1] << 8 | + packet->transaction_id[2]; + gettimeofday(&dhcp_client->start_time, NULL); + } else { + packet->transaction_id[0] = dhcp_client->xid >> 16; + packet->transaction_id[1] = dhcp_client->xid >> 8 ; + packet->transaction_id[2] = dhcp_client->xid; + } + + g_dhcp_client_set_request(dhcp_client, G_DHCPV6_ELAPSED_TIME); + + debug(dhcp_client, "sending DHCPv6 %s message xid 0x%04x", msg, + dhcp_client->xid); + + max_buf = MAX_DHCPV6_PKT_SIZE - sizeof(struct dhcpv6_packet); + + add_dhcpv6_request_options(dhcp_client, packet, buf, max_buf, &ptr); + + add_dhcpv6_send_options(dhcp_client, buf, max_buf, &ptr); + + ret = dhcpv6_send_packet(dhcp_client->ifindex, packet, ptr - buf); + + debug(dhcp_client, "sent %d pkt %p len %d", ret, packet, ptr - buf); + return ret; +} + +static int send_solicitation(GDHCPClient *dhcp_client) +{ + return send_dhcpv6_msg(dhcp_client, DHCPV6_SOLICIT, "solicit"); +} + +static int send_dhcpv6_request(GDHCPClient *dhcp_client) +{ + return send_dhcpv6_msg(dhcp_client, DHCPV6_REQUEST, "request"); +} + +static int send_dhcpv6_confirm(GDHCPClient *dhcp_client) +{ + return send_dhcpv6_msg(dhcp_client, DHCPV6_CONFIRM, "confirm"); +} + +static int send_dhcpv6_renew(GDHCPClient *dhcp_client) +{ + return send_dhcpv6_msg(dhcp_client, DHCPV6_RENEW, "renew"); +} + +static int send_dhcpv6_rebind(GDHCPClient *dhcp_client) +{ + return send_dhcpv6_msg(dhcp_client, DHCPV6_REBIND, "rebind"); +} + +static int send_dhcpv6_decline(GDHCPClient *dhcp_client) +{ + return send_dhcpv6_msg(dhcp_client, DHCPV6_DECLINE, "decline"); +} + +static int send_dhcpv6_release(GDHCPClient *dhcp_client) +{ + return send_dhcpv6_msg(dhcp_client, DHCPV6_RELEASE, "release"); +} + +static int send_information_req(GDHCPClient *dhcp_client) +{ + return send_dhcpv6_msg(dhcp_client, DHCPV6_INFORMATION_REQ, + "information-req"); +} + +static void remove_value(gpointer data, gpointer user_data) +{ + char *value = data; + g_free(value); +} + +static void remove_option_value(gpointer data) +{ + GList *option_value = data; + + g_list_foreach(option_value, remove_value, NULL); +} + +GDHCPClient *g_dhcp_client_new(GDHCPType type, + int ifindex, GDHCPClientError *error) +{ + GDHCPClient *dhcp_client; + + if (ifindex < 0) { + *error = G_DHCP_CLIENT_ERROR_INVALID_INDEX; + return NULL; + } + + dhcp_client = g_try_new0(GDHCPClient, 1); + if (!dhcp_client) { + *error = G_DHCP_CLIENT_ERROR_NOMEM; + return NULL; + } + + dhcp_client->interface = get_interface_name(ifindex); + if (!dhcp_client->interface) { + *error = G_DHCP_CLIENT_ERROR_INTERFACE_UNAVAILABLE; + goto error; + } + + if (!interface_is_up(ifindex)) { + *error = G_DHCP_CLIENT_ERROR_INTERFACE_DOWN; + goto error; + } + + get_interface_mac_address(ifindex, dhcp_client->mac_address); + + dhcp_client->listener_sockfd = -1; + dhcp_client->listener_channel = NULL; + dhcp_client->listen_mode = L_NONE; + dhcp_client->ref_count = 1; + dhcp_client->type = type; + dhcp_client->ifindex = ifindex; + dhcp_client->lease_available_cb = NULL; + dhcp_client->ipv4ll_available_cb = NULL; + dhcp_client->no_lease_cb = NULL; + dhcp_client->lease_lost_cb = NULL; + dhcp_client->ipv4ll_lost_cb = NULL; + dhcp_client->address_conflict_cb = NULL; + dhcp_client->listener_watch = 0; + dhcp_client->retry_times = 0; + dhcp_client->ack_retry_times = 0; + dhcp_client->code_value_hash = g_hash_table_new_full(g_direct_hash, + g_direct_equal, NULL, remove_option_value); + dhcp_client->send_value_hash = g_hash_table_new_full(g_direct_hash, + g_direct_equal, NULL, g_free); + dhcp_client->request_list = NULL; + dhcp_client->require_list = NULL; + dhcp_client->duid = NULL; + dhcp_client->duid_len = 0; + dhcp_client->last_request = time(NULL); + dhcp_client->expire = 0; + + *error = G_DHCP_CLIENT_ERROR_NONE; + + return dhcp_client; + +error: + g_free(dhcp_client->interface); + g_free(dhcp_client); + return NULL; +} + +#define SERVER_AND_CLIENT_PORTS ((67 << 16) + 68) + +static int dhcp_l2_socket(int ifindex) +{ + int fd; + struct sockaddr_ll sock; + + /* + * Comment: + * + * I've selected not to see LL header, so BPF doesn't see it, too. + * The filter may also pass non-IP and non-ARP packets, but we do + * a more complete check when receiving the message in userspace. + * + * and filter shamelessly stolen from: + * + * http://www.flamewarmaster.de/software/dhcpclient/ + * + * There are a few other interesting ideas on that page (look under + * "Motivation"). Use of netlink events is most interesting. Think + * of various network servers listening for events and reconfiguring. + * That would obsolete sending HUP signals and/or make use of restarts. + * + * Copyright: 2006, 2007 Stefan Rompf . + * License: GPL v2. + * + * TODO: make conditional? + */ + static const struct sock_filter filter_instr[] = { + /* check for udp */ + BPF_STMT(BPF_LD|BPF_B|BPF_ABS, 9), + /* L5, L1, is UDP? */ + BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, IPPROTO_UDP, 2, 0), + /* ugly check for arp on ethernet-like and IPv4 */ + BPF_STMT(BPF_LD|BPF_W|BPF_ABS, 2), /* L1: */ + BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, 0x08000604, 3, 4),/* L3, L4 */ + /* skip IP header */ + BPF_STMT(BPF_LDX|BPF_B|BPF_MSH, 0), /* L5: */ + /* check udp source and destination ports */ + BPF_STMT(BPF_LD|BPF_W|BPF_IND, 0), + /* L3, L4 */ + BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, SERVER_AND_CLIENT_PORTS, 0, 1), + /* returns */ + BPF_STMT(BPF_RET|BPF_K, 0x0fffffff), /* L3: pass */ + BPF_STMT(BPF_RET|BPF_K, 0), /* L4: reject */ + }; + + static const struct sock_fprog filter_prog = { + .len = sizeof(filter_instr) / sizeof(filter_instr[0]), + /* casting const away: */ + .filter = (struct sock_filter *) filter_instr, + }; + + fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IP)); + if (fd < 0) + return fd; + + if (SERVER_PORT == 67 && CLIENT_PORT == 68) + /* Use only if standard ports are in use */ + setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog, + sizeof(filter_prog)); + + memset(&sock, 0, sizeof(sock)); + sock.sll_family = AF_PACKET; + sock.sll_protocol = htons(ETH_P_IP); + sock.sll_ifindex = ifindex; + + if (bind(fd, (struct sockaddr *) &sock, sizeof(sock)) != 0) { + close(fd); + return -errno; + } + + return fd; +} + +static bool sanity_check(struct ip_udp_dhcp_packet *packet, int bytes) +{ + if (packet->ip.protocol != IPPROTO_UDP) + return false; + + if (packet->ip.version != IPVERSION) + return false; + + if (packet->ip.ihl != sizeof(packet->ip) >> 2) + return false; + + if (packet->udp.dest != htons(CLIENT_PORT)) + return false; + + if (ntohs(packet->udp.len) != (uint16_t)(bytes - sizeof(packet->ip))) + return false; + + return true; +} + +static int dhcp_recv_l2_packet(struct dhcp_packet *dhcp_pkt, int fd) +{ + int bytes; + struct ip_udp_dhcp_packet packet; + uint16_t check; + + memset(&packet, 0, sizeof(packet)); + + bytes = read(fd, &packet, sizeof(packet)); + if (bytes < 0) + return -1; + + if (bytes < (int) (sizeof(packet.ip) + sizeof(packet.udp))) + return -1; + + if (bytes < ntohs(packet.ip.tot_len)) + /* packet is bigger than sizeof(packet), we did partial read */ + return -1; + + /* ignore any extra garbage bytes */ + bytes = ntohs(packet.ip.tot_len); + + if (!sanity_check(&packet, bytes)) + return -1; + + check = packet.ip.check; + packet.ip.check = 0; + if (check != dhcp_checksum(&packet.ip, sizeof(packet.ip))) + return -1; + + /* verify UDP checksum. IP header has to be modified for this */ + memset(&packet.ip, 0, offsetof(struct iphdr, protocol)); + /* ip.xx fields which are not memset: protocol, check, saddr, daddr */ + packet.ip.tot_len = packet.udp.len; /* yes, this is needed */ + check = packet.udp.check; + packet.udp.check = 0; + if (check && check != dhcp_checksum(&packet, bytes)) + return -1; + + memcpy(dhcp_pkt, &packet.data, bytes - (sizeof(packet.ip) + + sizeof(packet.udp))); + + if (dhcp_pkt->cookie != htonl(DHCP_MAGIC)) + return -1; + + return bytes - (sizeof(packet.ip) + sizeof(packet.udp)); +} + +static void ipv4ll_start(GDHCPClient *dhcp_client) +{ + guint timeout; + int seed; + + if (dhcp_client->timeout > 0) { + g_source_remove(dhcp_client->timeout); + dhcp_client->timeout = 0; + } + + switch_listening_mode(dhcp_client, L_NONE); + dhcp_client->retry_times = 0; + dhcp_client->requested_ip = 0; + + /*try to start with a based mac address ip*/ + seed = (dhcp_client->mac_address[4] << 8 | dhcp_client->mac_address[4]); + dhcp_client->requested_ip = ipv4ll_random_ip(seed); + + /*first wait a random delay to avoid storm of arp request on boot*/ + timeout = ipv4ll_random_delay_ms(PROBE_WAIT); + + dhcp_client->retry_times++; + dhcp_client->timeout = g_timeout_add_full(G_PRIORITY_HIGH, + timeout, + send_probe_packet, + dhcp_client, + NULL); +} + +static void ipv4ll_stop(GDHCPClient *dhcp_client) +{ + + switch_listening_mode(dhcp_client, L_NONE); + + if (dhcp_client->timeout > 0) + g_source_remove(dhcp_client->timeout); + + if (dhcp_client->listener_watch > 0) { + g_source_remove(dhcp_client->listener_watch); + dhcp_client->listener_watch = 0; + } + + dhcp_client->state = IPV4LL_PROBE; + dhcp_client->retry_times = 0; + dhcp_client->requested_ip = 0; + + g_free(dhcp_client->assigned_ip); + dhcp_client->assigned_ip = NULL; +} + +static int ipv4ll_recv_arp_packet(GDHCPClient *dhcp_client) +{ + int bytes; + struct ether_arp arp; + uint32_t ip_requested; + int source_conflict; + int target_conflict; + + memset(&arp, 0, sizeof(arp)); + bytes = read(dhcp_client->listener_sockfd, &arp, sizeof(arp)); + if (bytes < 0) + return bytes; + + if (arp.arp_op != htons(ARPOP_REPLY) && + arp.arp_op != htons(ARPOP_REQUEST)) + return -EINVAL; + + ip_requested = htonl(dhcp_client->requested_ip); + source_conflict = !memcmp(arp.arp_spa, &ip_requested, + sizeof(ip_requested)); + + target_conflict = !memcmp(arp.arp_tpa, &ip_requested, + sizeof(ip_requested)); + + if (!source_conflict && !target_conflict) + return 0; + + dhcp_client->conflicts++; + + debug(dhcp_client, "IPV4LL conflict detected"); + + if (dhcp_client->state == IPV4LL_MONITOR) { + if (!source_conflict) + return 0; + dhcp_client->state = IPV4LL_DEFEND; + debug(dhcp_client, "DEFEND mode conflicts : %d", + dhcp_client->conflicts); + /*Try to defend with a single announce*/ + send_announce_packet(dhcp_client); + return 0; + } + + if (dhcp_client->state == IPV4LL_DEFEND) { + if (!source_conflict) + return 0; + else if (dhcp_client->ipv4ll_lost_cb) + dhcp_client->ipv4ll_lost_cb(dhcp_client, + dhcp_client->ipv4ll_lost_data); + } + + ipv4ll_stop(dhcp_client); + + if (dhcp_client->conflicts < MAX_CONFLICTS) { + /*restart whole state machine*/ + dhcp_client->retry_times++; + dhcp_client->timeout = + g_timeout_add_full(G_PRIORITY_HIGH, + ipv4ll_random_delay_ms(PROBE_WAIT), + send_probe_packet, + dhcp_client, + NULL); + } + /* Here we got a lot of conflicts, RFC3927 states that we have + * to wait RATE_LIMIT_INTERVAL before retrying, + * but we just report failure. + */ + else if (dhcp_client->no_lease_cb) + dhcp_client->no_lease_cb(dhcp_client, + dhcp_client->no_lease_data); + + return 0; +} + +static bool check_package_owner(GDHCPClient *dhcp_client, gpointer pkt) +{ + if (dhcp_client->type == G_DHCP_IPV6) { + struct dhcpv6_packet *packet6 = pkt; + uint32_t xid; + + if (!packet6) + return false; + + xid = packet6->transaction_id[0] << 16 | + packet6->transaction_id[1] << 8 | + packet6->transaction_id[2]; + + if (xid != dhcp_client->xid) + return false; + } else { + struct dhcp_packet *packet = pkt; + + if (packet->xid != dhcp_client->xid) + return false; + + if (packet->hlen != 6) + return false; + + if (memcmp(packet->chaddr, dhcp_client->mac_address, 6)) + return false; + } + + return true; +} + +static void start_request(GDHCPClient *dhcp_client); + +static gboolean request_timeout(gpointer user_data) +{ + GDHCPClient *dhcp_client = user_data; + + debug(dhcp_client, "request timeout (retries %d)", + dhcp_client->retry_times); + + dhcp_client->retry_times++; + + start_request(dhcp_client); + + return FALSE; +} + +static gboolean listener_event(GIOChannel *channel, GIOCondition condition, + gpointer user_data); + +static int switch_listening_mode(GDHCPClient *dhcp_client, + ListenMode listen_mode) +{ + GIOChannel *listener_channel; + int listener_sockfd; + + if (dhcp_client->listen_mode == listen_mode) + return 0; + + debug(dhcp_client, "switch listening mode (%d ==> %d)", + dhcp_client->listen_mode, listen_mode); + + if (dhcp_client->listen_mode != L_NONE) { + if (dhcp_client->listener_watch > 0) + g_source_remove(dhcp_client->listener_watch); + dhcp_client->listener_channel = NULL; + dhcp_client->listen_mode = L_NONE; + dhcp_client->listener_sockfd = -1; + dhcp_client->listener_watch = 0; + } + + if (listen_mode == L_NONE) + return 0; + + if (listen_mode == L2) + listener_sockfd = dhcp_l2_socket(dhcp_client->ifindex); + else if (listen_mode == L3) { + if (dhcp_client->type == G_DHCP_IPV6) + listener_sockfd = dhcp_l3_socket(DHCPV6_CLIENT_PORT, + dhcp_client->interface, + AF_INET6); + else + listener_sockfd = dhcp_l3_socket(CLIENT_PORT, + dhcp_client->interface, + AF_INET); + } else if (listen_mode == L_ARP) + listener_sockfd = ipv4ll_arp_socket(dhcp_client->ifindex); + else + return -EIO; + + if (listener_sockfd < 0) + return -EIO; + + listener_channel = g_io_channel_unix_new(listener_sockfd); + if (!listener_channel) { + /* Failed to create listener channel */ + close(listener_sockfd); + return -EIO; + } + + dhcp_client->listen_mode = listen_mode; + dhcp_client->listener_sockfd = listener_sockfd; + dhcp_client->listener_channel = listener_channel; + + g_io_channel_set_close_on_unref(listener_channel, TRUE); + dhcp_client->listener_watch = + g_io_add_watch_full(listener_channel, G_PRIORITY_HIGH, + G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP, + listener_event, dhcp_client, + NULL); + g_io_channel_unref(dhcp_client->listener_channel); + + return 0; +} + +static void start_request(GDHCPClient *dhcp_client) +{ + debug(dhcp_client, "start request (retries %d)", + dhcp_client->retry_times); + + if (dhcp_client->retry_times == REQUEST_RETRIES) { + if (dhcp_client->no_lease_cb) + dhcp_client->no_lease_cb(dhcp_client, + dhcp_client->no_lease_data); + return; + } + + if (dhcp_client->retry_times == 0) { + dhcp_client->state = REQUESTING; + switch_listening_mode(dhcp_client, L2); + } + + send_select(dhcp_client); + + dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, + REQUEST_TIMEOUT, + request_timeout, + dhcp_client, + NULL); +} + +static uint32_t get_lease(struct dhcp_packet *packet) +{ + uint8_t *option; + uint32_t lease_seconds; + + option = dhcp_get_option(packet, DHCP_LEASE_TIME); + if (!option) + return 3600; + + lease_seconds = get_be32(option); + /* paranoia: must not be prone to overflows */ + lease_seconds &= 0x0fffffff; + if (lease_seconds < 10) + lease_seconds = 10; + + return lease_seconds; +} + +static void restart_dhcp(GDHCPClient *dhcp_client, int retry_times) +{ + debug(dhcp_client, "restart DHCP (retries %d)", retry_times); + + if (dhcp_client->timeout > 0) { + g_source_remove(dhcp_client->timeout); + dhcp_client->timeout = 0; + } + + dhcp_client->retry_times = retry_times; + dhcp_client->requested_ip = 0; + dhcp_client->state = INIT_SELECTING; + switch_listening_mode(dhcp_client, L2); + + g_dhcp_client_start(dhcp_client, dhcp_client->last_address); +} + +static gboolean start_rebound_timeout(gpointer user_data) +{ + GDHCPClient *dhcp_client = user_data; + + debug(dhcp_client, "start rebound timeout"); + + switch_listening_mode(dhcp_client, L2); + + dhcp_client->lease_seconds >>= 1; + + /* We need to have enough time to receive ACK package*/ + if (dhcp_client->lease_seconds <= 6) { + + /* ip need to be cleared */ + if (dhcp_client->lease_lost_cb) + dhcp_client->lease_lost_cb(dhcp_client, + dhcp_client->lease_lost_data); + + restart_dhcp(dhcp_client, 0); + } else { + send_rebound(dhcp_client); + + dhcp_client->timeout = + g_timeout_add_seconds_full(G_PRIORITY_HIGH, + dhcp_client->lease_seconds >> 1, + start_rebound_timeout, + dhcp_client, + NULL); + } + + return FALSE; +} + +static void start_rebound(GDHCPClient *dhcp_client) +{ + debug(dhcp_client, "start rebound"); + + dhcp_client->state = REBINDING; + + dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, + dhcp_client->lease_seconds >> 1, + start_rebound_timeout, + dhcp_client, + NULL); +} + +static gboolean start_renew_request_timeout(gpointer user_data) +{ + GDHCPClient *dhcp_client = user_data; + + debug(dhcp_client, "renew request timeout"); + + if (dhcp_client->no_lease_cb) + dhcp_client->no_lease_cb(dhcp_client, + dhcp_client->no_lease_data); + + return false; +} + +static gboolean start_renew_timeout(gpointer user_data) +{ + GDHCPClient *dhcp_client = user_data; + + debug(dhcp_client, "start renew timeout"); + + dhcp_client->state = RENEWING; + + dhcp_client->lease_seconds >>= 1; + + switch_listening_mode(dhcp_client, L3); + if (dhcp_client->lease_seconds <= 60) + start_rebound(dhcp_client); + else { + send_renew(dhcp_client); + + if (dhcp_client->timeout > 0) + g_source_remove(dhcp_client->timeout); + + dhcp_client->timeout = + g_timeout_add_seconds_full(G_PRIORITY_HIGH, + REQUEST_TIMEOUT, + start_renew_request_timeout, + dhcp_client, + NULL); + } + + return FALSE; +} + +static void start_bound(GDHCPClient *dhcp_client) +{ + debug(dhcp_client, "start bound"); + + dhcp_client->state = BOUND; + + if (dhcp_client->timeout > 0) + g_source_remove(dhcp_client->timeout); + + dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, + dhcp_client->lease_seconds >> 1, + start_renew_timeout, dhcp_client, + NULL); +} + +static gboolean restart_dhcp_timeout(gpointer user_data) +{ + GDHCPClient *dhcp_client = user_data; + + debug(dhcp_client, "restart DHCP timeout"); + + dhcp_client->ack_retry_times++; + + restart_dhcp(dhcp_client, dhcp_client->ack_retry_times); + + return FALSE; +} + +static char *get_ip(uint32_t ip) +{ + struct in_addr addr; + + addr.s_addr = ip; + + return g_strdup(inet_ntoa(addr)); +} + +/* get a rough idea of how long an option will be */ +static const uint8_t len_of_option_as_string[] = { + [OPTION_IP] = sizeof("255.255.255.255 "), + [OPTION_STRING] = 1, + [OPTION_U8] = sizeof("255 "), + [OPTION_U16] = sizeof("65535 "), + [OPTION_U32] = sizeof("4294967295 "), +}; + +static int sprint_nip(char *dest, const char *pre, const uint8_t *ip) +{ + return sprintf(dest, "%s%u.%u.%u.%u", pre, ip[0], ip[1], ip[2], ip[3]); +} + +/* Create "opt_value1 option_value2 ..." string */ +static char *malloc_option_value_string(uint8_t *option, GDHCPOptionType type) +{ + unsigned upper_length; + int len, optlen; + char *dest, *ret; + + len = option[OPT_LEN - OPT_DATA]; + type &= OPTION_TYPE_MASK; + optlen = dhcp_option_lengths[type]; + if (optlen == 0) + return NULL; + upper_length = len_of_option_as_string[type] * + ((unsigned)len / (unsigned)optlen); + dest = ret = g_malloc(upper_length + 1); + if (!ret) + return NULL; + + while (len >= optlen) { + switch (type) { + case OPTION_IP: + dest += sprint_nip(dest, "", option); + break; + case OPTION_U16: { + uint16_t val_u16 = get_be16(option); + dest += sprintf(dest, "%u", val_u16); + break; + } + case OPTION_U32: { + uint32_t val_u32 = get_be32(option); + dest += sprintf(dest, "%u", val_u32); + break; + } + case OPTION_STRING: + memcpy(dest, option, len); + dest[len] = '\0'; + return ret; + default: + break; + } + option += optlen; + len -= optlen; + if (len <= 0) + break; + *dest++ = ' '; + *dest = '\0'; + } + + return ret; +} + +static GList *get_option_value_list(char *value, GDHCPOptionType type) +{ + char *pos = value; + GList *list = NULL; + + if (!pos) + return NULL; + + if (type == OPTION_STRING) + return g_list_append(list, g_strdup(value)); + + while ((pos = strchr(pos, ' '))) { + *pos = '\0'; + + list = g_list_append(list, g_strdup(value)); + + value = ++pos; + } + + list = g_list_append(list, g_strdup(value)); + + return list; +} + +static inline uint32_t get_uint32(unsigned char *value) +{ + return value[0] << 24 | value[1] << 16 | + value[2] << 8 | value[3]; +} + +static inline uint16_t get_uint16(unsigned char *value) +{ + return value[0] << 8 | value[1]; +} + +static GList *add_prefix(GDHCPClient *dhcp_client, GList *list, + struct in6_addr *addr, + unsigned char prefixlen, uint32_t preferred, + uint32_t valid) +{ + GDHCPIAPrefix *ia_prefix; + + ia_prefix = g_try_new(GDHCPIAPrefix, 1); + if (!ia_prefix) + return list; + + if (dhcp_client->debug_func) { + char addr_str[INET6_ADDRSTRLEN + 1]; + inet_ntop(AF_INET6, addr, addr_str, INET6_ADDRSTRLEN); + debug(dhcp_client, "prefix %s/%d preferred %u valid %u", + addr_str, prefixlen, preferred, valid); + } + + memcpy(&ia_prefix->prefix, addr, sizeof(struct in6_addr)); + ia_prefix->prefixlen = prefixlen; + ia_prefix->preferred = preferred; + ia_prefix->valid = valid; + ia_prefix->expire = time(NULL) + valid; + + return g_list_prepend(list, ia_prefix); +} + +static GList *get_addresses(GDHCPClient *dhcp_client, + int code, int len, + unsigned char *value, + uint16_t *status) +{ + GList *list = NULL; + struct in6_addr addr; + uint32_t iaid, T1 = 0, T2 = 0, preferred = 0, valid = 0; + uint16_t option_len, option_code, st = 0, max_len; + int addr_count = 0, prefix_count = 0, i, pos; + unsigned char prefixlen; + unsigned int shortest_valid = 0; + uint8_t *option; + char *str; + + if (!value || len < 4) + return NULL; + + iaid = get_uint32(&value[0]); + if (dhcp_client->iaid != iaid) + return NULL; + + if (code == G_DHCPV6_IA_NA || code == G_DHCPV6_IA_PD) { + T1 = get_uint32(&value[4]); + T2 = get_uint32(&value[8]); + + if (T1 > T2) + /* IA_NA: RFC 3315, 22.4 */ + /* IA_PD: RFC 3633, ch 9 */ + return NULL; + + pos = 12; + } else + pos = 4; + + if (len <= pos) + return NULL; + + max_len = len - pos; + + debug(dhcp_client, "header %d sub-option max len %d", pos, max_len); + + /* We have more sub-options in this packet. */ + do { + option = dhcpv6_get_sub_option(&value[pos], max_len, + &option_code, &option_len); + + debug(dhcp_client, "pos %d option %p code %d len %d", + pos, option, option_code, option_len); + + if (!option) + break; + + if (pos >= len) + break; + + switch (option_code) { + case G_DHCPV6_IAADDR: + i = 0; + memcpy(&addr, &option[0], sizeof(addr)); + i += sizeof(addr); + preferred = get_uint32(&option[i]); + i += 4; + valid = get_uint32(&option[i]); + + addr_count++; + break; + + case G_DHCPV6_STATUS_CODE: + st = get_uint16(&option[0]); + debug(dhcp_client, "error code %d", st); + if (option_len > 2) { + str = g_strndup((gchar *)&option[2], + option_len - 2); + debug(dhcp_client, "error text: %s", str); + g_free(str); + } + + *status = st; + break; + + case G_DHCPV6_IA_PREFIX: + i = 0; + preferred = get_uint32(&option[i]); + i += 4; + valid = get_uint32(&option[i]); + i += 4; + prefixlen = option[i]; + i += 1; + memcpy(&addr, &option[i], sizeof(addr)); + i += sizeof(addr); + if (preferred < valid) { + /* RFC 3633, ch 10 */ + list = add_prefix(dhcp_client, list, &addr, + prefixlen, preferred, valid); + if (shortest_valid > valid) + shortest_valid = valid; + prefix_count++; + } + break; + } + + pos += 2 + 2 + option_len; + + } while (pos < len); + + if (addr_count > 0 && st == 0) { + /* We only support one address atm */ + char addr_str[INET6_ADDRSTRLEN + 1]; + + if (preferred > valid) + /* RFC 3315, 22.6 */ + return NULL; + + dhcp_client->T1 = T1; + dhcp_client->T2 = T2; + + inet_ntop(AF_INET6, &addr, addr_str, INET6_ADDRSTRLEN); + debug(dhcp_client, "address count %d addr %s T1 %u T2 %u", + addr_count, addr_str, T1, T2); + + list = g_list_append(list, g_strdup(addr_str)); + + if (code == G_DHCPV6_IA_NA) + memcpy(&dhcp_client->ia_na, &addr, + sizeof(struct in6_addr)); + else + memcpy(&dhcp_client->ia_ta, &addr, + sizeof(struct in6_addr)); + + if (valid > dhcp_client->expire) + dhcp_client->expire = valid; + } + + if (prefix_count > 0 && list) { + /* + * This means we have a list of prefixes to delegate. + */ + list = g_list_reverse(list); + + debug(dhcp_client, "prefix count %d T1 %u T2 %u", + prefix_count, T1, T2); + + dhcp_client->T1 = T1; + dhcp_client->T2 = T2; + + dhcp_client->expire = shortest_valid; + } + + if (status && *status != 0) + debug(dhcp_client, "status %d", *status); + + return list; +} + +static GList *get_domains(int maxlen, unsigned char *value) + +{ + GList *list = NULL; + int pos = 0; + unsigned char *c; + char dns_name[NS_MAXDNAME + 1]; + + if (!value || maxlen < 3) + return NULL; + + while (pos < maxlen) { + strncpy(dns_name, (char *)&value[pos], NS_MAXDNAME); + + c = (unsigned char *)dns_name; + while (c && *c) { + int jump; + jump = *c; + *c = '.'; + c += jump + 1; + } + list = g_list_prepend(list, g_strdup(&dns_name[1])); + pos += (char *)c - dns_name + 1; + } + + return g_list_reverse(list); +} + +static GList *get_dhcpv6_option_value_list(GDHCPClient *dhcp_client, + int code, int len, + unsigned char *value, + uint16_t *status) +{ + GList *list = NULL; + char *str; + int i; + + if (!value) + return NULL; + + switch (code) { + case G_DHCPV6_DNS_SERVERS: /* RFC 3646, chapter 3 */ + case G_DHCPV6_SNTP_SERVERS: /* RFC 4075, chapter 4 */ + if (len % 16) { + debug(dhcp_client, + "%s server list length (%d) is invalid", + code == G_DHCPV6_DNS_SERVERS ? "DNS" : "SNTP", + len); + return NULL; + } + for (i = 0; i < len; i += 16) { + + str = g_try_malloc0(INET6_ADDRSTRLEN+1); + if (!str) + return list; + + if (!inet_ntop(AF_INET6, &value[i], str, + INET6_ADDRSTRLEN)) + g_free(str); + else + list = g_list_append(list, str); + } + break; + + case G_DHCPV6_IA_NA: /* RFC 3315, chapter 22.4 */ + case G_DHCPV6_IA_TA: /* RFC 3315, chapter 22.5 */ + case G_DHCPV6_IA_PD: /* RFC 3633, chapter 9 */ + list = get_addresses(dhcp_client, code, len, value, status); + break; + + case G_DHCPV6_DOMAIN_LIST: + list = get_domains(len, value); + break; + + default: + break; + } + + return list; +} + +static void get_dhcpv6_request(GDHCPClient *dhcp_client, + struct dhcpv6_packet *packet, + uint16_t pkt_len, uint16_t *status) +{ + GList *list, *value_list; + uint8_t *option; + uint16_t code; + uint16_t option_len; + + for (list = dhcp_client->request_list; list; list = list->next) { + code = (uint16_t) GPOINTER_TO_INT(list->data); + + option = dhcpv6_get_option(packet, pkt_len, code, &option_len, + NULL); + if (!option) { + g_hash_table_remove(dhcp_client->code_value_hash, + GINT_TO_POINTER((int) code)); + continue; + } + + value_list = get_dhcpv6_option_value_list(dhcp_client, code, + option_len, option, status); + + debug(dhcp_client, "code %d %p len %d list %p", code, option, + option_len, value_list); + + if (!value_list) + g_hash_table_remove(dhcp_client->code_value_hash, + GINT_TO_POINTER((int) code)); + else + g_hash_table_insert(dhcp_client->code_value_hash, + GINT_TO_POINTER((int) code), value_list); + } +} + +static void get_request(GDHCPClient *dhcp_client, struct dhcp_packet *packet) +{ + GDHCPOptionType type; + GList *list, *value_list; + char *option_value; + uint8_t *option; + uint8_t code; + + for (list = dhcp_client->request_list; list; list = list->next) { + code = (uint8_t) GPOINTER_TO_INT(list->data); + + option = dhcp_get_option(packet, code); + if (!option) { + g_hash_table_remove(dhcp_client->code_value_hash, + GINT_TO_POINTER((int) code)); + continue; + } + + type = dhcp_get_code_type(code); + + option_value = malloc_option_value_string(option, type); + if (!option_value) + g_hash_table_remove(dhcp_client->code_value_hash, + GINT_TO_POINTER((int) code)); + + value_list = get_option_value_list(option_value, type); + + g_free(option_value); + + if (!value_list) + g_hash_table_remove(dhcp_client->code_value_hash, + GINT_TO_POINTER((int) code)); + else + g_hash_table_insert(dhcp_client->code_value_hash, + GINT_TO_POINTER((int) code), value_list); + } +} + +static gboolean listener_event(GIOChannel *channel, GIOCondition condition, + gpointer user_data) +{ + GDHCPClient *dhcp_client = user_data; + struct dhcp_packet packet; + struct dhcpv6_packet *packet6 = NULL; + uint8_t *message_type = NULL, *client_id = NULL, *option, + *server_id = NULL; + uint16_t option_len = 0, status = 0; + uint32_t xid = 0; + gpointer pkt; + unsigned char buf[MAX_DHCPV6_PKT_SIZE]; + uint16_t pkt_len = 0; + int count; + int re; + + if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { + dhcp_client->listener_watch = 0; + return FALSE; + } + + if (dhcp_client->listen_mode == L_NONE) + return FALSE; + + pkt = &packet; + + dhcp_client->status_code = 0; + + if (dhcp_client->listen_mode == L2) { + re = dhcp_recv_l2_packet(&packet, + dhcp_client->listener_sockfd); + } else if (dhcp_client->listen_mode == L3) { + if (dhcp_client->type == G_DHCP_IPV6) { + re = dhcpv6_recv_l3_packet(&packet6, buf, sizeof(buf), + dhcp_client->listener_sockfd); + pkt_len = re; + pkt = packet6; + xid = packet6->transaction_id[0] << 16 | + packet6->transaction_id[1] << 8 | + packet6->transaction_id[2]; + } else { + re = dhcp_recv_l3_packet(&packet, + dhcp_client->listener_sockfd); + xid = packet.xid; + } + } else if (dhcp_client->listen_mode == L_ARP) { + ipv4ll_recv_arp_packet(dhcp_client); + return TRUE; + } else + re = -EIO; + + if (re < 0) + return TRUE; + + if (!check_package_owner(dhcp_client, pkt)) + return TRUE; + + if (dhcp_client->type == G_DHCP_IPV6) { + if (!packet6) + return TRUE; + + count = 0; + client_id = dhcpv6_get_option(packet6, pkt_len, + G_DHCPV6_CLIENTID, &option_len, &count); + + if (!client_id || count == 0 || option_len == 0 || + memcmp(dhcp_client->duid, client_id, + dhcp_client->duid_len) != 0) { + debug(dhcp_client, + "client duid error, discarding msg %p/%d/%d", + client_id, option_len, count); + return TRUE; + } + + option = dhcpv6_get_option(packet6, pkt_len, + G_DHCPV6_STATUS_CODE, &option_len, NULL); + if (option != 0 && option_len > 0) { + status = option[0]<<8 | option[1]; + if (status != 0) { + debug(dhcp_client, "error code %d", status); + if (option_len > 2) { + gchar *txt = g_strndup( + (gchar *)&option[2], + option_len - 2); + debug(dhcp_client, "error text: %s", + txt); + g_free(txt); + } + } + dhcp_client->status_code = status; + } + } else { + message_type = dhcp_get_option(&packet, DHCP_MESSAGE_TYPE); + if (!message_type) + return TRUE; + } + + if (!message_type && !client_id) + /* No message type / client id option, ignore package */ + return TRUE; + + debug(dhcp_client, "received DHCP packet xid 0x%04x " + "(current state %d)", xid, dhcp_client->state); + + switch (dhcp_client->state) { + case INIT_SELECTING: + if (*message_type != DHCPOFFER) + return TRUE; + + g_source_remove(dhcp_client->timeout); + dhcp_client->timeout = 0; + dhcp_client->retry_times = 0; + + option = dhcp_get_option(&packet, DHCP_SERVER_ID); + dhcp_client->server_ip = get_be32(option); + dhcp_client->requested_ip = ntohl(packet.yiaddr); + + dhcp_client->state = REQUESTING; + + start_request(dhcp_client); + + return TRUE; + case REQUESTING: + case RENEWING: + case REBINDING: + if (*message_type == DHCPACK) { + dhcp_client->retry_times = 0; + + if (dhcp_client->timeout > 0) + g_source_remove(dhcp_client->timeout); + dhcp_client->timeout = 0; + + dhcp_client->lease_seconds = get_lease(&packet); + + get_request(dhcp_client, &packet); + + switch_listening_mode(dhcp_client, L_NONE); + + g_free(dhcp_client->assigned_ip); + dhcp_client->assigned_ip = get_ip(packet.yiaddr); + + /* Address should be set up here */ + if (dhcp_client->lease_available_cb) + dhcp_client->lease_available_cb(dhcp_client, + dhcp_client->lease_available_data); + + start_bound(dhcp_client); + } else if (*message_type == DHCPNAK) { + dhcp_client->retry_times = 0; + + if (dhcp_client->timeout > 0) + g_source_remove(dhcp_client->timeout); + + dhcp_client->timeout = g_timeout_add_seconds_full( + G_PRIORITY_HIGH, 3, + restart_dhcp_timeout, + dhcp_client, + NULL); + } + + break; + case SOLICITATION: + if (dhcp_client->type != G_DHCP_IPV6) + return TRUE; + + if (packet6->message != DHCPV6_REPLY && + packet6->message != DHCPV6_ADVERTISE) + return TRUE; + + count = 0; + server_id = dhcpv6_get_option(packet6, pkt_len, + G_DHCPV6_SERVERID, &option_len, &count); + if (!server_id || count != 1 || option_len == 0) { + /* RFC 3315, 15.10 */ + debug(dhcp_client, + "server duid error, discarding msg %p/%d/%d", + server_id, option_len, count); + return TRUE; + } + dhcp_client->server_duid = g_try_malloc(option_len); + if (!dhcp_client->server_duid) + return TRUE; + memcpy(dhcp_client->server_duid, server_id, option_len); + dhcp_client->server_duid_len = option_len; + + if (packet6->message == DHCPV6_REPLY) { + uint8_t *rapid_commit; + count = 0; + option_len = 0; + rapid_commit = dhcpv6_get_option(packet6, pkt_len, + G_DHCPV6_RAPID_COMMIT, + &option_len, &count); + if (!rapid_commit || option_len == 0 || + count != 1) + /* RFC 3315, 17.1.4 */ + return TRUE; + } + + switch_listening_mode(dhcp_client, L_NONE); + + if (dhcp_client->status_code == 0) + get_dhcpv6_request(dhcp_client, packet6, pkt_len, + &dhcp_client->status_code); + + if (packet6->message == DHCPV6_ADVERTISE) { + if (dhcp_client->advertise_cb) + dhcp_client->advertise_cb(dhcp_client, + dhcp_client->advertise_data); + return TRUE; + } + + if (dhcp_client->solicitation_cb) { + /* + * The dhcp_client might not be valid after the + * callback call so just return immediately. + */ + dhcp_client->solicitation_cb(dhcp_client, + dhcp_client->solicitation_data); + return TRUE; + } + break; + case REBIND: + if (dhcp_client->type != G_DHCP_IPV6) + return TRUE; + + server_id = dhcpv6_get_option(packet6, pkt_len, + G_DHCPV6_SERVERID, &option_len, &count); + if (!dhcp_client->server_duid && server_id && + count == 1) { + /* + * If we do not have server duid yet, then get it now. + * Prefix delegation renew support needs it. + */ + dhcp_client->server_duid = g_try_malloc(option_len); + if (!dhcp_client->server_duid) + return TRUE; + memcpy(dhcp_client->server_duid, server_id, option_len); + dhcp_client->server_duid_len = option_len; + } + /* fall through */ + case INFORMATION_REQ: + case REQUEST: + case RENEW: + case RELEASE: + case CONFIRM: + case DECLINE: + if (dhcp_client->type != G_DHCP_IPV6) + return TRUE; + + if (packet6->message != DHCPV6_REPLY) + return TRUE; + + count = 0; + option_len = 0; + server_id = dhcpv6_get_option(packet6, pkt_len, + G_DHCPV6_SERVERID, &option_len, &count); + if (!server_id || count != 1 || option_len == 0 || + (dhcp_client->server_duid_len > 0 && + memcmp(dhcp_client->server_duid, server_id, + dhcp_client->server_duid_len) != 0)) { + /* RFC 3315, 15.10 */ + debug(dhcp_client, + "server duid error, discarding msg %p/%d/%d", + server_id, option_len, count); + return TRUE; + } + + switch_listening_mode(dhcp_client, L_NONE); + + get_dhcpv6_request(dhcp_client, packet6, pkt_len, + &dhcp_client->status_code); + + if (dhcp_client->information_req_cb) { + /* + * The dhcp_client might not be valid after the + * callback call so just return immediately. + */ + dhcp_client->information_req_cb(dhcp_client, + dhcp_client->information_req_data); + return TRUE; + } + if (dhcp_client->request_cb) { + dhcp_client->request_cb(dhcp_client, + dhcp_client->request_data); + return TRUE; + } + if (dhcp_client->renew_cb) { + dhcp_client->renew_cb(dhcp_client, + dhcp_client->renew_data); + return TRUE; + } + if (dhcp_client->rebind_cb) { + dhcp_client->rebind_cb(dhcp_client, + dhcp_client->rebind_data); + return TRUE; + } + if (dhcp_client->release_cb) { + dhcp_client->release_cb(dhcp_client, + dhcp_client->release_data); + return TRUE; + } + if (dhcp_client->decline_cb) { + dhcp_client->decline_cb(dhcp_client, + dhcp_client->decline_data); + return TRUE; + } + if (dhcp_client->confirm_cb) { + count = 0; + server_id = dhcpv6_get_option(packet6, pkt_len, + G_DHCPV6_SERVERID, &option_len, + &count); + if (!server_id || count != 1 || + option_len == 0) { + /* RFC 3315, 15.10 */ + debug(dhcp_client, + "confirm server duid error, " + "discarding msg %p/%d/%d", + server_id, option_len, count); + return TRUE; + } + dhcp_client->server_duid = g_try_malloc(option_len); + if (!dhcp_client->server_duid) + return TRUE; + memcpy(dhcp_client->server_duid, server_id, option_len); + dhcp_client->server_duid_len = option_len; + + dhcp_client->confirm_cb(dhcp_client, + dhcp_client->confirm_data); + return TRUE; + } + break; + default: + break; + } + + debug(dhcp_client, "processed DHCP packet (new state %d)", + dhcp_client->state); + + return TRUE; +} + +static gboolean discover_timeout(gpointer user_data) +{ + GDHCPClient *dhcp_client = user_data; + + dhcp_client->retry_times++; + + /* + * We do not send the REQUESTED IP option if we are retrying because + * if the server is non-authoritative it will ignore the request if the + * option is present. + */ + g_dhcp_client_start(dhcp_client, NULL); + + return FALSE; +} + +static gboolean ipv4ll_defend_timeout(gpointer dhcp_data) +{ + GDHCPClient *dhcp_client = dhcp_data; + + debug(dhcp_client, "back to MONITOR mode"); + + dhcp_client->conflicts = 0; + dhcp_client->state = IPV4LL_MONITOR; + + return FALSE; +} + +static gboolean ipv4ll_announce_timeout(gpointer dhcp_data) +{ + GDHCPClient *dhcp_client = dhcp_data; + uint32_t ip; + + debug(dhcp_client, "request timeout (retries %d)", + dhcp_client->retry_times); + + if (dhcp_client->retry_times != ANNOUNCE_NUM) { + dhcp_client->retry_times++; + send_announce_packet(dhcp_client); + return FALSE; + } + + ip = htonl(dhcp_client->requested_ip); + debug(dhcp_client, "switching to monitor mode"); + dhcp_client->state = IPV4LL_MONITOR; + dhcp_client->assigned_ip = get_ip(ip); + + if (dhcp_client->ipv4ll_available_cb) + dhcp_client->ipv4ll_available_cb(dhcp_client, + dhcp_client->ipv4ll_available_data); + dhcp_client->conflicts = 0; + + return FALSE; +} + +static gboolean ipv4ll_probe_timeout(gpointer dhcp_data) +{ + + GDHCPClient *dhcp_client = dhcp_data; + + debug(dhcp_client, "IPV4LL probe timeout (retries %d)", + dhcp_client->retry_times); + + if (dhcp_client->retry_times == PROBE_NUM) { + dhcp_client->state = IPV4LL_ANNOUNCE; + dhcp_client->retry_times = 0; + + dhcp_client->retry_times++; + send_announce_packet(dhcp_client); + return FALSE; + } + dhcp_client->retry_times++; + send_probe_packet(dhcp_client); + + return FALSE; +} + +int g_dhcp_client_start(GDHCPClient *dhcp_client, const char *last_address) +{ + int re; + uint32_t addr; + + if (dhcp_client->type == G_DHCP_IPV6) { + if (dhcp_client->information_req_cb) { + dhcp_client->state = INFORMATION_REQ; + re = switch_listening_mode(dhcp_client, L3); + if (re != 0) { + switch_listening_mode(dhcp_client, L_NONE); + dhcp_client->state = 0; + return re; + } + send_information_req(dhcp_client); + + } else if (dhcp_client->solicitation_cb) { + dhcp_client->state = SOLICITATION; + re = switch_listening_mode(dhcp_client, L3); + if (re != 0) { + switch_listening_mode(dhcp_client, L_NONE); + dhcp_client->state = 0; + return re; + } + send_solicitation(dhcp_client); + + } else if (dhcp_client->request_cb) { + dhcp_client->state = REQUEST; + re = switch_listening_mode(dhcp_client, L3); + if (re != 0) { + switch_listening_mode(dhcp_client, L_NONE); + dhcp_client->state = 0; + return re; + } + send_dhcpv6_request(dhcp_client); + + } else if (dhcp_client->confirm_cb) { + dhcp_client->state = CONFIRM; + re = switch_listening_mode(dhcp_client, L3); + if (re != 0) { + switch_listening_mode(dhcp_client, L_NONE); + dhcp_client->state = 0; + return re; + } + send_dhcpv6_confirm(dhcp_client); + + } else if (dhcp_client->renew_cb) { + dhcp_client->state = RENEW; + re = switch_listening_mode(dhcp_client, L3); + if (re != 0) { + switch_listening_mode(dhcp_client, L_NONE); + dhcp_client->state = 0; + return re; + } + send_dhcpv6_renew(dhcp_client); + + } else if (dhcp_client->rebind_cb) { + dhcp_client->state = REBIND; + re = switch_listening_mode(dhcp_client, L3); + if (re != 0) { + switch_listening_mode(dhcp_client, L_NONE); + dhcp_client->state = 0; + return re; + } + send_dhcpv6_rebind(dhcp_client); + + } else if (dhcp_client->release_cb) { + dhcp_client->state = RENEW; + re = switch_listening_mode(dhcp_client, L3); + if (re != 0) { + switch_listening_mode(dhcp_client, L_NONE); + dhcp_client->state = 0; + return re; + } + send_dhcpv6_release(dhcp_client); + } else if (dhcp_client->decline_cb) { + dhcp_client->state = DECLINE; + re = switch_listening_mode(dhcp_client, L3); + if (re != 0) { + switch_listening_mode(dhcp_client, L_NONE); + dhcp_client->state = 0; + return re; + } + send_dhcpv6_decline(dhcp_client); + } + + return 0; + } + + if (dhcp_client->type == G_DHCP_IPV4LL) { + dhcp_client->state = INIT_SELECTING; + ipv4ll_start(dhcp_client); + return 0; + } + + if (dhcp_client->retry_times == DISCOVER_RETRIES) { + if (dhcp_client->no_lease_cb) + dhcp_client->no_lease_cb(dhcp_client, + dhcp_client->no_lease_data); + dhcp_client->retry_times = 0; + return 0; + } + + if (dhcp_client->retry_times == 0) { + g_free(dhcp_client->assigned_ip); + dhcp_client->assigned_ip = NULL; + + dhcp_client->state = INIT_SELECTING; + re = switch_listening_mode(dhcp_client, L2); + if (re != 0) + return re; + + dhcp_client->xid = rand(); + dhcp_client->start = time(NULL); + } + + if (!last_address) { + addr = 0; + } else { + addr = ntohl(inet_addr(last_address)); + if (addr == 0xFFFFFFFF) { + addr = 0; + } else { + g_free(dhcp_client->last_address); + dhcp_client->last_address = g_strdup(last_address); + } + } + send_discover(dhcp_client, addr); + + dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, + DISCOVER_TIMEOUT, + discover_timeout, + dhcp_client, + NULL); + return 0; +} + +void g_dhcp_client_stop(GDHCPClient *dhcp_client) +{ + switch_listening_mode(dhcp_client, L_NONE); + + if (dhcp_client->state == BOUND || + dhcp_client->state == RENEWING || + dhcp_client->state == REBINDING) + send_release(dhcp_client, dhcp_client->server_ip, + dhcp_client->requested_ip); + + if (dhcp_client->timeout > 0) { + g_source_remove(dhcp_client->timeout); + dhcp_client->timeout = 0; + } + + if (dhcp_client->listener_watch > 0) { + g_source_remove(dhcp_client->listener_watch); + dhcp_client->listener_watch = 0; + } + + dhcp_client->listener_channel = NULL; + + dhcp_client->retry_times = 0; + dhcp_client->ack_retry_times = 0; + + dhcp_client->requested_ip = 0; + dhcp_client->state = RELEASED; + dhcp_client->lease_seconds = 0; +} + +GList *g_dhcp_client_get_option(GDHCPClient *dhcp_client, + unsigned char option_code) +{ + return g_hash_table_lookup(dhcp_client->code_value_hash, + GINT_TO_POINTER((int) option_code)); +} + +void g_dhcp_client_register_event(GDHCPClient *dhcp_client, + GDHCPClientEvent event, + GDHCPClientEventFunc func, + gpointer data) +{ + switch (event) { + case G_DHCP_CLIENT_EVENT_LEASE_AVAILABLE: + dhcp_client->lease_available_cb = func; + dhcp_client->lease_available_data = data; + return; + case G_DHCP_CLIENT_EVENT_IPV4LL_AVAILABLE: + if (dhcp_client->type == G_DHCP_IPV6) + return; + dhcp_client->ipv4ll_available_cb = func; + dhcp_client->ipv4ll_available_data = data; + return; + case G_DHCP_CLIENT_EVENT_NO_LEASE: + dhcp_client->no_lease_cb = func; + dhcp_client->no_lease_data = data; + return; + case G_DHCP_CLIENT_EVENT_LEASE_LOST: + dhcp_client->lease_lost_cb = func; + dhcp_client->lease_lost_data = data; + return; + case G_DHCP_CLIENT_EVENT_IPV4LL_LOST: + if (dhcp_client->type == G_DHCP_IPV6) + return; + dhcp_client->ipv4ll_lost_cb = func; + dhcp_client->ipv4ll_lost_data = data; + return; + case G_DHCP_CLIENT_EVENT_ADDRESS_CONFLICT: + dhcp_client->address_conflict_cb = func; + dhcp_client->address_conflict_data = data; + return; + case G_DHCP_CLIENT_EVENT_INFORMATION_REQ: + if (dhcp_client->type != G_DHCP_IPV6) + return; + dhcp_client->information_req_cb = func; + dhcp_client->information_req_data = data; + return; + case G_DHCP_CLIENT_EVENT_SOLICITATION: + if (dhcp_client->type != G_DHCP_IPV6) + return; + dhcp_client->solicitation_cb = func; + dhcp_client->solicitation_data = data; + return; + case G_DHCP_CLIENT_EVENT_ADVERTISE: + if (dhcp_client->type != G_DHCP_IPV6) + return; + dhcp_client->advertise_cb = func; + dhcp_client->advertise_data = data; + return; + case G_DHCP_CLIENT_EVENT_REQUEST: + if (dhcp_client->type != G_DHCP_IPV6) + return; + dhcp_client->request_cb = func; + dhcp_client->request_data = data; + return; + case G_DHCP_CLIENT_EVENT_RENEW: + if (dhcp_client->type != G_DHCP_IPV6) + return; + dhcp_client->renew_cb = func; + dhcp_client->renew_data = data; + return; + case G_DHCP_CLIENT_EVENT_REBIND: + if (dhcp_client->type != G_DHCP_IPV6) + return; + dhcp_client->rebind_cb = func; + dhcp_client->rebind_data = data; + return; + case G_DHCP_CLIENT_EVENT_RELEASE: + if (dhcp_client->type != G_DHCP_IPV6) + return; + dhcp_client->release_cb = func; + dhcp_client->release_data = data; + return; + case G_DHCP_CLIENT_EVENT_CONFIRM: + if (dhcp_client->type != G_DHCP_IPV6) + return; + dhcp_client->confirm_cb = func; + dhcp_client->confirm_data = data; + return; + case G_DHCP_CLIENT_EVENT_DECLINE: + if (dhcp_client->type != G_DHCP_IPV6) + return; + dhcp_client->decline_cb = func; + dhcp_client->decline_data = data; + return; + } +} + +int g_dhcp_client_get_index(GDHCPClient *dhcp_client) +{ + return dhcp_client->ifindex; +} + +char *g_dhcp_client_get_address(GDHCPClient *dhcp_client) +{ + return g_strdup(dhcp_client->assigned_ip); +} + +char *g_dhcp_client_get_netmask(GDHCPClient *dhcp_client) +{ + GList *option = NULL; + + if (dhcp_client->type == G_DHCP_IPV6) + return NULL; + + switch (dhcp_client->state) { + case IPV4LL_DEFEND: + case IPV4LL_MONITOR: + return g_strdup("255.255.0.0"); + case BOUND: + case RENEWING: + case REBINDING: + option = g_dhcp_client_get_option(dhcp_client, G_DHCP_SUBNET); + if (option) + return g_strdup(option->data); + case INIT_SELECTING: + case REQUESTING: + case RELEASED: + case IPV4LL_PROBE: + case IPV4LL_ANNOUNCE: + case INFORMATION_REQ: + case SOLICITATION: + case REQUEST: + case CONFIRM: + case RENEW: + case REBIND: + case RELEASE: + case DECLINE: + break; + } + return NULL; +} + +GDHCPClientError g_dhcp_client_set_request(GDHCPClient *dhcp_client, + unsigned int option_code) +{ + if (!g_list_find(dhcp_client->request_list, + GINT_TO_POINTER((int)option_code))) + dhcp_client->request_list = g_list_prepend( + dhcp_client->request_list, + (GINT_TO_POINTER((int) option_code))); + + return G_DHCP_CLIENT_ERROR_NONE; +} + +void g_dhcp_client_clear_requests(GDHCPClient *dhcp_client) +{ + g_list_free(dhcp_client->request_list); + dhcp_client->request_list = NULL; +} + +void g_dhcp_client_clear_values(GDHCPClient *dhcp_client) +{ + g_hash_table_remove_all(dhcp_client->send_value_hash); +} + +static uint8_t *alloc_dhcp_option(int code, const uint8_t *data, unsigned size) +{ + uint8_t *storage; + + storage = g_try_malloc(size + OPT_DATA); + if (!storage) + return NULL; + + storage[OPT_CODE] = code; + storage[OPT_LEN] = size; + memcpy(&storage[OPT_DATA], data, size); + + return storage; +} + +static uint8_t *alloc_dhcp_data_option(int code, const uint8_t *data, + unsigned size) +{ + return alloc_dhcp_option(code, data, MIN(size, 255)); +} + +static uint8_t *alloc_dhcp_string_option(int code, const char *str) +{ + return alloc_dhcp_data_option(code, (const uint8_t *)str, strlen(str)); +} + +GDHCPClientError g_dhcp_client_set_id(GDHCPClient *dhcp_client) +{ + const unsigned maclen = 6; + const unsigned idlen = maclen + 1; + const uint8_t option_code = G_DHCP_CLIENT_ID; + uint8_t idbuf[idlen]; + uint8_t *data_option; + + idbuf[0] = ARPHRD_ETHER; + + memcpy(&idbuf[1], dhcp_client->mac_address, maclen); + + data_option = alloc_dhcp_data_option(option_code, idbuf, idlen); + if (!data_option) + return G_DHCP_CLIENT_ERROR_NOMEM; + + g_hash_table_insert(dhcp_client->send_value_hash, + GINT_TO_POINTER((int) option_code), data_option); + + return G_DHCP_CLIENT_ERROR_NONE; +} + +/* Now only support send hostname */ +GDHCPClientError g_dhcp_client_set_send(GDHCPClient *dhcp_client, + unsigned char option_code, const char *option_value) +{ + uint8_t *binary_option; + + if (option_code == G_DHCP_HOST_NAME && option_value) { + binary_option = alloc_dhcp_string_option(option_code, + option_value); + if (!binary_option) + return G_DHCP_CLIENT_ERROR_NOMEM; + + g_hash_table_insert(dhcp_client->send_value_hash, + GINT_TO_POINTER((int) option_code), binary_option); + } + + return G_DHCP_CLIENT_ERROR_NONE; +} + +static uint8_t *alloc_dhcpv6_option(uint16_t code, uint8_t *option, + uint16_t len) +{ + uint8_t *storage; + + storage = g_malloc(2 + 2 + len); + if (!storage) + return NULL; + + storage[0] = code >> 8; + storage[1] = code & 0xff; + storage[2] = len >> 8; + storage[3] = len & 0xff; + memcpy(storage + 2 + 2, option, len); + + return storage; +} + +gboolean g_dhcpv6_client_clear_send(GDHCPClient *dhcp_client, uint16_t code) +{ + return g_hash_table_remove(dhcp_client->send_value_hash, + GINT_TO_POINTER((int)code)); +} + +void g_dhcpv6_client_set_send(GDHCPClient *dhcp_client, + uint16_t option_code, + uint8_t *option_value, + uint16_t option_len) +{ + if (option_value) { + uint8_t *binary_option; + + debug(dhcp_client, "setting option %d to %p len %d", + option_code, option_value, option_len); + + binary_option = alloc_dhcpv6_option(option_code, option_value, + option_len); + if (binary_option) + g_hash_table_insert(dhcp_client->send_value_hash, + GINT_TO_POINTER((int) option_code), + binary_option); + } +} + +void g_dhcpv6_client_reset_request(GDHCPClient *dhcp_client) +{ + if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6) + return; + + dhcp_client->last_request = time(NULL); +} + +uint16_t g_dhcpv6_client_get_status(GDHCPClient *dhcp_client) +{ + if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6) + return 0; + + return dhcp_client->status_code; +} + +GDHCPClient *g_dhcp_client_ref(GDHCPClient *dhcp_client) +{ + if (!dhcp_client) + return NULL; + + __sync_fetch_and_add(&dhcp_client->ref_count, 1); + + return dhcp_client; +} + +void g_dhcp_client_unref(GDHCPClient *dhcp_client) +{ + if (!dhcp_client) + return; + + if (__sync_fetch_and_sub(&dhcp_client->ref_count, 1) != 1) + return; + + g_dhcp_client_stop(dhcp_client); + + g_free(dhcp_client->interface); + g_free(dhcp_client->assigned_ip); + g_free(dhcp_client->last_address); + g_free(dhcp_client->duid); + g_free(dhcp_client->server_duid); + + g_list_free(dhcp_client->request_list); + g_list_free(dhcp_client->require_list); + + g_hash_table_destroy(dhcp_client->code_value_hash); + g_hash_table_destroy(dhcp_client->send_value_hash); + + g_free(dhcp_client); +} + +void g_dhcp_client_set_debug(GDHCPClient *dhcp_client, + GDHCPDebugFunc func, gpointer user_data) +{ + if (!dhcp_client) + return; + + dhcp_client->debug_func = func; + dhcp_client->debug_data = user_data; +} + +static GDHCPIAPrefix *copy_prefix(gpointer data) +{ + GDHCPIAPrefix *copy, *prefix = data; + + copy = g_try_new(GDHCPIAPrefix, 1); + if (!copy) + return NULL; + + memcpy(copy, prefix, sizeof(GDHCPIAPrefix)); + + return copy; +} + +GSList *g_dhcpv6_copy_prefixes(GSList *prefixes) +{ + GSList *copy = NULL; + GSList *list; + + for (list = prefixes; list; list = list->next) + copy = g_slist_prepend(copy, copy_prefix(list->data)); + + return copy; +} diff --git a/src/gdhcp/common.c b/src/gdhcp/common.c new file mode 100644 index 0000000..91897b1 --- /dev/null +++ b/src/gdhcp/common.c @@ -0,0 +1,759 @@ +/* + * DHCP library with GLib integration + * + * Copyright (C) 2007-2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gdhcp.h" +#include "common.h" + +static const DHCPOption client_options[] = { + { OPTION_IP, 0x01 }, /* subnet-mask */ + { OPTION_IP | OPTION_LIST, 0x03 }, /* routers */ + { OPTION_IP | OPTION_LIST, 0x06 }, /* domain-name-servers */ + { OPTION_STRING, 0x0c }, /* hostname */ + { OPTION_STRING, 0x0f }, /* domain-name */ + { OPTION_IP | OPTION_LIST, 0x2a }, /* ntp-servers */ + { OPTION_U32, 0x33 }, /* dhcp-lease-time */ + /* Options below will not be exposed to user */ + { OPTION_IP, 0x32 }, /* requested-ip */ + { OPTION_U8, 0x35 }, /* message-type */ + { OPTION_U32, 0x36 }, /* server-id */ + { OPTION_U16, 0x39 }, /* max-size */ + { OPTION_STRING, 0x3c }, /* vendor */ + { OPTION_STRING, 0x3d }, /* client-id */ + { OPTION_STRING, 0xfc }, /* UNOFFICIAL proxy-pac */ + { OPTION_UNKNOWN, 0x00 }, +}; + +GDHCPOptionType dhcp_get_code_type(uint8_t code) +{ + int i; + + for (i = 0; client_options[i].code; i++) { + if (client_options[i].code == code) + return client_options[i].type; + } + + return OPTION_UNKNOWN; +} + +uint8_t *dhcp_get_option(struct dhcp_packet *packet, int code) +{ + int len, rem; + uint8_t *optionptr; + uint8_t overload = 0; + + /* option bytes: [code][len][data1][data2]..[dataLEN] */ + optionptr = packet->options; + rem = sizeof(packet->options); + + while (1) { + if (rem <= 0) + /* Bad packet, malformed option field */ + return NULL; + + if (optionptr[OPT_CODE] == DHCP_PADDING) { + rem--; + optionptr++; + + continue; + } + + if (optionptr[OPT_CODE] == DHCP_END) { + if (overload & FILE_FIELD) { + overload &= ~FILE_FIELD; + + optionptr = packet->file; + rem = sizeof(packet->file); + + continue; + } else if (overload & SNAME_FIELD) { + overload &= ~SNAME_FIELD; + + optionptr = packet->sname; + rem = sizeof(packet->sname); + + continue; + } + + break; + } + + len = 2 + optionptr[OPT_LEN]; + + rem -= len; + if (rem < 0) + continue; /* complain and return NULL */ + + if (optionptr[OPT_CODE] == code) + return optionptr + OPT_DATA; + + if (optionptr[OPT_CODE] == DHCP_OPTION_OVERLOAD) + overload |= optionptr[OPT_DATA]; + + optionptr += len; + } + + return NULL; +} + +int dhcp_end_option(uint8_t *optionptr) +{ + int i = 0; + + while (optionptr[i] != DHCP_END) { + if (optionptr[i] != DHCP_PADDING) + i += optionptr[i + OPT_LEN] + OPT_DATA - 1; + + i++; + } + + return i; +} + +uint8_t *dhcpv6_get_option(struct dhcpv6_packet *packet, uint16_t pkt_len, + int code, uint16_t *option_len, int *option_count) +{ + int rem, count = 0; + uint8_t *optionptr, *found = NULL; + uint16_t opt_code, opt_len, len; + + optionptr = packet->options; + rem = pkt_len - 1 - 3; + + if (rem <= 0) + goto bad_packet; + + while (1) { + opt_code = optionptr[0] << 8 | optionptr[1]; + opt_len = len = optionptr[2] << 8 | optionptr[3]; + len += 2 + 2; /* skip code and len */ + + if (len < 4) + goto bad_packet; + + rem -= len; + if (rem < 0) + break; + + if (opt_code == code) { + if (option_len) + *option_len = opt_len; + if (rem < 0) + goto bad_packet; + else + found = optionptr + 2 + 2; + count++; + } + + if (rem == 0) + break; + + optionptr += len; + } + + if (option_count) + *option_count = count; + + return found; + +bad_packet: + if (option_len) + *option_len = 0; + if (option_count) + *option_count = 0; + return NULL; +} + +uint8_t *dhcpv6_get_sub_option(unsigned char *option, uint16_t max_len, + uint16_t *option_code, uint16_t *option_len) +{ + int rem; + uint16_t code, len; + + rem = max_len - 2 - 2; + + if (rem <= 0) + /* Bad option */ + return NULL; + + code = option[0] << 8 | option[1]; + len = option[2] << 8 | option[3]; + + rem -= len; + if (rem < 0) + return NULL; + + *option_code = code; + *option_len = len; + + return &option[4]; +} + +/* + * Add an option (supplied in binary form) to the options. + * Option format: [code][len][data1][data2]..[dataLEN] + */ +void dhcp_add_binary_option(struct dhcp_packet *packet, uint8_t *addopt) +{ + unsigned len; + uint8_t *optionptr = packet->options; + unsigned end = dhcp_end_option(optionptr); + + len = OPT_DATA + addopt[OPT_LEN]; + + /* end position + (option code/length + addopt length) + end option */ + if (end + len + 1 >= DHCP_OPTIONS_BUFSIZE) + /* option did not fit into the packet */ + return; + + memcpy(optionptr + end, addopt, len); + + optionptr[end + len] = DHCP_END; +} + +/* + * Add an option (supplied in binary form) to the options. + * Option format: [code][len][data1][data2]..[dataLEN] + */ +void dhcpv6_add_binary_option(struct dhcpv6_packet *packet, uint16_t max_len, + uint16_t *pkt_len, uint8_t *addopt) +{ + unsigned len; + uint8_t *optionptr = packet->options; + + len = 2 + 2 + (addopt[2] << 8 | addopt[3]); + + /* end position + (option code/length + addopt length) */ + if (*pkt_len + len >= max_len) + /* option did not fit into the packet */ + return; + + memcpy(optionptr + *pkt_len, addopt, len); + *pkt_len += len; +} + +static GDHCPOptionType check_option(uint8_t code, uint8_t data_len) +{ + GDHCPOptionType type = dhcp_get_code_type(code); + uint8_t len; + + if (type == OPTION_UNKNOWN) + return type; + + len = dhcp_option_lengths[type & OPTION_TYPE_MASK]; + if (len != data_len) { + printf("Invalid option len %d (expecting %d) for code 0x%x\n", + data_len, len, code); + return OPTION_UNKNOWN; + } + + return type; +} + +void dhcp_add_option_uint32(struct dhcp_packet *packet, uint8_t code, + uint32_t data) +{ + uint8_t option[6]; + + if (check_option(code, sizeof(data)) == OPTION_UNKNOWN) + return; + + option[OPT_CODE] = code; + option[OPT_LEN] = sizeof(data); + put_be32(data, option + OPT_DATA); + + dhcp_add_binary_option(packet, option); + + return; +} + +void dhcp_add_option_uint16(struct dhcp_packet *packet, uint8_t code, + uint16_t data) +{ + uint8_t option[6]; + + if (check_option(code, sizeof(data)) == OPTION_UNKNOWN) + return; + + option[OPT_CODE] = code; + option[OPT_LEN] = sizeof(data); + put_be16(data, option + OPT_DATA); + + dhcp_add_binary_option(packet, option); + + return; +} + +void dhcp_add_option_uint8(struct dhcp_packet *packet, uint8_t code, + uint8_t data) +{ + uint8_t option[6]; + + if (check_option(code, sizeof(data)) == OPTION_UNKNOWN) + return; + + option[OPT_CODE] = code; + option[OPT_LEN] = sizeof(data); + option[OPT_DATA] = data; + + dhcp_add_binary_option(packet, option); + + return; +} + +void dhcp_init_header(struct dhcp_packet *packet, char type) +{ + memset(packet, 0, sizeof(*packet)); + + packet->op = BOOTREQUEST; + + switch (type) { + case DHCPOFFER: + case DHCPACK: + case DHCPNAK: + packet->op = BOOTREPLY; + } + + packet->htype = 1; + packet->hlen = 6; + packet->cookie = htonl(DHCP_MAGIC); + packet->options[0] = DHCP_END; + + dhcp_add_option_uint8(packet, DHCP_MESSAGE_TYPE, type); +} + +void dhcpv6_init_header(struct dhcpv6_packet *packet, uint8_t type) +{ + int id; + + memset(packet, 0, sizeof(*packet)); + + packet->message = type; + + id = random(); + + packet->transaction_id[0] = (id >> 16) & 0xff; + packet->transaction_id[1] = (id >> 8) & 0xff; + packet->transaction_id[2] = id & 0xff; +} + +static bool check_vendor(uint8_t *option_vendor, const char *vendor) +{ + uint8_t vendor_length = sizeof(vendor) - 1; + + if (option_vendor[OPT_LEN - OPT_DATA] != vendor_length) + return false; + + if (memcmp(option_vendor, vendor, vendor_length) != 0) + return false; + + return true; +} + +static void check_broken_vendor(struct dhcp_packet *packet) +{ + uint8_t *vendor; + + if (packet->op != BOOTREQUEST) + return; + + vendor = dhcp_get_option(packet, DHCP_VENDOR); + if (!vendor) + return; + + if (check_vendor(vendor, "MSFT 98")) + packet->flags |= htons(BROADCAST_FLAG); +} + +int dhcp_recv_l3_packet(struct dhcp_packet *packet, int fd) +{ + int n; + + memset(packet, 0, sizeof(*packet)); + + n = read(fd, packet, sizeof(*packet)); + if (n < 0) + return -errno; + + if (packet->cookie != htonl(DHCP_MAGIC)) + return -EPROTO; + + check_broken_vendor(packet); + + return n; +} + +int dhcpv6_recv_l3_packet(struct dhcpv6_packet **packet, unsigned char *buf, + int buf_len, int fd) +{ + int n; + + n = read(fd, buf, buf_len); + if (n < 0) + return -errno; + + *packet = (struct dhcpv6_packet *)buf; + + return n; +} + +/* TODO: Use glib checksum */ +uint16_t dhcp_checksum(void *addr, int count) +{ + /* + * Compute Internet Checksum for "count" bytes + * beginning at location "addr". + */ + int32_t sum = 0; + uint16_t *source = (uint16_t *) addr; + + while (count > 1) { + /* This is the inner loop */ + sum += *source++; + count -= 2; + } + + /* Add left-over byte, if any */ + if (count > 0) { + /* Make sure that the left-over byte is added correctly both + * with little and big endian hosts */ + uint16_t tmp = 0; + *(uint8_t *) &tmp = *(uint8_t *) source; + sum += tmp; + } + /* Fold 32-bit sum to 16 bits */ + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + + return ~sum; +} + +#define IN6ADDR_ALL_DHCP_RELAY_AGENTS_AND_SERVERS_MC_INIT \ + { { { 0xff,0x02,0,0,0,0,0,0,0,0,0,0,0,0x1,0,0x2 } } } /* ff02::1:2 */ +static const struct in6_addr in6addr_all_dhcp_relay_agents_and_servers_mc = + IN6ADDR_ALL_DHCP_RELAY_AGENTS_AND_SERVERS_MC_INIT; + +int dhcpv6_send_packet(int index, struct dhcpv6_packet *dhcp_pkt, int len) +{ + struct msghdr m; + struct iovec v; + struct in6_pktinfo *pktinfo; + struct cmsghdr *cmsg; + int fd, ret; + struct sockaddr_in6 dst; + void *control_buf; + size_t control_buf_len; + + fd = socket(PF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP); + if (fd < 0) + return -errno; + + memset(&dst, 0, sizeof(dst)); + dst.sin6_family = AF_INET6; + dst.sin6_port = htons(DHCPV6_SERVER_PORT); + + dst.sin6_addr = in6addr_all_dhcp_relay_agents_and_servers_mc; + + control_buf_len = CMSG_SPACE(sizeof(struct in6_pktinfo)); + control_buf = g_try_malloc0(control_buf_len); + if (!control_buf) { + close(fd); + return -ENOMEM; + } + + memset(&m, 0, sizeof(m)); + memset(&v, 0, sizeof(v)); + + m.msg_name = &dst; + m.msg_namelen = sizeof(dst); + + v.iov_base = (char *)dhcp_pkt; + v.iov_len = len; + m.msg_iov = &v; + m.msg_iovlen = 1; + + m.msg_control = control_buf; + m.msg_controllen = control_buf_len; + cmsg = CMSG_FIRSTHDR(&m); + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(*pktinfo)); + + pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg); + memset(pktinfo, 0, sizeof(*pktinfo)); + pktinfo->ipi6_ifindex = index; + m.msg_controllen = cmsg->cmsg_len; + + ret = sendmsg(fd, &m, 0); + if (ret < 0) { + char *msg = "DHCPv6 msg send failed"; + + if (errno == EADDRNOTAVAIL) { + char *str = g_strdup_printf("%s (index %d)", + msg, index); + perror(str); + g_free(str); + } else + perror(msg); + } + + g_free(control_buf); + close(fd); + + return ret; +} + +int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt, + uint32_t source_ip, int source_port, uint32_t dest_ip, + int dest_port, const uint8_t *dest_arp, int ifindex) +{ + struct sockaddr_ll dest; + struct ip_udp_dhcp_packet packet; + int fd, n; + + enum { + IP_UPD_DHCP_SIZE = sizeof(struct ip_udp_dhcp_packet) - + EXTEND_FOR_BUGGY_SERVERS, + UPD_DHCP_SIZE = IP_UPD_DHCP_SIZE - + offsetof(struct ip_udp_dhcp_packet, udp), + }; + + fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IP)); + if (fd < 0) + return -errno; + + memset(&dest, 0, sizeof(dest)); + memset(&packet, 0, sizeof(packet)); + packet.data = *dhcp_pkt; + + dest.sll_family = AF_PACKET; + dest.sll_protocol = htons(ETH_P_IP); + dest.sll_ifindex = ifindex; + dest.sll_halen = 6; + memcpy(dest.sll_addr, dest_arp, 6); + if (bind(fd, (struct sockaddr *)&dest, sizeof(dest)) < 0) { + close(fd); + return -errno; + } + + packet.ip.protocol = IPPROTO_UDP; + packet.ip.saddr = source_ip; + packet.ip.daddr = dest_ip; + packet.udp.source = htons(source_port); + packet.udp.dest = htons(dest_port); + /* size, excluding IP header: */ + packet.udp.len = htons(UPD_DHCP_SIZE); + /* for UDP checksumming, ip.len is set to UDP packet len */ + packet.ip.tot_len = packet.udp.len; + packet.udp.check = dhcp_checksum(&packet, IP_UPD_DHCP_SIZE); + /* but for sending, it is set to IP packet len */ + packet.ip.tot_len = htons(IP_UPD_DHCP_SIZE); + packet.ip.ihl = sizeof(packet.ip) >> 2; + packet.ip.version = IPVERSION; + packet.ip.ttl = IPDEFTTL; + packet.ip.check = dhcp_checksum(&packet.ip, sizeof(packet.ip)); + + /* + * Currently we send full-sized DHCP packets (zero padded). + * If you need to change this: last byte of the packet is + * packet.data.options[dhcp_end_option(packet.data.options)] + */ + n = sendto(fd, &packet, IP_UPD_DHCP_SIZE, 0, + (struct sockaddr *) &dest, sizeof(dest)); + close(fd); + + if (n < 0) + return -errno; + + return n; +} + +int dhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt, + uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port) +{ + struct sockaddr_in client; + int fd, n, opt = 1; + + enum { + DHCP_SIZE = sizeof(struct dhcp_packet) - + EXTEND_FOR_BUGGY_SERVERS, + }; + + fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP); + if (fd < 0) + return -errno; + + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + memset(&client, 0, sizeof(client)); + client.sin_family = AF_INET; + client.sin_port = htons(source_port); + client.sin_addr.s_addr = htonl(source_ip); + if (bind(fd, (struct sockaddr *) &client, sizeof(client)) < 0) { + close(fd); + return -errno; + } + + memset(&client, 0, sizeof(client)); + client.sin_family = AF_INET; + client.sin_port = htons(dest_port); + client.sin_addr.s_addr = htonl(dest_ip); + if (connect(fd, (struct sockaddr *) &client, sizeof(client)) < 0) { + close(fd); + return -errno; + } + + n = write(fd, dhcp_pkt, DHCP_SIZE); + + close(fd); + + if (n < 0) + return -errno; + + return n; +} + +int dhcp_l3_socket(int port, const char *interface, int family) +{ + int fd, opt = 1, len; + struct sockaddr_in addr4; + struct sockaddr_in6 addr6; + struct sockaddr *addr; + + fd = socket(family, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP); + if (fd < 0) + return -errno; + + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, + interface, strlen(interface) + 1) < 0) { + close(fd); + return -1; + } + + if (family == AF_INET) { + memset(&addr4, 0, sizeof(addr4)); + addr4.sin_family = family; + addr4.sin_port = htons(port); + addr = (struct sockaddr *)&addr4; + len = sizeof(addr4); + } else if (family == AF_INET6) { + memset(&addr6, 0, sizeof(addr6)); + addr6.sin6_family = family; + addr6.sin6_port = htons(port); + addr = (struct sockaddr *)&addr6; + len = sizeof(addr6); + } else { + close(fd); + return -EINVAL; + } + + if (bind(fd, addr, len) != 0) { + close(fd); + return -1; + } + + return fd; +} + +char *get_interface_name(int index) +{ + struct ifreq ifr; + int sk, err; + + if (index < 0) + return NULL; + + sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (sk < 0) { + perror("Open socket error"); + return NULL; + } + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_ifindex = index; + + err = ioctl(sk, SIOCGIFNAME, &ifr); + if (err < 0) { + perror("Get interface name error"); + close(sk); + return NULL; + } + + close(sk); + + return g_strdup(ifr.ifr_name); +} + +bool interface_is_up(int index) +{ + int sk, err; + struct ifreq ifr; + bool ret = false; + + sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (sk < 0) { + perror("Open socket error"); + return false; + } + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_ifindex = index; + + err = ioctl(sk, SIOCGIFNAME, &ifr); + if (err < 0) { + perror("Get interface name error"); + goto done; + } + + err = ioctl(sk, SIOCGIFFLAGS, &ifr); + if (err < 0) { + perror("Get interface flags error"); + goto done; + } + + if (ifr.ifr_flags & IFF_UP) + ret = true; + +done: + close(sk); + + return ret; +} diff --git a/src/gdhcp/common.h b/src/gdhcp/common.h new file mode 100644 index 0000000..e4a4251 --- /dev/null +++ b/src/gdhcp/common.h @@ -0,0 +1,211 @@ +/* + * + * DHCP client library with GLib integration + * + * Copyright (C) 2009-2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +#include + +#include "unaligned.h" +#include "gdhcp.h" + +#define CLIENT_PORT 68 +#define SERVER_PORT 67 + +#define DHCPV6_CLIENT_PORT 546 +#define DHCPV6_SERVER_PORT 547 +#define MAX_DHCPV6_PKT_SIZE 1500 + +#define EXTEND_FOR_BUGGY_SERVERS 80 + +static const uint8_t MAC_BCAST_ADDR[ETH_ALEN] __attribute__((aligned(2))) = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +static const uint8_t MAC_ANY_ADDR[ETH_ALEN] __attribute__((aligned(2))) = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* DHCP packet */ +#define DHCP_MAGIC 0x63825363 +#define DHCP_OPTIONS_BUFSIZE 308 +#define BOOTREQUEST 1 +#define BOOTREPLY 2 + +#define BROADCAST_FLAG 0x8000 + +/* See RFC 2131 */ +struct dhcp_packet { + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; + uint32_t xid; + uint16_t secs; + uint16_t flags; + uint32_t ciaddr; + uint32_t yiaddr; + uint32_t siaddr_nip; + uint32_t gateway_nip; + uint8_t chaddr[16]; + uint8_t sname[64]; + uint8_t file[128]; + uint32_t cookie; + uint8_t options[DHCP_OPTIONS_BUFSIZE + EXTEND_FOR_BUGGY_SERVERS]; +} __attribute__((packed)); + +struct ip_udp_dhcp_packet { + struct iphdr ip; + struct udphdr udp; + struct dhcp_packet data; +} __attribute__((packed)); + +/* See RFC 3315 */ +struct dhcpv6_packet { + uint8_t message; + uint8_t transaction_id[3]; + uint8_t options[]; +} __attribute__((packed)); + + +/* See RFC 2132 */ +#define DHCP_PADDING 0x00 +#define DHCP_SUBNET 0x01 +#define DHCP_ROUTER 0x03 +#define DHCP_TIME_SERVER 0x04 +#define DHCP_NAME_SERVER 0x05 +#define DHCP_DNS_SERVER 0x06 +#define DHCP_HOST_NAME 0x0c +#define DHCP_DOMAIN_NAME 0x0f +#define DHCP_NTP_SERVER 0x2a +#define DHCP_REQUESTED_IP 0x32 +#define DHCP_LEASE_TIME 0x33 +#define DHCP_OPTION_OVERLOAD 0x34 +#define DHCP_MESSAGE_TYPE 0x35 +#define DHCP_SERVER_ID 0x36 +#define DHCP_PARAM_REQ 0x37 +#define DHCP_ERR_MESSAGE 0x38 +#define DHCP_MAX_SIZE 0x39 +#define DHCP_VENDOR 0x3c +#define DHCP_CLIENT_ID 0x3d +#define DHCP_END 0xff + +#define OPT_CODE 0 +#define OPT_LEN 1 +#define OPT_DATA 2 +#define OPTION_FIELD 0 +#define FILE_FIELD 1 +#define SNAME_FIELD 2 + +/* DHCP_MESSAGE_TYPE values */ +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPDECLINE 4 +#define DHCPACK 5 +#define DHCPNAK 6 +#define DHCPRELEASE 7 +#define DHCPINFORM 8 +#define DHCP_MINTYPE DHCPDISCOVER +#define DHCP_MAXTYPE DHCPINFORM + +/* Message types for DHCPv6, RFC 3315 sec 5.3 */ +#define DHCPV6_SOLICIT 1 +#define DHCPV6_ADVERTISE 2 +#define DHCPV6_REQUEST 3 +#define DHCPV6_CONFIRM 4 +#define DHCPV6_RENEW 5 +#define DHCPV6_REBIND 6 +#define DHCPV6_REPLY 7 +#define DHCPV6_RELEASE 8 +#define DHCPV6_DECLINE 9 +#define DHCPV6_RECONFIGURE 10 +#define DHCPV6_INFORMATION_REQ 11 + +/* + * DUID time starts 2000-01-01. + */ +#define DUID_TIME_EPOCH 946684800 + +typedef enum { + OPTION_UNKNOWN, + OPTION_IP, + OPTION_STRING, + OPTION_U8, + OPTION_U16, + OPTION_U32, + OPTION_TYPE_MASK = 0x0f, + OPTION_LIST = 0x10, +} GDHCPOptionType; + +typedef struct dhcp_option { + GDHCPOptionType type; + uint8_t code; +} DHCPOption; + +/* Length of the option types in binary form */ +static const uint8_t dhcp_option_lengths[] = { + [OPTION_IP] = 4, + [OPTION_STRING] = 1, + [OPTION_U8] = 1, + [OPTION_U16] = 2, + [OPTION_U32] = 4, +}; + +uint8_t *dhcp_get_option(struct dhcp_packet *packet, int code); +uint8_t *dhcpv6_get_option(struct dhcpv6_packet *packet, uint16_t pkt_len, + int code, uint16_t *option_len, int *option_count); +uint8_t *dhcpv6_get_sub_option(unsigned char *option, uint16_t max_len, + uint16_t *code, uint16_t *option_len); +int dhcp_end_option(uint8_t *optionptr); +void dhcp_add_binary_option(struct dhcp_packet *packet, uint8_t *addopt); +void dhcpv6_add_binary_option(struct dhcpv6_packet *packet, uint16_t max_len, + uint16_t *pkt_len, uint8_t *addopt); +void dhcp_add_option_uint8(struct dhcp_packet *packet, + uint8_t code, uint8_t data); +void dhcp_add_option_uint16(struct dhcp_packet *packet, + uint8_t code, uint16_t data); +void dhcp_add_option_uint32(struct dhcp_packet *packet, + uint8_t code, uint32_t data); +GDHCPOptionType dhcp_get_code_type(uint8_t code); +GDHCPOptionType dhcpv6_get_code_type(uint16_t code); + +uint16_t dhcp_checksum(void *addr, int count); + +void dhcp_init_header(struct dhcp_packet *packet, char type); +void dhcpv6_init_header(struct dhcpv6_packet *packet, uint8_t type); + +int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt, + uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port, + const uint8_t *dest_arp, int ifindex); +int dhcpv6_send_packet(int index, struct dhcpv6_packet *dhcp_pkt, int len); +int dhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt, + uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port); +int dhcp_l3_socket(int port, const char *interface, int family); +int dhcp_recv_l3_packet(struct dhcp_packet *packet, int fd); +int dhcpv6_recv_l3_packet(struct dhcpv6_packet **packet, unsigned char *buf, + int buf_len, int fd); +int dhcp_l3_socket_send(int index, int port, int family); + +char *get_interface_name(int index); +bool interface_is_up(int index); diff --git a/src/gdhcp/gdhcp.h b/src/gdhcp/gdhcp.h new file mode 100644 index 0000000..0d79361 --- /dev/null +++ b/src/gdhcp/gdhcp.h @@ -0,0 +1,230 @@ +/* + * + * DHCP library with GLib integration + * + * Copyright (C) 2009-2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __G_DHCP_H +#define __G_DHCP_H + +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* DHCP Client part*/ +struct _GDHCPClient; + +typedef struct _GDHCPClient GDHCPClient; + +typedef enum { + G_DHCP_CLIENT_ERROR_NONE, + G_DHCP_CLIENT_ERROR_INTERFACE_UNAVAILABLE, + G_DHCP_CLIENT_ERROR_INTERFACE_IN_USE, + G_DHCP_CLIENT_ERROR_INTERFACE_DOWN, + G_DHCP_CLIENT_ERROR_NOMEM, + G_DHCP_CLIENT_ERROR_INVALID_INDEX, + G_DHCP_CLIENT_ERROR_INVALID_OPTION +} GDHCPClientError; + +typedef enum { + G_DHCP_CLIENT_EVENT_LEASE_AVAILABLE, + G_DHCP_CLIENT_EVENT_IPV4LL_AVAILABLE, + G_DHCP_CLIENT_EVENT_NO_LEASE, + G_DHCP_CLIENT_EVENT_LEASE_LOST, + G_DHCP_CLIENT_EVENT_IPV4LL_LOST, + G_DHCP_CLIENT_EVENT_ADDRESS_CONFLICT, + G_DHCP_CLIENT_EVENT_INFORMATION_REQ, + G_DHCP_CLIENT_EVENT_SOLICITATION, + G_DHCP_CLIENT_EVENT_ADVERTISE, + G_DHCP_CLIENT_EVENT_REQUEST, + G_DHCP_CLIENT_EVENT_RENEW, + G_DHCP_CLIENT_EVENT_REBIND, + G_DHCP_CLIENT_EVENT_RELEASE, + G_DHCP_CLIENT_EVENT_CONFIRM, + G_DHCP_CLIENT_EVENT_DECLINE, +} GDHCPClientEvent; + +typedef enum { + G_DHCP_IPV4, + G_DHCP_IPV6, + G_DHCP_IPV4LL, +} GDHCPType; + +#define G_DHCP_SUBNET 0x01 +#define G_DHCP_ROUTER 0x03 +#define G_DHCP_TIME_SERVER 0x04 +#define G_DHCP_DNS_SERVER 0x06 +#define G_DHCP_DOMAIN_NAME 0x0f +#define G_DHCP_HOST_NAME 0x0c +#define G_DHCP_NTP_SERVER 0x2a +#define G_DHCP_CLIENT_ID 0x3d + +#define G_DHCPV6_CLIENTID 1 +#define G_DHCPV6_SERVERID 2 +#define G_DHCPV6_IA_NA 3 +#define G_DHCPV6_IA_TA 4 +#define G_DHCPV6_IAADDR 5 +#define G_DHCPV6_ORO 6 +#define G_DHCPV6_PREFERENCE 7 +#define G_DHCPV6_ELAPSED_TIME 8 +#define G_DHCPV6_STATUS_CODE 13 +#define G_DHCPV6_RAPID_COMMIT 14 +#define G_DHCPV6_DNS_SERVERS 23 +#define G_DHCPV6_DOMAIN_LIST 24 +#define G_DHCPV6_IA_PD 25 +#define G_DHCPV6_IA_PREFIX 26 +#define G_DHCPV6_SNTP_SERVERS 31 + +#define G_DHCPV6_ERROR_SUCCESS 0 +#define G_DHCPV6_ERROR_FAILURE 1 +#define G_DHCPV6_ERROR_NO_ADDR 2 +#define G_DHCPV6_ERROR_BINDING 3 +#define G_DHCPV6_ERROR_LINK 4 +#define G_DHCPV6_ERROR_MCAST 5 +#define G_DHCPV6_ERROR_NO_PREFIX 6 + +typedef enum { + G_DHCPV6_DUID_LLT = 1, + G_DHCPV6_DUID_EN = 2, + G_DHCPV6_DUID_LL = 3, +} GDHCPDuidType; + +typedef struct { + /* + * Note that no field in this struct can be allocated + * from heap or there will be a memory leak when the + * struct is freed by client.c:remove_option_value() + */ + struct in6_addr prefix; + unsigned char prefixlen; + uint32_t preferred; + uint32_t valid; + time_t expire; +} GDHCPIAPrefix; + +typedef void (*GDHCPClientEventFunc) (GDHCPClient *client, gpointer user_data); + +typedef void (*GDHCPDebugFunc)(const char *str, gpointer user_data); + +GDHCPClient *g_dhcp_client_new(GDHCPType type, int index, + GDHCPClientError *error); + +int g_dhcp_client_start(GDHCPClient *client, const char *last_address); +void g_dhcp_client_stop(GDHCPClient *client); + +GDHCPClient *g_dhcp_client_ref(GDHCPClient *client); +void g_dhcp_client_unref(GDHCPClient *client); + +void g_dhcp_client_register_event(GDHCPClient *client, + GDHCPClientEvent event, + GDHCPClientEventFunc func, + gpointer user_data); + +GDHCPClientError g_dhcp_client_set_request(GDHCPClient *client, + unsigned int option_code); +void g_dhcp_client_clear_requests(GDHCPClient *dhcp_client); +void g_dhcp_client_clear_values(GDHCPClient *dhcp_client); +GDHCPClientError g_dhcp_client_set_id(GDHCPClient *client); +GDHCPClientError g_dhcp_client_set_send(GDHCPClient *client, + unsigned char option_code, + const char *option_value); + +char *g_dhcp_client_get_address(GDHCPClient *client); +char *g_dhcp_client_get_netmask(GDHCPClient *client); +GList *g_dhcp_client_get_option(GDHCPClient *client, + unsigned char option_code); +int g_dhcp_client_get_index(GDHCPClient *client); + +void g_dhcp_client_set_debug(GDHCPClient *client, + GDHCPDebugFunc func, gpointer user_data); +int g_dhcpv6_create_duid(GDHCPDuidType duid_type, int index, int type, + unsigned char **duid, int *duid_len); +int g_dhcpv6_client_set_duid(GDHCPClient *dhcp_client, unsigned char *duid, + int duid_len); +int g_dhcpv6_client_set_pd(GDHCPClient *dhcp_client, uint32_t *T1, uint32_t *T2, + GSList *prefixes); +GSList *g_dhcpv6_copy_prefixes(GSList *prefixes); +gboolean g_dhcpv6_client_clear_send(GDHCPClient *dhcp_client, uint16_t code); +void g_dhcpv6_client_set_send(GDHCPClient *dhcp_client, uint16_t option_code, + uint8_t *option_value, uint16_t option_len); +uint16_t g_dhcpv6_client_get_status(GDHCPClient *dhcp_client); +int g_dhcpv6_client_set_oro(GDHCPClient *dhcp_client, int args, ...); +void g_dhcpv6_client_create_iaid(GDHCPClient *dhcp_client, int index, + unsigned char *iaid); +int g_dhcpv6_client_get_timeouts(GDHCPClient *dhcp_client, + uint32_t *T1, uint32_t *T2, + time_t *started, time_t *expire); +uint32_t g_dhcpv6_client_get_iaid(GDHCPClient *dhcp_client); +void g_dhcpv6_client_set_iaid(GDHCPClient *dhcp_client, uint32_t iaid); +int g_dhcpv6_client_set_ia(GDHCPClient *dhcp_client, int index, + int code, uint32_t *T1, uint32_t *T2, + bool add_addresses, const char *address); +int g_dhcpv6_client_set_ias(GDHCPClient *dhcp_client, int index, + int code, uint32_t *T1, uint32_t *T2, + GSList *addresses); +void g_dhcpv6_client_reset_request(GDHCPClient *dhcp_client); +void g_dhcpv6_client_set_retransmit(GDHCPClient *dhcp_client); +void g_dhcpv6_client_clear_retransmit(GDHCPClient *dhcp_client); + +/* DHCP Server */ +typedef enum { + G_DHCP_SERVER_ERROR_NONE, + G_DHCP_SERVER_ERROR_INTERFACE_UNAVAILABLE, + G_DHCP_SERVER_ERROR_INTERFACE_IN_USE, + G_DHCP_SERVER_ERROR_INTERFACE_DOWN, + G_DHCP_SERVER_ERROR_NOMEM, + G_DHCP_SERVER_ERROR_INVALID_INDEX, + G_DHCP_SERVER_ERROR_INVALID_OPTION, + G_DHCP_SERVER_ERROR_IP_ADDRESS_INVALID +} GDHCPServerError; + +typedef void (*GDHCPSaveLeaseFunc) (unsigned char *mac, + unsigned int nip, unsigned int expire); +struct _GDHCPServer; + +typedef struct _GDHCPServer GDHCPServer; + +GDHCPServer *g_dhcp_server_new(GDHCPType type, + int ifindex, GDHCPServerError *error); +int g_dhcp_server_start(GDHCPServer *server); +void g_dhcp_server_stop(GDHCPServer *server); + +GDHCPServer *g_dhcp_server_ref(GDHCPServer *server); +void g_dhcp_server_unref(GDHCPServer *server); + +int g_dhcp_server_set_option(GDHCPServer *server, + unsigned char option_code, const char *option_value); +int g_dhcp_server_set_ip_range(GDHCPServer *server, + const char *start_ip, const char *end_ip); +void g_dhcp_server_set_debug(GDHCPServer *server, + GDHCPDebugFunc func, gpointer user_data); +void g_dhcp_server_set_lease_time(GDHCPServer *dhcp_server, + unsigned int lease_time); +void g_dhcp_server_set_save_lease(GDHCPServer *dhcp_server, + GDHCPSaveLeaseFunc func, gpointer user_data); +#ifdef __cplusplus +} +#endif + +#endif /* __G_DHCP_H */ diff --git a/src/gdhcp/ipv4ll.c b/src/gdhcp/ipv4ll.c new file mode 100644 index 0000000..033ef81 --- /dev/null +++ b/src/gdhcp/ipv4ll.c @@ -0,0 +1,139 @@ +/* + * + * IPV4 Local Link library with GLib integration + * + * Copyright (C) 2009-2010 Aldebaran Robotics. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "ipv4ll.h" + +/** + * Return a random link local IP (in host byte order) + */ +uint32_t ipv4ll_random_ip(int seed) +{ + unsigned tmp; + + if (seed) + srand(seed); + else { + struct timeval tv; + gettimeofday(&tv, NULL); + srand(tv.tv_usec); + } + do { + tmp = rand(); + tmp = tmp & IN_CLASSB_HOST; + } while (tmp > (IN_CLASSB_HOST - 0x0200)); + return ((LINKLOCAL_ADDR + 0x0100) + tmp); +} + +/** + * Return a random delay in range of zero to secs*1000 + */ +guint ipv4ll_random_delay_ms(guint secs) +{ + struct timeval tv; + guint tmp; + + gettimeofday(&tv, NULL); + srand(tv.tv_usec); + tmp = rand(); + return tmp % (secs * 1000); +} + +int ipv4ll_send_arp_packet(uint8_t* source_eth, uint32_t source_ip, + uint32_t target_ip, int ifindex) +{ + struct sockaddr_ll dest; + struct ether_arp p; + uint32_t ip_source; + uint32_t ip_target; + int fd, n; + + fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_ARP)); + if (fd < 0) + return -errno; + + memset(&dest, 0, sizeof(dest)); + memset(&p, 0, sizeof(p)); + + dest.sll_family = AF_PACKET; + dest.sll_protocol = htons(ETH_P_ARP); + dest.sll_ifindex = ifindex; + dest.sll_halen = ETH_ALEN; + memset(dest.sll_addr, 0xFF, ETH_ALEN); + if (bind(fd, (struct sockaddr *)&dest, sizeof(dest)) < 0) { + close(fd); + return -errno; + } + + ip_source = htonl(source_ip); + ip_target = htonl(target_ip); + p.arp_hrd = htons(ARPHRD_ETHER); + p.arp_pro = htons(ETHERTYPE_IP); + p.arp_hln = ETH_ALEN; + p.arp_pln = 4; + p.arp_op = htons(ARPOP_REQUEST); + + memcpy(&p.arp_sha, source_eth, ETH_ALEN); + memcpy(&p.arp_spa, &ip_source, sizeof(p.arp_spa)); + memcpy(&p.arp_tpa, &ip_target, sizeof(p.arp_tpa)); + + n = sendto(fd, &p, sizeof(p), 0, + (struct sockaddr*) &dest, sizeof(dest)); + if (n < 0) + n = -errno; + + close(fd); + + return n; +} + +int ipv4ll_arp_socket(int ifindex) +{ + int fd; + struct sockaddr_ll sock; + fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_ARP)); + if (fd < 0) + return fd; + + sock.sll_family = AF_PACKET; + sock.sll_protocol = htons(ETH_P_ARP); + sock.sll_ifindex = ifindex; + + if (bind(fd, (struct sockaddr *) &sock, sizeof(sock)) != 0) { + close(fd); + return -errno; + } + + return fd; +} diff --git a/src/gdhcp/ipv4ll.h b/src/gdhcp/ipv4ll.h new file mode 100644 index 0000000..aaac33e --- /dev/null +++ b/src/gdhcp/ipv4ll.h @@ -0,0 +1,55 @@ +/* + * + * IPV4 Local Link library with GLib integration + * + * Copyright (C) 2009-2010 Aldebaran Robotics. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __G_IPV4LL_H +#define __G_IPV4LL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* 169.254.0.0 */ +#define LINKLOCAL_ADDR 0xa9fe0000 + +/* See RFC 3927 */ +#define PROBE_WAIT 1 +#define PROBE_NUM 3 +#define PROBE_MIN 1 +#define PROBE_MAX 2 +#define ANNOUNCE_WAIT 2 +#define ANNOUNCE_NUM 2 +#define ANNOUNCE_INTERVAL 2 +#define MAX_CONFLICTS 10 +#define RATE_LIMIT_INTERVAL 60 +#define DEFEND_INTERVAL 10 + +uint32_t ipv4ll_random_ip(int seed); +guint ipv4ll_random_delay_ms(guint secs); +int ipv4ll_send_arp_packet(uint8_t* source_eth, uint32_t source_ip, + uint32_t target_ip, int ifindex); +int ipv4ll_arp_socket(int ifindex); + +#ifdef __cplusplus +} +#endif +#endif /* !IPV4LL_H_ */ diff --git a/src/gdhcp/server.c b/src/gdhcp/server.c new file mode 100644 index 0000000..728992d --- /dev/null +++ b/src/gdhcp/server.c @@ -0,0 +1,896 @@ +/* + * + * DHCP Server library with GLib integration + * + * Copyright (C) 2009-2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include "common.h" + +/* 8 hours */ +#define DEFAULT_DHCP_LEASE_SEC (8*60*60) + +/* 5 minutes */ +#define OFFER_TIME (5*60) + +struct _GDHCPServer { + int ref_count; + GDHCPType type; + bool started; + int ifindex; + char *interface; + uint32_t start_ip; + uint32_t end_ip; + uint32_t server_nip; + uint32_t lease_seconds; + int listener_sockfd; + guint listener_watch; + GIOChannel *listener_channel; + GList *lease_list; + GHashTable *nip_lease_hash; + GHashTable *option_hash; /* Options send to client */ + GDHCPSaveLeaseFunc save_lease_func; + GDHCPDebugFunc debug_func; + gpointer debug_data; +}; + +struct dhcp_lease { + time_t expire; + uint32_t lease_nip; + uint8_t lease_mac[ETH_ALEN]; +}; + +static inline void debug(GDHCPServer *server, const char *format, ...) +{ + char str[256]; + va_list ap; + + if (!server->debug_func) + return; + + va_start(ap, format); + + if (vsnprintf(str, sizeof(str), format, ap) > 0) + server->debug_func(str, server->debug_data); + + va_end(ap); +} + +static struct dhcp_lease *find_lease_by_mac(GDHCPServer *dhcp_server, + const uint8_t *mac) +{ + GList *list; + + for (list = dhcp_server->lease_list; list; list = list->next) { + struct dhcp_lease *lease = list->data; + + if (memcmp(lease->lease_mac, mac, ETH_ALEN) == 0) + return lease; + } + + return NULL; +} + +static void remove_lease(GDHCPServer *dhcp_server, struct dhcp_lease *lease) +{ + dhcp_server->lease_list = + g_list_remove(dhcp_server->lease_list, lease); + + g_hash_table_remove(dhcp_server->nip_lease_hash, + GINT_TO_POINTER((int) lease->lease_nip)); + g_free(lease); +} + +/* Clear the old lease and create the new one */ +static int get_lease(GDHCPServer *dhcp_server, uint32_t yiaddr, + const uint8_t *mac, struct dhcp_lease **lease) +{ + struct dhcp_lease *lease_nip, *lease_mac; + + if (yiaddr == 0) + return -ENXIO; + + if (ntohl(yiaddr) < dhcp_server->start_ip) + return -ENXIO; + + if (ntohl(yiaddr) > dhcp_server->end_ip) + return -ENXIO; + + if (memcmp(mac, MAC_BCAST_ADDR, ETH_ALEN) == 0) + return -ENXIO; + + if (memcmp(mac, MAC_ANY_ADDR, ETH_ALEN) == 0) + return -ENXIO; + + lease_mac = find_lease_by_mac(dhcp_server, mac); + + lease_nip = g_hash_table_lookup(dhcp_server->nip_lease_hash, + GINT_TO_POINTER((int) ntohl(yiaddr))); + debug(dhcp_server, "lease_mac %p lease_nip %p", lease_mac, lease_nip); + + if (lease_nip) { + dhcp_server->lease_list = + g_list_remove(dhcp_server->lease_list, + lease_nip); + g_hash_table_remove(dhcp_server->nip_lease_hash, + GINT_TO_POINTER((int) ntohl(yiaddr))); + + if (!lease_mac) + *lease = lease_nip; + else if (lease_nip != lease_mac) { + remove_lease(dhcp_server, lease_mac); + *lease = lease_nip; + } else + *lease = lease_nip; + + return 0; + } + + if (lease_mac) { + dhcp_server->lease_list = + g_list_remove(dhcp_server->lease_list, + lease_mac); + g_hash_table_remove(dhcp_server->nip_lease_hash, + GINT_TO_POINTER((int) lease_mac->lease_nip)); + *lease = lease_mac; + + return 0; + } + + *lease = g_try_new0(struct dhcp_lease, 1); + if (!*lease) + return -ENOMEM; + + return 0; +} + +static gint compare_expire(gconstpointer a, gconstpointer b) +{ + const struct dhcp_lease *lease1 = a; + const struct dhcp_lease *lease2 = b; + + return lease2->expire - lease1->expire; +} + +static struct dhcp_lease *add_lease(GDHCPServer *dhcp_server, uint32_t expire, + const uint8_t *chaddr, uint32_t yiaddr) +{ + struct dhcp_lease *lease = NULL; + int ret; + + ret = get_lease(dhcp_server, yiaddr, chaddr, &lease); + if (ret != 0) + return NULL; + + memset(lease, 0, sizeof(*lease)); + + memcpy(lease->lease_mac, chaddr, ETH_ALEN); + lease->lease_nip = ntohl(yiaddr); + + if (expire == 0) + lease->expire = time(NULL) + dhcp_server->lease_seconds; + else + lease->expire = expire; + + dhcp_server->lease_list = g_list_insert_sorted(dhcp_server->lease_list, + lease, compare_expire); + + g_hash_table_insert(dhcp_server->nip_lease_hash, + GINT_TO_POINTER((int) lease->lease_nip), lease); + + return lease; +} + +static struct dhcp_lease *find_lease_by_nip(GDHCPServer *dhcp_server, + uint32_t nip) +{ + return g_hash_table_lookup(dhcp_server->nip_lease_hash, + GINT_TO_POINTER((int) nip)); +} + +/* Check if the IP is taken; if it is, add it to the lease table */ +static bool arp_check(uint32_t nip, const uint8_t *safe_mac) +{ + /* TODO: Add ARP checking */ + return true; +} + +static bool is_expired_lease(struct dhcp_lease *lease) +{ + if (lease->expire < time(NULL)) + return true; + + return false; +} + +static uint32_t find_free_or_expired_nip(GDHCPServer *dhcp_server, + const uint8_t *safe_mac) +{ + uint32_t ip_addr; + struct dhcp_lease *lease; + GList *list; + ip_addr = dhcp_server->start_ip; + for (; ip_addr <= dhcp_server->end_ip; ip_addr++) { + /* e.g. 192.168.55.0 */ + if ((ip_addr & 0xff) == 0) + continue; + + /* e.g. 192.168.55.255 */ + if ((ip_addr & 0xff) == 0xff) + continue; + + lease = find_lease_by_nip(dhcp_server, ip_addr); + if (lease) + continue; + + if (arp_check(htonl(ip_addr), safe_mac)) + return ip_addr; + } + + /* The last lease is the oldest one */ + list = g_list_last(dhcp_server->lease_list); + if (!list) + return 0; + + lease = list->data; + if (!lease) + return 0; + + if (!is_expired_lease(lease)) + return 0; + + if (!arp_check(lease->lease_nip, safe_mac)) + return 0; + + return lease->lease_nip; +} + +static void lease_set_expire(GDHCPServer *dhcp_server, + struct dhcp_lease *lease, uint32_t expire) +{ + dhcp_server->lease_list = g_list_remove(dhcp_server->lease_list, lease); + + lease->expire = expire; + + dhcp_server->lease_list = g_list_insert_sorted(dhcp_server->lease_list, + lease, compare_expire); +} + +static void destroy_lease_table(GDHCPServer *dhcp_server) +{ + GList *list; + + g_hash_table_destroy(dhcp_server->nip_lease_hash); + + dhcp_server->nip_lease_hash = NULL; + + for (list = dhcp_server->lease_list; list; list = list->next) { + struct dhcp_lease *lease = list->data; + + g_free(lease); + } + + g_list_free(dhcp_server->lease_list); + + dhcp_server->lease_list = NULL; +} +static uint32_t get_interface_address(int index) +{ + struct ifreq ifr; + int sk, err; + struct sockaddr_in *server_ip; + uint32_t ret = 0; + + sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (sk < 0) { + perror("Open socket error"); + return 0; + } + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_ifindex = index; + + err = ioctl(sk, SIOCGIFNAME, &ifr); + if (err < 0) { + perror("Get interface name error"); + goto done; + } + + err = ioctl(sk, SIOCGIFADDR, &ifr); + if (err < 0) { + perror("Get ip address error"); + goto done; + } + + server_ip = (struct sockaddr_in *) &ifr.ifr_addr; + ret = server_ip->sin_addr.s_addr; + +done: + close(sk); + + return ret; +} + +GDHCPServer *g_dhcp_server_new(GDHCPType type, + int ifindex, GDHCPServerError *error) +{ + GDHCPServer *dhcp_server = NULL; + + if (ifindex < 0) { + *error = G_DHCP_SERVER_ERROR_INVALID_INDEX; + return NULL; + } + + dhcp_server = g_try_new0(GDHCPServer, 1); + if (!dhcp_server) { + *error = G_DHCP_SERVER_ERROR_NOMEM; + return NULL; + } + + dhcp_server->interface = get_interface_name(ifindex); + if (!dhcp_server->interface) { + *error = G_DHCP_SERVER_ERROR_INTERFACE_UNAVAILABLE; + goto error; + } + + if (!interface_is_up(ifindex)) { + *error = G_DHCP_SERVER_ERROR_INTERFACE_DOWN; + goto error; + } + + dhcp_server->server_nip = get_interface_address(ifindex); + if (dhcp_server->server_nip == 0) { + *error = G_DHCP_SERVER_ERROR_IP_ADDRESS_INVALID; + goto error; + } + + dhcp_server->nip_lease_hash = g_hash_table_new_full(g_direct_hash, + g_direct_equal, NULL, NULL); + dhcp_server->option_hash = g_hash_table_new_full(g_direct_hash, + g_direct_equal, NULL, NULL); + + dhcp_server->started = FALSE; + + /* All the leases have the same fixed lease time, + * do not support DHCP_LEASE_TIME option from client. + */ + dhcp_server->lease_seconds = DEFAULT_DHCP_LEASE_SEC; + + dhcp_server->type = type; + dhcp_server->ref_count = 1; + dhcp_server->ifindex = ifindex; + dhcp_server->listener_sockfd = -1; + dhcp_server->listener_watch = -1; + dhcp_server->listener_channel = NULL; + dhcp_server->save_lease_func = NULL; + dhcp_server->debug_func = NULL; + dhcp_server->debug_data = NULL; + + *error = G_DHCP_SERVER_ERROR_NONE; + + return dhcp_server; + +error: + g_free(dhcp_server->interface); + g_free(dhcp_server); + return NULL; +} + + +static uint8_t check_packet_type(struct dhcp_packet *packet) +{ + uint8_t *type; + + if (packet->hlen != ETH_ALEN) + return 0; + + if (packet->op != BOOTREQUEST) + return 0; + + type = dhcp_get_option(packet, DHCP_MESSAGE_TYPE); + + if (!type) + return 0; + + if (*type < DHCP_MINTYPE) + return 0; + + if (*type > DHCP_MAXTYPE) + return 0; + + return *type; +} + +static void init_packet(GDHCPServer *dhcp_server, struct dhcp_packet *packet, + struct dhcp_packet *client_packet, char type) +{ + /* Sets op, htype, hlen, cookie fields + * and adds DHCP_MESSAGE_TYPE option */ + dhcp_init_header(packet, type); + + packet->xid = client_packet->xid; + memcpy(packet->chaddr, client_packet->chaddr, + sizeof(client_packet->chaddr)); + packet->flags = client_packet->flags; + packet->gateway_nip = client_packet->gateway_nip; + packet->ciaddr = client_packet->ciaddr; + dhcp_add_option_uint32(packet, DHCP_SERVER_ID, + dhcp_server->server_nip); +} + +static void add_option(gpointer key, gpointer value, gpointer user_data) +{ + const char *option_value = value; + uint8_t option_code = GPOINTER_TO_INT(key); + struct in_addr nip; + struct dhcp_packet *packet = user_data; + + if (!option_value) + return; + + switch (option_code) { + case G_DHCP_SUBNET: + case G_DHCP_ROUTER: + case G_DHCP_DNS_SERVER: + if (inet_aton(option_value, &nip) == 0) + return; + + dhcp_add_option_uint32(packet, (uint8_t) option_code, + ntohl(nip.s_addr)); + break; + default: + return; + } +} + +static void add_server_options(GDHCPServer *dhcp_server, + struct dhcp_packet *packet) +{ + g_hash_table_foreach(dhcp_server->option_hash, + add_option, packet); +} + +static bool check_requested_nip(GDHCPServer *dhcp_server, + uint32_t requested_nip) +{ + struct dhcp_lease *lease; + + if (requested_nip == 0) + return false; + + if (requested_nip < dhcp_server->start_ip) + return false; + + if (requested_nip > dhcp_server->end_ip) + return false; + + lease = find_lease_by_nip(dhcp_server, requested_nip); + if (!lease) + return true; + + if (!is_expired_lease(lease)) + return false; + + return true; +} + +static void send_packet_to_client(GDHCPServer *dhcp_server, + struct dhcp_packet *dhcp_pkt) +{ + const uint8_t *chaddr; + uint32_t ciaddr; + + if ((dhcp_pkt->flags & htons(BROADCAST_FLAG)) + || dhcp_pkt->ciaddr == 0) { + debug(dhcp_server, "Broadcasting packet to client"); + ciaddr = INADDR_BROADCAST; + chaddr = MAC_BCAST_ADDR; + } else { + debug(dhcp_server, "Unicasting packet to client ciaddr"); + ciaddr = dhcp_pkt->ciaddr; + chaddr = dhcp_pkt->chaddr; + } + + dhcp_send_raw_packet(dhcp_pkt, + dhcp_server->server_nip, SERVER_PORT, + ciaddr, CLIENT_PORT, chaddr, + dhcp_server->ifindex); +} + +static void send_offer(GDHCPServer *dhcp_server, + struct dhcp_packet *client_packet, + struct dhcp_lease *lease, + uint32_t requested_nip) +{ + struct dhcp_packet packet; + struct in_addr addr; + + init_packet(dhcp_server, &packet, client_packet, DHCPOFFER); + + if (lease) + packet.yiaddr = htonl(lease->lease_nip); + else if (check_requested_nip(dhcp_server, requested_nip)) + packet.yiaddr = htonl(requested_nip); + else + packet.yiaddr = htonl(find_free_or_expired_nip( + dhcp_server, client_packet->chaddr)); + + debug(dhcp_server, "find yiaddr %u", packet.yiaddr); + + if (!packet.yiaddr) { + debug(dhcp_server, "Err: Can not found lease and send offer"); + return; + } + + lease = add_lease(dhcp_server, OFFER_TIME, + packet.chaddr, packet.yiaddr); + if (!lease) { + debug(dhcp_server, + "Err: No free IP addresses. OFFER abandoned"); + return; + } + + dhcp_add_option_uint32(&packet, DHCP_LEASE_TIME, + dhcp_server->lease_seconds); + add_server_options(dhcp_server, &packet); + + addr.s_addr = packet.yiaddr; + + debug(dhcp_server, "Sending OFFER of %s", inet_ntoa(addr)); + send_packet_to_client(dhcp_server, &packet); +} + +static void save_lease(GDHCPServer *dhcp_server) +{ + GList *list; + + if (!dhcp_server->save_lease_func) + return; + + for (list = dhcp_server->lease_list; list; list = list->next) { + struct dhcp_lease *lease = list->data; + dhcp_server->save_lease_func(lease->lease_mac, + lease->lease_nip, lease->expire); + } +} + +static void send_ACK(GDHCPServer *dhcp_server, + struct dhcp_packet *client_packet, uint32_t dest) +{ + struct dhcp_packet packet; + uint32_t lease_time_sec; + struct in_addr addr; + + init_packet(dhcp_server, &packet, client_packet, DHCPACK); + packet.yiaddr = htonl(dest); + + lease_time_sec = dhcp_server->lease_seconds; + + dhcp_add_option_uint32(&packet, DHCP_LEASE_TIME, lease_time_sec); + + add_server_options(dhcp_server, &packet); + + addr.s_addr = htonl(dest); + + debug(dhcp_server, "Sending ACK to %s", inet_ntoa(addr)); + + send_packet_to_client(dhcp_server, &packet); + + add_lease(dhcp_server, 0, packet.chaddr, packet.yiaddr); +} + +static void send_NAK(GDHCPServer *dhcp_server, + struct dhcp_packet *client_packet) +{ + struct dhcp_packet packet; + + init_packet(dhcp_server, &packet, client_packet, DHCPNAK); + + debug(dhcp_server, "Sending NAK"); + + dhcp_send_raw_packet(&packet, + dhcp_server->server_nip, SERVER_PORT, + INADDR_BROADCAST, CLIENT_PORT, MAC_BCAST_ADDR, + dhcp_server->ifindex); +} + +static void send_inform(GDHCPServer *dhcp_server, + struct dhcp_packet *client_packet) +{ + struct dhcp_packet packet; + + init_packet(dhcp_server, &packet, client_packet, DHCPACK); + add_server_options(dhcp_server, &packet); + send_packet_to_client(dhcp_server, &packet); +} + +static gboolean listener_event(GIOChannel *channel, GIOCondition condition, + gpointer user_data) +{ + GDHCPServer *dhcp_server = user_data; + struct dhcp_packet packet; + struct dhcp_lease *lease; + uint32_t requested_nip = 0; + uint8_t type, *server_id_option, *request_ip_option; + int re; + + if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { + dhcp_server->listener_watch = 0; + return FALSE; + } + + re = dhcp_recv_l3_packet(&packet, dhcp_server->listener_sockfd); + if (re < 0) + return TRUE; + + type = check_packet_type(&packet); + if (type == 0) + return TRUE; + + server_id_option = dhcp_get_option(&packet, DHCP_SERVER_ID); + if (server_id_option) { + uint32_t server_nid = get_be32(server_id_option); + + if (server_nid != dhcp_server->server_nip) + return TRUE; + } + + request_ip_option = dhcp_get_option(&packet, DHCP_REQUESTED_IP); + if (request_ip_option) + requested_nip = get_be32(request_ip_option); + + lease = find_lease_by_mac(dhcp_server, packet.chaddr); + + switch (type) { + case DHCPDISCOVER: + debug(dhcp_server, "Received DISCOVER"); + + send_offer(dhcp_server, &packet, lease, requested_nip); + break; + case DHCPREQUEST: + debug(dhcp_server, "Received REQUEST NIP %d", + requested_nip); + if (requested_nip == 0) { + requested_nip = packet.ciaddr; + if (requested_nip == 0) + break; + } + + if (lease && requested_nip == lease->lease_nip) { + debug(dhcp_server, "Sending ACK"); + send_ACK(dhcp_server, &packet, + lease->lease_nip); + break; + } + + if (server_id_option || !lease) { + debug(dhcp_server, "Sending NAK"); + send_NAK(dhcp_server, &packet); + } + + break; + case DHCPDECLINE: + debug(dhcp_server, "Received DECLINE"); + + if (!server_id_option) + break; + + if (!request_ip_option) + break; + + if (!lease) + break; + + if (requested_nip == lease->lease_nip) + remove_lease(dhcp_server, lease); + + break; + case DHCPRELEASE: + debug(dhcp_server, "Received RELEASE"); + + if (!server_id_option) + break; + + if (!lease) + break; + + if (packet.ciaddr == lease->lease_nip) + lease_set_expire(dhcp_server, lease, + time(NULL)); + break; + case DHCPINFORM: + debug(dhcp_server, "Received INFORM"); + send_inform(dhcp_server, &packet); + break; + } + + return TRUE; +} + +/* Caller need to load leases before call it */ +int g_dhcp_server_start(GDHCPServer *dhcp_server) +{ + GIOChannel *listener_channel; + int listener_sockfd; + + if (dhcp_server->started) + return 0; + + listener_sockfd = dhcp_l3_socket(SERVER_PORT, + dhcp_server->interface, AF_INET); + if (listener_sockfd < 0) + return -EIO; + + listener_channel = g_io_channel_unix_new(listener_sockfd); + if (!listener_channel) { + close(listener_sockfd); + return -EIO; + } + + dhcp_server->listener_sockfd = listener_sockfd; + dhcp_server->listener_channel = listener_channel; + + g_io_channel_set_close_on_unref(listener_channel, TRUE); + dhcp_server->listener_watch = + g_io_add_watch_full(listener_channel, G_PRIORITY_HIGH, + G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP, + listener_event, dhcp_server, + NULL); + g_io_channel_unref(dhcp_server->listener_channel); + + dhcp_server->started = TRUE; + + return 0; +} + +int g_dhcp_server_set_option(GDHCPServer *dhcp_server, + unsigned char option_code, const char *option_value) +{ + struct in_addr nip; + + if (!option_value) + return -EINVAL; + + debug(dhcp_server, "option_code %d option_value %s", + option_code, option_value); + switch (option_code) { + case G_DHCP_SUBNET: + case G_DHCP_ROUTER: + case G_DHCP_DNS_SERVER: + if (inet_aton(option_value, &nip) == 0) + return -ENXIO; + break; + default: + return -EINVAL; + } + + g_hash_table_replace(dhcp_server->option_hash, + GINT_TO_POINTER((int) option_code), + (gpointer) option_value); + return 0; +} + +void g_dhcp_server_set_save_lease(GDHCPServer *dhcp_server, + GDHCPSaveLeaseFunc func, gpointer user_data) +{ + if (!dhcp_server) + return; + + dhcp_server->save_lease_func = func; +} + +GDHCPServer *g_dhcp_server_ref(GDHCPServer *dhcp_server) +{ + if (!dhcp_server) + return NULL; + + __sync_fetch_and_add(&dhcp_server->ref_count, 1); + + return dhcp_server; +} + +void g_dhcp_server_stop(GDHCPServer *dhcp_server) +{ + /* Save leases, before stop; load them before start */ + save_lease(dhcp_server); + + if (dhcp_server->listener_watch > 0) { + g_source_remove(dhcp_server->listener_watch); + dhcp_server->listener_watch = 0; + } + + dhcp_server->listener_channel = NULL; + + dhcp_server->started = FALSE; +} + +void g_dhcp_server_unref(GDHCPServer *dhcp_server) +{ + if (!dhcp_server) + return; + + if (__sync_fetch_and_sub(&dhcp_server->ref_count, 1) != 1) + return; + + g_dhcp_server_stop(dhcp_server); + + g_hash_table_destroy(dhcp_server->option_hash); + + destroy_lease_table(dhcp_server); + + g_free(dhcp_server->interface); + + g_free(dhcp_server); +} + +int g_dhcp_server_set_ip_range(GDHCPServer *dhcp_server, + const char *start_ip, const char *end_ip) +{ + struct in_addr _host_addr; + + if (inet_aton(start_ip, &_host_addr) == 0) + return -ENXIO; + + dhcp_server->start_ip = ntohl(_host_addr.s_addr); + + if (inet_aton(end_ip, &_host_addr) == 0) + return -ENXIO; + + dhcp_server->end_ip = ntohl(_host_addr.s_addr); + + return 0; +} + +void g_dhcp_server_set_lease_time(GDHCPServer *dhcp_server, + unsigned int lease_time) +{ + if (!dhcp_server) + return; + + dhcp_server->lease_seconds = lease_time; +} + +void g_dhcp_server_set_debug(GDHCPServer *dhcp_server, + GDHCPDebugFunc func, gpointer user_data) +{ + if (!dhcp_server) + return; + + dhcp_server->debug_func = func; + dhcp_server->debug_data = user_data; +} diff --git a/src/gdhcp/unaligned.h b/src/gdhcp/unaligned.h new file mode 100644 index 0000000..ffdaae2 --- /dev/null +++ b/src/gdhcp/unaligned.h @@ -0,0 +1,163 @@ +/* + * + * Connection Manager + * + * Copyright (C) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +#define get_unaligned(ptr) \ +({ \ + struct __attribute__((packed)) { \ + typeof(*(ptr)) __v; \ + } *__p = (typeof(__p)) (ptr); \ + __p->__v; \ +}) + +#define put_unaligned(val, ptr) \ +do { \ + struct __attribute__((packed)) { \ + typeof(*(ptr)) __v; \ + } *__p = (typeof(__p)) (ptr); \ + __p->__v = (val); \ +} while(0) + +#if __BYTE_ORDER == __LITTLE_ENDIAN +static inline uint64_t get_le64(const void *ptr) +{ + return get_unaligned((const uint64_t *) ptr); +} + +static inline uint64_t get_be64(const void *ptr) +{ + return bswap_64(get_unaligned((const uint64_t *) ptr)); +} + +static inline uint32_t get_le32(const void *ptr) +{ + return get_unaligned((const uint32_t *) ptr); +} + +static inline uint32_t get_be32(const void *ptr) +{ + return bswap_32(get_unaligned((const uint32_t *) ptr)); +} + +static inline uint16_t get_le16(const void *ptr) +{ + return get_unaligned((const uint16_t *) ptr); +} + +static inline uint16_t get_be16(const void *ptr) +{ + return bswap_16(get_unaligned((const uint16_t *) ptr)); +} + +static inline void put_be16(uint16_t val, void *ptr) +{ + put_unaligned(bswap_16(val), (uint16_t *) ptr); +} + +static inline void put_be32(uint32_t val, void *ptr) +{ + put_unaligned(bswap_32(val), (uint32_t *) ptr); +} + +static inline void put_le16(uint16_t val, void *ptr) +{ + put_unaligned(val, (uint16_t *) ptr); +} + +static inline void put_le32(uint32_t val, void *ptr) +{ + put_unaligned(val, (uint32_t *) ptr); +} + +static inline void put_be64(uint64_t val, void *ptr) +{ + put_unaligned(bswap_64(val), (uint64_t *) ptr); +} + +static inline void put_le64(uint64_t val, void *ptr) +{ + put_unaligned(val, (uint64_t *) ptr); +} +#elif __BYTE_ORDER == __BIG_ENDIAN +static inline uint64_t get_le64(const void *ptr) +{ + return bswap_64(get_unaligned((const uint64_t *) ptr)); +} + +static inline uint64_t get_be64(const void *ptr) +{ + return get_unaligned((const uint64_t *) ptr); +} + +static inline uint32_t get_le32(const void *ptr) +{ + return bswap_32(get_unaligned((const uint32_t *) ptr)); +} + +static inline uint32_t get_be32(const void *ptr) +{ + return get_unaligned((const uint32_t *) ptr); +} + +static inline uint16_t get_le16(const void *ptr) +{ + return bswap_16(get_unaligned((const uint16_t *) ptr)); +} + +static inline uint16_t get_be16(const void *ptr) +{ + return get_unaligned((const uint16_t *) ptr); +} + +static inline void put_be16(uint16_t val, void *ptr) +{ + put_unaligned(val, (uint16_t *) ptr); +} + +static inline void put_be32(uint32_t val, void *ptr) +{ + put_unaligned(val, (uint32_t *) ptr); +} + +static inline void put_le16(uint16_t val, void *ptr) +{ + put_unaligned(bswap_16(val), (uint16_t *) ptr); +} + +static inline void put_le32(uint32_t val, void *ptr) +{ + put_unaligned(bswap_32(val), (uint32_t *) ptr); +} + +static inline void put_be64(uint64_t val, void *ptr) +{ + put_unaligned(val, (uint64_t *) ptr); +} + +static inline void put_le64(uint64_t val, void *ptr) +{ + put_unaligned(bswap_64(val), (uint64_t *) ptr); +} +#else +#error "Unknown byte order" +#endif diff --git a/src/miracle-dhcp.c b/src/miracle-dhcp.c new file mode 100644 index 0000000..a0a8180 --- /dev/null +++ b/src/miracle-dhcp.c @@ -0,0 +1,936 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * 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. + */ + +/* + * Small DHCP Client/Server + * Wifi-P2P requires us to use DHCP to set up a private P2P network. As all + * DHCP daemons available have horrible interfaces for ad-hoc setups, we have + * this small replacement for all DHCP operations. Once sd-dhcp is public, we + * will switch to it instead of this helper binary. However, that also requires + * DHCP-server support in sd-dhcp. + * + * This program implements a DHCP server and daemon. See --help for usage + * information. We build on gdhcp from connman as the underlying DHCP protocol + * implementation. To configure network devices, we actually invoke the "ip" + * binary. + * + * Note that this is a gross hack! We don't intend to provide a fully functional + * DHCP server or client here. This is only a replacement for the current lack + * of Wifi-P2P support in common network managers. Once they gain proper + * support, we will drop this helper! + * + * The "ip" invokation is quite fragile and ugly. However, performing these + * steps directly involves netlink operations and more. As no-one came up with + * patches, yet, we keep the hack. To anyone trying to fix it: Please, spend + * this time hacking on NetworkManager, connman and friends instead! If they + * gain Wifi-P2P support, this whole thing will get trashed. + */ + +#define LOG_SUBSYSTEM "dhcp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "gdhcp/gdhcp.h" +#include "shl_log.h" + +static const char *arg_netdev; +static const char *arg_ip_binary = "/bin/ip"; +static bool arg_server; +static char arg_local[INET_ADDRSTRLEN]; +static char arg_gateway[INET_ADDRSTRLEN]; +static char arg_dns[INET_ADDRSTRLEN]; +static char arg_subnet[INET_ADDRSTRLEN]; +static char arg_from[INET_ADDRSTRLEN]; +static char arg_to[INET_ADDRSTRLEN]; +static int arg_comm = -1; + +struct manager { + int ifindex; + GMainLoop *loop; + + int sfd; + GIOChannel *sfd_chan; + guint sfd_id; + + GDHCPClient *client; + char *client_addr; + + GDHCPServer *server; + char *server_addr; +}; + +/* + * We send prefixed messages via @comm. You should use a packet-based + * socket-type so boundaries are preserved. Following packets are sent: + * sent on local lease: + * L: # local iface addr + * S: # subnet mask + * D: # primary DNS server + * G: # primary gateway + * sent on remote lease: + * R: # addr given to remote device + */ +static void write_comm(const void *msg, size_t size) +{ + static bool warned; + int r; + + if (arg_comm < 0) + return; + + r = send(arg_comm, msg, size, MSG_NOSIGNAL); + if (r < 0 && !warned) { + warned = true; + arg_comm = -1; + log_error("cannot write to comm-socket, disabling it: %m"); + } +} + +static void writef_comm(const void *format, ...) +{ + va_list args; + char *msg; + int r; + + va_start(args, format); + r = vasprintf(&msg, format, args); + va_end(args); + if (r < 0) + return log_vENOMEM(); + + write_comm(msg, r); + free(msg); +} + +static int flush_if_addr(void) +{ + char *argv[64]; + int i, r; + pid_t pid, rp; + sigset_t mask; + + pid = fork(); + if (pid < 0) { + return log_ERRNO(); + } else if (!pid) { + /* child */ + + sigemptyset(&mask); + sigprocmask(SIG_SETMASK, &mask, NULL); + + /* redirect stdout to stderr */ + dup2(2, 1); + + i = 0; + argv[i++] = (char*)arg_ip_binary; + argv[i++] = "addr"; + argv[i++] = "flush"; + argv[i++] = "dev"; + argv[i++] = (char*)arg_netdev; + argv[i] = NULL; + + execve(argv[0], argv, environ); + _exit(1); + } + + log_info("flushing local if-addr"); + rp = waitpid(pid, &r, 0); + if (rp != pid) { + log_error("cannot flush local if-addr via '%s'", + arg_ip_binary); + return -EFAULT; + } else if (!WIFEXITED(r)) { + log_error("flushing local if-addr via '%s' failed", + arg_ip_binary); + return -EFAULT; + } else if (WEXITSTATUS(r)) { + log_error("flushing local if-addr via '%s' failed with: %d", + arg_ip_binary, WEXITSTATUS(r)); + return -EFAULT; + } + + log_debug("successfully flushed local if-addr via %s", + arg_ip_binary); + + return 0; +} + +static int add_if_addr(const char *addr) +{ + char *argv[64]; + int i, r; + pid_t pid, rp; + sigset_t mask; + + pid = fork(); + if (pid < 0) { + return log_ERRNO(); + } else if (!pid) { + /* child */ + + sigemptyset(&mask); + sigprocmask(SIG_SETMASK, &mask, NULL); + + /* redirect stdout to stderr */ + dup2(2, 1); + + i = 0; + argv[i++] = (char*)arg_ip_binary; + argv[i++] = "addr"; + argv[i++] = "add"; + argv[i++] = (char*)addr; + argv[i++] = "dev"; + argv[i++] = (char*)arg_netdev; + argv[i] = NULL; + + execve(argv[0], argv, environ); + _exit(1); + } + + log_info("adding local if-addr %s", addr); + rp = waitpid(pid, &r, 0); + if (rp != pid) { + log_error("cannot set local if-addr %s via '%s'", + addr, arg_ip_binary); + return -EFAULT; + } else if (!WIFEXITED(r)) { + log_error("setting local if-addr %s via '%s' failed", + addr, arg_ip_binary); + return -EFAULT; + } else if (WEXITSTATUS(r)) { + log_error("setting local if-addr %s via '%s' failed with: %d", + addr, arg_ip_binary, WEXITSTATUS(r)); + return -EFAULT; + } + + log_debug("successfully set local if-addr %s via %s", + addr, arg_ip_binary); + + return 0; +} + +int if_name_to_index(const char *name) +{ + struct ifreq ifr; + int fd, r; + + if (strlen(name) > sizeof(ifr.ifr_name)) + return -EINVAL; + + fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (fd < 0) + return -errno; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); + + r = ioctl(fd, SIOCGIFINDEX, &ifr); + if (r < 0) + r = -errno; + else + r = ifr.ifr_ifindex; + + close(fd); + return r; +} + +static void sig_dummy(int sig) +{ +} + +static void client_lease_fn(GDHCPClient *client, gpointer data) +{ + struct manager *m = data; + char *addr = NULL, *a, *subnet = NULL, *gateway = NULL, *dns = NULL; + GList *l; + int r; + + log_info("lease available"); + + addr = g_dhcp_client_get_address(client); + log_info("lease: address: %s", addr); + + l = g_dhcp_client_get_option(client, G_DHCP_SUBNET); + for ( ; l; l = l->next) { + subnet = subnet ? : (char*)l->data; + log_info("lease: subnet: %s", (char*)l->data); + } + + l = g_dhcp_client_get_option(client, G_DHCP_DNS_SERVER); + for ( ; l; l = l->next) { + dns = dns ? : (char*)l->data; + log_info("lease: dns-server: %s", (char*)l->data); + } + + l = g_dhcp_client_get_option(client, G_DHCP_ROUTER); + for ( ; l; l = l->next) { + gateway = gateway ? : (char*)l->data; + log_info("lease: router: %s", (char*)l->data); + } + + if (!addr) { + log_error("lease without IP address"); + goto error; + } + if (!subnet) { + log_warning("lease without subnet mask, using 24"); + subnet = "24"; + } + + r = asprintf(&a, "%s/%s", addr, subnet); + if (r < 0) { + log_vENOMEM(); + goto error; + } + + if (m->client_addr && !strcmp(m->client_addr, a)) { + log_info("given address already set"); + free(a); + } else { + free(m->client_addr); + m->client_addr = a; + + r = flush_if_addr(); + if (r < 0) { + log_error("cannot flush addr on local interface %s", + arg_netdev); + goto error; + } + + r = add_if_addr(m->client_addr); + if (r < 0) { + log_error("cannot set parameters on local interface %s", + arg_netdev); + goto error; + } + + writef_comm("L:%s", addr); + writef_comm("S:%s", subnet); + if (dns) + writef_comm("D:%s", dns); + if (gateway) + writef_comm("G:%s", gateway); + } + + g_free(addr); + return; + +error: + g_free(addr); + g_main_loop_quit(m->loop); +} + +static void client_no_lease_fn(GDHCPClient *client, gpointer data) +{ + struct manager *m = data; + + log_error("no lease available"); + g_main_loop_quit(m->loop); +} + +static void server_log_fn(const char *str, void *data) +{ + log_format(NULL, 0, NULL, "gdhcp", LOG_DEBUG, "%s", str); +} + +static gboolean manager_signal_fn(GIOChannel *chan, GIOCondition mask, + gpointer data) +{ + struct manager *m = data; + ssize_t l; + struct signalfd_siginfo info; + + if (mask & (G_IO_HUP | G_IO_ERR)) { + log_vEPIPE(); + g_main_loop_quit(m->loop); + return FALSE; + } + + l = read(m->sfd, &info, sizeof(info)); + if (l < 0) { + log_vERRNO(); + g_main_loop_quit(m->loop); + return FALSE; + } else if (l != sizeof(info)) { + log_vEFAULT(); + return TRUE; + } + + log_notice("received signal %d: %s", + info.ssi_signo, strsignal(info.ssi_signo)); + + g_main_loop_quit(m->loop); + return FALSE; +} + +static void manager_free(struct manager *m) +{ + if (!m) + return; + + if (!arg_server) { + if (m->client) { + g_dhcp_client_stop(m->client); + + if (m->client_addr) { + flush_if_addr(); + free(m->client_addr); + } + + g_dhcp_client_unref(m->client); + } + } else { + if (m->server) { + g_dhcp_server_stop(m->server); + + g_dhcp_server_unref(m->server); + } + + if (m->server_addr) { + flush_if_addr(); + free(m->server_addr); + } + } + + if (m->sfd >= 0) { + g_source_remove(m->sfd_id); + g_io_channel_unref(m->sfd_chan); + close(m->sfd); + } + + if (m->loop) + g_main_loop_unref(m->loop); + + free(m); +} + +static int manager_new(struct manager **out) +{ + static const int sigs[] = { + SIGINT, + SIGTERM, + SIGQUIT, + SIGHUP, + SIGPIPE, + 0 + }; + int r, i; + sigset_t mask; + struct sigaction sig; + GDHCPClientError cerr; + GDHCPServerError serr; + struct manager *m; + + m = calloc(1, sizeof(*m)); + if (!m) + return log_ENOMEM(); + + m->sfd = -1; + + if (geteuid()) + log_warning("not running as uid=0, dhcp might not work"); + + m->ifindex = if_name_to_index(arg_netdev); + if (m->ifindex < 0) { + r = -EINVAL; + log_error("cannot find interface %s (%d)", + arg_netdev, m->ifindex); + goto error; + } + + m->loop = g_main_loop_new(NULL, FALSE); + + sigemptyset(&mask); + memset(&sig, 0, sizeof(sig)); + sig.sa_handler = sig_dummy; + sig.sa_flags = SA_RESTART; + + for (i = 0; sigs[i]; ++i) { + sigaddset(&mask, sigs[i]); + r = sigaction(sigs[i], &sig, NULL); + if (r < 0) { + r = log_ERRNO(); + goto error; + } + } + + r = sigprocmask(SIG_BLOCK, &mask, NULL); + if (r < 0) { + r = log_ERRNO(); + goto error; + } + + m->sfd = signalfd(-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK); + if (m->sfd < 0) { + r = log_ERRNO(); + goto error; + } + + m->sfd_chan = g_io_channel_unix_new(m->sfd); + m->sfd_id = g_io_add_watch(m->sfd_chan, + G_IO_HUP | G_IO_ERR | G_IO_IN, + manager_signal_fn, + m); + + if (!arg_server) { + m->client = g_dhcp_client_new(G_DHCP_IPV4, m->ifindex, + &cerr); + if (!m->client) { + r = -EINVAL; + + switch (cerr) { + case G_DHCP_CLIENT_ERROR_INTERFACE_UNAVAILABLE: + log_error("cannot create GDHCP client: interface %s unavailable", + arg_netdev); + break; + case G_DHCP_CLIENT_ERROR_INTERFACE_IN_USE: + log_error("cannot create GDHCP client: interface %s in use", + arg_netdev); + break; + case G_DHCP_CLIENT_ERROR_INTERFACE_DOWN: + log_error("cannot create GDHCP client: interface %s down", + arg_netdev); + break; + case G_DHCP_CLIENT_ERROR_NOMEM: + r = log_ENOMEM(); + break; + case G_DHCP_CLIENT_ERROR_INVALID_INDEX: + log_error("cannot create GDHCP client: invalid interface %s", + arg_netdev); + break; + case G_DHCP_CLIENT_ERROR_INVALID_OPTION: + log_error("cannot create GDHCP client: invalid options"); + break; + default: + log_error("cannot create GDHCP client (%d)", + cerr); + break; + } + + goto error; + } + + g_dhcp_client_set_send(m->client, G_DHCP_HOST_NAME, + ""); + + g_dhcp_client_set_request(m->client, G_DHCP_SUBNET); + g_dhcp_client_set_request(m->client, G_DHCP_DNS_SERVER); + g_dhcp_client_set_request(m->client, G_DHCP_ROUTER); + + g_dhcp_client_register_event(m->client, + G_DHCP_CLIENT_EVENT_LEASE_AVAILABLE, + client_lease_fn, m); + g_dhcp_client_register_event(m->client, + G_DHCP_CLIENT_EVENT_NO_LEASE, + client_no_lease_fn, m); + } else { + r = asprintf(&m->server_addr, "%s/%s", arg_local, arg_subnet); + if (r < 0) { + r = log_ENOMEM(); + goto error; + } + + r = flush_if_addr(); + if (r < 0) { + log_error("cannot flush addr on local interface %s", + arg_netdev); + goto error; + } + + r = add_if_addr(m->server_addr); + if (r < 0) { + log_error("cannot set parameters on local interface %s", + arg_netdev); + goto error; + } + + m->server = g_dhcp_server_new(G_DHCP_IPV4, m->ifindex, + &serr); + if (!m->server) { + r = -EINVAL; + + switch(serr) { + case G_DHCP_SERVER_ERROR_INTERFACE_UNAVAILABLE: + log_error("cannot create GDHCP server: interface %s unavailable", + arg_netdev); + break; + case G_DHCP_SERVER_ERROR_INTERFACE_IN_USE: + log_error("cannot create GDHCP server: interface %s in use", + arg_netdev); + break; + case G_DHCP_SERVER_ERROR_INTERFACE_DOWN: + log_error("cannot create GDHCP server: interface %s down", + arg_netdev); + break; + case G_DHCP_SERVER_ERROR_NOMEM: + r = log_ENOMEM(); + break; + case G_DHCP_SERVER_ERROR_INVALID_INDEX: + log_error("cannot create GDHCP server: invalid interface %s", + arg_netdev); + break; + case G_DHCP_SERVER_ERROR_INVALID_OPTION: + log_error("cannot create GDHCP server: invalid options"); + break; + case G_DHCP_SERVER_ERROR_IP_ADDRESS_INVALID: + log_error("cannot create GDHCP server: invalid ip address"); + break; + default: + log_error("cannot create GDHCP server (%d)", + serr); + break; + } + + goto error; + } + + g_dhcp_server_set_debug(m->server, server_log_fn, NULL); + g_dhcp_server_set_lease_time(m->server, 60 * 60); + + r = g_dhcp_server_set_option(m->server, G_DHCP_SUBNET, + arg_subnet); + if (r != 0) { + log_vERR(r); + goto error; + } + + r = g_dhcp_server_set_option(m->server, G_DHCP_ROUTER, + arg_gateway); + if (r != 0) { + log_vERR(r); + goto error; + } + + r = g_dhcp_server_set_option(m->server, G_DHCP_DNS_SERVER, + arg_dns); + if (r != 0) { + log_vERR(r); + goto error; + } + + r = g_dhcp_server_set_ip_range(m->server, arg_from, arg_to); + if (r != 0) { + log_vERR(r); + goto error; + } + } + + *out = m; + return 0; + +error: + manager_free(m); + return r; +} + +static int manager_run(struct manager *m) +{ + int r; + + if (!arg_server) { + log_info("running dhcp client on %s via '%s'", + arg_netdev, arg_ip_binary); + + r = g_dhcp_client_start(m->client, NULL); + if (r != 0) { + log_error("cannot start DHCP client: %d", r); + return -EFAULT; + } + } else { + log_info("running dhcp server on %s via '%s'", + arg_netdev, arg_ip_binary); + + r = g_dhcp_server_start(m->server); + if (r != 0) { + log_error("cannot start DHCP server: %d", r); + return -EFAULT; + } + } + + g_main_loop_run(m->loop); + + return 0; +} + +static int make_address(char *buf, const char *prefix, const char *suffix, + const char *name) +{ + int r; + struct in_addr addr; + + if (!prefix) + prefix = "192.168.77"; + + r = snprintf(buf, INET_ADDRSTRLEN, "%s.%s", prefix, suffix); + if (r >= INET_ADDRSTRLEN) + goto error; + + r = inet_pton(AF_INET, buf, &addr); + if (r != 1) + goto error; + + inet_ntop(AF_INET, &addr, buf, INET_ADDRSTRLEN); + buf[INET_ADDRSTRLEN] = 0; + return 0; + +error: + log_error("Invalid address --%s=%s.%s (prefix: %s suffix: %s)", + name, prefix, suffix, prefix, suffix); + return -EINVAL; +} + +static int make_subnet(char *buf, const char *subnet) +{ + int r; + struct in_addr addr; + + r = inet_pton(AF_INET, subnet, &addr); + if (r != 1) + goto error; + + inet_ntop(AF_INET, &addr, buf, INET_ADDRSTRLEN); + buf[INET_ADDRSTRLEN] = 0; + return 0; + +error: + log_error("Invalid address --subnet=%s", subnet); + return -EINVAL; +} + +static int help(void) +{ + printf("%s [OPTIONS...] ...\n\n" + "Ad-hoc IPv4 DHCP Server/Client.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --log-level Maximum level for log messages\n" + " --log-time Prefix log-messages with timestamp\n" + "\n" + " --netdev Network device to run on\n" + " --ip-binary Path to 'ip' binary [default: /bin/ip]\n" + " --comm-fd Comm-socket FD passed through execve()\n" + "\n" + "Server Options:\n" + " --server Run as DHCP server instead of client\n" + " --prefix Network prefix [default: 192.168.77]\n" + " --local Local address suffix [default: 1]\n" + " --gateway Gateway suffix [default: 1]\n" + " --dns DNS suffix [default: 1]\n" + " --subnet Subnet mask [default: 255.255.255.0]\n" + " --from Start address [default: 100]\n" + " --to End address [default: 199]\n" + , program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) +{ + enum { + ARG_VERSION = 0x100, + ARG_LOG_LEVEL, + ARG_LOG_TIME, + + ARG_NETDEV, + ARG_IP_BINARY, + ARG_COMM_FD, + + ARG_SERVER, + ARG_PREFIX, + ARG_LOCAL, + ARG_GATEWAY, + ARG_DNS, + ARG_SUBNET, + ARG_FROM, + ARG_TO, + }; + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + { "log-time", no_argument, NULL, ARG_LOG_TIME }, + + { "netdev", required_argument, NULL, ARG_NETDEV }, + { "ip-binary", required_argument, NULL, ARG_IP_BINARY }, + { "comm-fd", required_argument, NULL, ARG_COMM_FD }, + + { "server", no_argument, NULL, ARG_SERVER }, + { "prefix", required_argument, NULL, ARG_PREFIX }, + { "local", required_argument, NULL, ARG_LOCAL }, + { "gateway", required_argument, NULL, ARG_GATEWAY }, + { "dns", required_argument, NULL, ARG_DNS }, + { "subnet", required_argument, NULL, ARG_SUBNET }, + { "from", required_argument, NULL, ARG_FROM }, + { "to", required_argument, NULL, ARG_TO }, + {} + }; + int c, r; + const char *prefix = NULL, *local = NULL, *gateway = NULL; + const char *dns = NULL, *subnet = NULL, *from = NULL, *to = NULL; + + while ((c = getopt_long(argc, argv, "hs:", options, NULL)) >= 0) { + switch (c) { + case 'h': + return help(); + case ARG_VERSION: + puts(PACKAGE_STRING); + return 0; + case ARG_LOG_LEVEL: + log_max_sev = atoi(optarg); + break; + case ARG_LOG_TIME: + log_init_time(); + break; + case ARG_NETDEV: + arg_netdev = optarg; + break; + case ARG_IP_BINARY: + arg_ip_binary = optarg; + break; + case ARG_COMM_FD: + arg_comm = atoi(optarg); + break; + + case ARG_SERVER: + arg_server = true; + break; + case ARG_PREFIX: + prefix = optarg; + break; + case ARG_LOCAL: + local = optarg; + break; + case ARG_GATEWAY: + gateway = optarg; + break; + case ARG_DNS: + dns = optarg; + break; + case ARG_SUBNET: + subnet = optarg; + break; + case ARG_FROM: + from = optarg; + break; + case ARG_TO: + to = optarg; + break; + case '?': + return -EINVAL; + } + } + + if (optind < argc) { + log_error("unparsed remaining arguments starting with: %s", + argv[optind]); + return -EINVAL; + } + + if (!arg_netdev) { + log_error("no network-device given (see --help for --netdev)"); + return -EINVAL; + } + + if (access(arg_ip_binary, X_OK) < 0) { + log_error("execution of ip-binary (%s) not allowed: %m", + arg_ip_binary); + return -EINVAL; + } + + if (!arg_server) { + if (prefix || local || gateway || + dns || subnet || from || to) { + log_error("server option given, but running as client"); + return -EINVAL; + } + } else { + r = make_address(arg_local, prefix, local ? : "1", "local"); + if (r < 0) + return -EINVAL; + r = make_address(arg_gateway, prefix, gateway ? : "1", + "gateway"); + if (r < 0) + return -EINVAL; + r = make_address(arg_dns, prefix, dns ? : "1", "dns"); + if (r < 0) + return -EINVAL; + r = make_subnet(arg_subnet, subnet ? : "255.255.255.0"); + if (r < 0) + return -EINVAL; + r = make_address(arg_from, prefix, from ? : "100", "from"); + if (r < 0) + return -EINVAL; + r = make_address(arg_to, prefix, to ? : "199", "to"); + if (r < 0) + return -EINVAL; + } + + log_format(LOG_DEFAULT_BASE, NULL, LOG_INFO, + "miracle-dhcp - revision %s %s %s", + "some-rev-TODO-xyz", __DATE__, __TIME__); + + return 1; +} + +int main(int argc, char **argv) +{ + struct manager *m = NULL; + int r; + + r = parse_argv(argc, argv); + if (r < 0) + return EXIT_FAILURE; + if (!r) + return EXIT_SUCCESS; + + r = manager_new(&m); + if (r < 0) + goto finish; + + r = manager_run(m); + +finish: + manager_free(m); + + log_debug("exiting.."); + return abs(r); +} diff --git a/src/miracle.h b/src/miracle.h new file mode 100644 index 0000000..01d438b --- /dev/null +++ b/src/miracle.h @@ -0,0 +1,68 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * 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. + */ + +#include +#include +#include +#include +#include "shl_log.h" +#include "shl_macro.h" + +#ifndef MIRACLE_H +#define MIRACLE_H + +static inline void cleanup_free(void *p) +{ + free(*(void**)p); +} + +static inline void cleanup_sd_bus_message(sd_bus_message **ptr) +{ + sd_bus_message_unref(*ptr); +} + +#define _cleanup_free_ _shl_cleanup_(cleanup_free) +#define _cleanup_sd_bus_error_ _shl_cleanup_(sd_bus_error_free) +#define _cleanup_sd_bus_message_ _shl_cleanup_(cleanup_sd_bus_message) + +static inline const char *bus_error_message(const sd_bus_error *e, int error) +{ + if (e) { + if (sd_bus_error_has_name(e, SD_BUS_ERROR_ACCESS_DENIED)) + return "Access denied"; + if (e->message) + return e->message; + } + + return strerror(error < 0 ? -error : error); +} + +static inline int log_bus_parser(int r) +{ + log_error("cannot parse dbus message: %s", strerror(r < 0 ? -r : r)); + return r; +} + +#endif /* MIRACLE_H */ diff --git a/src/miraclectl.c b/src/miraclectl.c new file mode 100644 index 0000000..c27b98a --- /dev/null +++ b/src/miraclectl.c @@ -0,0 +1,484 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "miracle.h" +#include "shl_log.h" +#include "shl_macro.h" +#include "shl_util.h" + +static int verb_list_links(sd_bus *bus, sd_bus_message *m) +{ + char *link; + unsigned int link_cnt = 0; + const char *obj; + int r; + + r = sd_bus_message_enter_container(m, 'a', "{oa{sa{sv}}}"); + if (r < 0) + return log_bus_parser(r); + + while ((r = sd_bus_message_enter_container(m, + 'e', + "oa{sa{sv}}")) > 0) { + r = sd_bus_message_read(m, "o", &obj); + if (r < 0) + return log_bus_parser(r); + + obj = shl_startswith(obj, "/org/freedesktop/miracle/link/"); + if (obj) { + link = sd_bus_label_unescape(obj); + if (!link) + return log_ENOMEM(); + + printf("%24s %-9s\n", link, ""); + + free(link); + ++link_cnt; + } + + r = sd_bus_message_skip(m, "a{sa{sv}}"); + if (r < 0) + return log_bus_parser(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return log_bus_parser(r); + } + if (r < 0) + return log_bus_parser(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return log_bus_parser(r); + + return link_cnt; +} + +static int verb_list_peer(sd_bus *bus, sd_bus_message *m, const char *peer) +{ + _cleanup_free_ char *link = NULL; + const char *obj; + int r; + + r = sd_bus_message_enter_container(m, 'a', "{sa{sv}}"); + if (r < 0) + return log_bus_parser(r); + + while ((r = sd_bus_message_enter_container(m, + 'e', + "sa{sv}")) > 0) { + r = sd_bus_message_read(m, "s", &obj); + if (r < 0) + return log_bus_parser(r); + + if (strcmp(obj, "org.freedesktop.miracle.Peer")) { + r = sd_bus_message_skip(m, "a{sv}"); + if (r < 0) + return log_bus_parser(r); + r = sd_bus_message_exit_container(m); + if (r < 0) + return log_bus_parser(r); + continue; + } + + r = sd_bus_message_enter_container(m, 'a', "{sv}"); + if (r < 0) + return log_bus_parser(r); + + while ((r = sd_bus_message_enter_container(m, + 'e', + "sv")) > 0) { + r = sd_bus_message_read(m, "s", &obj); + if (r < 0) + return log_bus_parser(r); + + if (!strcmp(obj, "Link")) { + r = sd_bus_message_enter_container(m, + 'v', + "o"); + if (r < 0) + return log_bus_parser(r); + + r = sd_bus_message_read(m, "o", &obj); + if (r < 0) + return log_bus_parser(r); + + obj = shl_startswith(obj, + "/org/freedesktop/miracle/link/"); + if (obj) { + free(link); + link = sd_bus_label_unescape(obj); + if (!link) + return log_ENOMEM(); + } + + r = sd_bus_message_exit_container(m); + if (r < 0) + return log_bus_parser(r); + } else { + sd_bus_message_skip(m, "v"); + } + + r = sd_bus_message_exit_container(m); + if (r < 0) + return log_bus_parser(r); + } + if (r < 0) + return log_bus_parser(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return log_bus_parser(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return log_bus_parser(r); + } + if (r < 0) + return log_bus_parser(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return log_bus_parser(r); + + printf("%24s %-9s\n", link ? : "", peer); + + return 0; +} + +static int verb_list_peers(sd_bus *bus, sd_bus_message *m) +{ + _cleanup_free_ char *peer = NULL; + unsigned int peer_cnt = 0; + const char *obj; + int r; + + r = sd_bus_message_enter_container(m, 'a', "{oa{sa{sv}}}"); + if (r < 0) + return log_bus_parser(r); + + while ((r = sd_bus_message_enter_container(m, + 'e', + "oa{sa{sv}}")) > 0) { + r = sd_bus_message_read(m, "o", &obj); + if (r < 0) + return log_bus_parser(r); + + obj = shl_startswith(obj, "/org/freedesktop/miracle/peer/"); + if (!obj) { + r = sd_bus_message_skip(m, "a{sa{sv}}"); + if (r < 0) + return log_bus_parser(r); + r = sd_bus_message_exit_container(m); + if (r < 0) + return log_bus_parser(r); + continue; + } + + free(peer); + peer = sd_bus_label_unescape(obj); + if (!peer) + return log_ENOMEM(); + + ++peer_cnt; + r = verb_list_peer(bus, m, peer); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return log_bus_parser(r); + } + if (r < 0) + return log_bus_parser(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return log_bus_parser(r); + + return peer_cnt; +} + +static int verb_list(sd_bus *bus, char **args, unsigned int n) +{ + _cleanup_sd_bus_message_ sd_bus_message *m = NULL; + _cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL; + unsigned int link_cnt, peer_cnt; + int r; + + r = sd_bus_call_method(bus, + "org.freedesktop.miracle", + "/org/freedesktop/miracle", + "org.freedesktop.DBus.ObjectManager", + "GetManagedObjects", + &err, + &m, + ""); + if (r < 0) { + log_error("cannot retrieve objects: %s", + bus_error_message(&err, r)); + return r; + } + + printf("%24s %-9s\n", "LINK", "PEER"); + + /* print links */ + + r = verb_list_links(bus, m); + if (r < 0) + return r; + link_cnt = r; + + sd_bus_message_rewind(m, true); + if (link_cnt > 0) + printf("\n"); + + /* print peers */ + + r = verb_list_peers(bus, m); + if (r < 0) + return r; + peer_cnt = r; + + if (peer_cnt > 0 || !link_cnt) + printf("\n"); + + /* print stats */ + + printf(" %u peers and %u links listed.\n", peer_cnt, link_cnt); + + return 0; +} + +static int verb_add_link(sd_bus *bus, char **args, unsigned int n) +{ + _cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL; + _cleanup_sd_bus_message_ sd_bus_message *m = NULL; + _cleanup_free_ char *link = NULL; + const char *name; + int r; + + r = sd_bus_call_method(bus, + "org.freedesktop.miracle", + "/org/freedesktop/miracle", + "org.freedesktop.miracle.Manager", + "AddLink", + &err, + &m, + "ss", args[1], args[2]); + if (r < 0) { + log_error("cannot add link %s:%s: %s", + args[1], args[2], bus_error_message(&err, r)); + return r; + } + + r = sd_bus_message_read(m, "s", &name); + if (r < 0) + return log_bus_parser(r); + + link = sd_bus_label_unescape(name); + if (!link) + return log_ENOMEM(); + + printf("Link added as %s\n", link); + return 0; +} + +static int verb_remove_link(sd_bus *bus, char **args, unsigned int n) +{ + _cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL; + int r; + + r = sd_bus_call_method(bus, + "org.freedesktop.miracle", + "/org/freedesktop/miracle", + "org.freedesktop.miracle.Manager", + "RemoveLink", + &err, + NULL, + "s", args[1]); + if (r < 0) { + log_error("cannot remove link %s: %s", + args[1], bus_error_message(&err, r)); + return r; + } + + printf("Link %s removed\n", args[1]); + return 0; +} + +static int help(void) +{ + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Send control command to or query the MiracleCast manager.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --log-level Maximum level for log messages\n" + " --log-time Prefix log-messages with timestamp\n" + "\n" + "Commands:\n" + " list List managed links and their peers\n" + " add-link LINK... Start managing the given link\n" + " remove-link LINK... Stop managing the given link\n" + , program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) +{ + enum { + ARG_VERSION = 0x100, + ARG_LOG_LEVEL, + ARG_LOG_TIME, + }; + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + { "log-time", no_argument, NULL, ARG_LOG_TIME }, + {} + }; + int c; + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + switch (c) { + case 'h': + return help(); + case ARG_VERSION: + puts(PACKAGE_STRING); + return 0; + case ARG_LOG_LEVEL: + log_max_sev = atoi(optarg); + break; + case ARG_LOG_TIME: + log_init_time(); + break; + case '?': + return -EINVAL; + } + } + + return 1; +} + +static int miraclectl_main(sd_bus *bus, int argc, char *argv[]) +{ + static const struct { + const char *verb; + const enum { + MORE, + LESS, + EQUAL + } argc_cmp; + const int argc; + int (*dispatch) (sd_bus *bus, char **args, unsigned int n); + } verbs[] = { + { "list", LESS, 1, verb_list }, + { "add-link", EQUAL, 3, verb_add_link }, + { "remove-link", EQUAL, 2, verb_remove_link }, + }; + int left; + unsigned int i; + + left = argc - optind; + if (left <= 0) { + /* no argument means "list" */ + i = 0; + } else { + if (!strcmp(argv[optind], "help")) { + help(); + return 0; + } + + for (i = 0; i < SHL_ARRAY_LENGTH(verbs); i++) + if (!strcmp(argv[optind], verbs[i].verb)) + break; + + if (i >= SHL_ARRAY_LENGTH(verbs)) { + log_error("unknown operation %s", argv[optind]); + return -EINVAL; + } + } + + switch (verbs[i].argc_cmp) { + case EQUAL: + if (left != verbs[i].argc) { + log_error("invalid number of arguments"); + return -EINVAL; + } + + break; + case MORE: + if (left < verbs[i].argc) { + log_error("too few arguments"); + return -EINVAL; + } + + break; + case LESS: + if (left > verbs[i].argc) { + log_error("too many arguments"); + return -EINVAL; + } + + break; + } + + return verbs[i].dispatch(bus, argv + optind, left); +} + +int main(int argc, char **argv) +{ + sd_bus *bus; + int r; + + setlocale(LC_ALL, ""); + + r = parse_argv(argc, argv); + if (r < 0) + return EXIT_FAILURE; + if (!r) + return EXIT_SUCCESS; + + r = sd_bus_default_system(&bus); + if (r < 0) { + log_error("cannot connect to system bus: %s", strerror(-r)); + return EXIT_FAILURE; + } + + r = miraclectl_main(bus, argc, argv); + sd_bus_unref(bus); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/miracled-dbus.c b/src/miracled-dbus.c new file mode 100644 index 0000000..6d23778 --- /dev/null +++ b/src/miracled-dbus.c @@ -0,0 +1,595 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * 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. + */ + +#define LOG_SUBSYSTEM "dbus" + +#include +#include +#include +#include +#include +#include +#include "miracle.h" +#include "miracled.h" +#include "shl_log.h" +#include "shl_util.h" + +/* + * Peer DBus + */ + +static int peer_dbus_allow(sd_bus *bus, sd_bus_message *msg, + void *data, sd_bus_error *err) +{ + return -EINVAL; +} + +static int peer_dbus_reject(sd_bus *bus, sd_bus_message *msg, + void *data, sd_bus_error *err) +{ + return -EINVAL; +} + +static int peer_dbus_connect(sd_bus *bus, sd_bus_message *msg, + void *data, sd_bus_error *err) +{ + return -EINVAL; +} + +static int peer_dbus_disconnect(sd_bus *bus, sd_bus_message *msg, + void *data, sd_bus_error *err) +{ + return -EINVAL; +} + +static int peer_dbus_get_link(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + _cleanup_free_ char *link = NULL; + struct peer *p = data; + int r; + + link = shl_strcat("/org/freedesktop/miracle/link/", p->l->name); + if (!link) + return log_ENOMEM(); + + r = sd_bus_message_append_basic(reply, 'o', link); + if (r < 0) + return r; + + return 1; +} + +/* +static int peer_dbus_get_name(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + return -EINVAL; +} + +static int peer_dbus_get_connected(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + return -EINVAL; +} + +static int peer_dbus_get_interface(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + return -EINVAL; +} + +static int peer_dbus_get_local_address(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + return -EINVAL; +} + +static int peer_dbus_get_remote_address(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + return -EINVAL; +} +*/ + +static const sd_bus_vtable peer_dbus_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("Allow", + "s", + NULL, + peer_dbus_allow, + 0), + SD_BUS_METHOD("Reject", + NULL, + NULL, + peer_dbus_reject, + 0), + SD_BUS_METHOD("Connect", + NULL, + NULL, + peer_dbus_connect, + 0), + SD_BUS_METHOD("Disconnect", + NULL, + NULL, + peer_dbus_disconnect, + 0), + SD_BUS_PROPERTY("Link", + "o", + peer_dbus_get_link, + 0, + SD_BUS_VTABLE_PROPERTY_CONST), +/* + SD_BUS_PROPERTY("Name", + "s", + peer_dbus_get_name, + 0, + SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Connected", + "b", + peer_dbus_get_connected, + 0, + SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Interface", + "s", + peer_dbus_get_interface, + 0, + 0), + SD_BUS_PROPERTY("LocalAddress", + "s", + peer_dbus_get_local_address, + 0, + 0), + SD_BUS_PROPERTY("RemoteAddress", + "s", + peer_dbus_get_remote_address, + 0, + 0), +*/ + SD_BUS_SIGNAL("ProvisionRequest", "ss", 0), + SD_BUS_VTABLE_END +}; + +static int peer_dbus_find(sd_bus *bus, + const char *path, + const char *interface, + void *data, + void **found, + sd_bus_error *err) +{ + struct manager *m = data; + struct peer *p; + const char *name; + + if (!(name = shl_startswith(path, "/org/freedesktop/miracle/peer/"))) + return 0; + + p = manager_find_peer(m, name); + if (!p) + return 0; + + *found = p; + return 1; +} + +/* + * Link DBus + */ + +static int link_dbus_start_scan(sd_bus *bus, sd_bus_message *msg, + void *data, sd_bus_error *err) +{ + return -EINVAL; +} + +static int link_dbus_stop_scan(sd_bus *bus, sd_bus_message *msg, + void *data, sd_bus_error *err) +{ + return -EINVAL; +} + +static int link_dbus_get_type(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + struct link *l = data; + int r; + + r = sd_bus_message_append_basic(reply, 's', + link_type_to_str(l->type)); + if (r < 0) + return r; + + return 1; +} + +static int link_dbus_get_interface(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + struct link *l = data; + int r; + + r = sd_bus_message_append_basic(reply, 's', l->interface); + if (r < 0) + return r; + + return 1; +} + +static int link_dbus_get_name(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *data, + sd_bus_error *err) +{ + struct link *l = data; + int r; + + r = sd_bus_message_append_basic(reply, 's', l->friendly_name); + if (r < 0) + return r; + + return 1; +} + +static int link_dbus_set_name(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *value, + void *data, + sd_bus_error *err) +{ + return -EACCES; +} + +static const sd_bus_vtable link_dbus_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("StartScan", + NULL, + NULL, + link_dbus_start_scan, + 0), + SD_BUS_METHOD("StopScan", + NULL, + NULL, + link_dbus_stop_scan, + 0), + SD_BUS_PROPERTY("Type", + "s", + link_dbus_get_type, + 0, + SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Interface", + "s", + link_dbus_get_interface, + 0, + SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_WRITABLE_PROPERTY("Name", + "s", + link_dbus_get_name, + link_dbus_set_name, + 0, + SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_VTABLE_END +}; + +static int link_dbus_find(sd_bus *bus, + const char *path, + const char *interface, + void *data, + void **found, + sd_bus_error *err) +{ + struct manager *m = data; + struct link *l; + const char *name; + + if (!(name = shl_startswith(path, "/org/freedesktop/miracle/link/"))) + return 0; + + l = manager_find_link(m, name); + if (!l) + return 0; + + *found = l; + return 1; +} + +/* + * Manager DBus + */ + +static int manager_dbus_add_link(sd_bus *bus, sd_bus_message *msg, + void *data, sd_bus_error *err) +{ + struct manager *m = data; + const char *stype, *interface; + unsigned int type; + struct link *l; + int r; + + r = sd_bus_message_read(msg, "ss", &stype, &interface); + if (r < 0) + return r; + + type = link_type_from_str(stype); + if (type >= LINK_CNT) + return sd_bus_error_setf(err, + SD_BUS_ERROR_INVALID_ARGS, + "invalid type"); + + r = link_new(m, type, interface, &l); + if (r == -EALREADY) + return sd_bus_error_setf(err, + SD_BUS_ERROR_INVALID_ARGS, + "link already available"); + else if (r < 0) + return r; + + return sd_bus_reply_method_return(msg, "s", l->name); +} + +static int manager_dbus_remove_link(sd_bus *bus, sd_bus_message *msg, + void *data, sd_bus_error *err) +{ + _cleanup_free_ char *link = NULL; + struct manager *m = data; + struct link *l; + const char *name; + int r; + + r = sd_bus_message_read(msg, "s", &name); + if (r < 0) + return r; + + link = sd_bus_label_escape(name); + if (!link) + return log_ENOMEM(); + + l = manager_find_link(m, link); + if (!l) + return sd_bus_error_setf(err, + SD_BUS_ERROR_INVALID_ARGS, + "link not available"); + + link_free(l); + + return sd_bus_reply_method_return(msg, NULL); +} + +static const sd_bus_vtable manager_dbus_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("AddLink", + "ss", + "s", + manager_dbus_add_link, + 0), + SD_BUS_METHOD("RemoveLink", + "s", + NULL, + manager_dbus_remove_link, + 0), + SD_BUS_VTABLE_END +}; + +static int manager_dbus_enumerate(sd_bus *bus, + const char *path, + void *data, + char ***out, + sd_bus_error *err) +{ + struct manager *m = data; + struct link *l; + struct peer *p; + size_t i; + char **nodes, *node; + int r; + + nodes = malloc(sizeof(*nodes) * (m->link_cnt + m->peer_cnt + 2)); + if (!nodes) + return log_ENOMEM(); + + i = 0; + MANAGER_FOREACH_LINK(l, m) { + if (i >= m->link_cnt + m->peer_cnt) { + log_warning("overflow: skipping link %s", + l->name); + continue; + } + + node = shl_strcat("/org/freedesktop/miracle/link/", + l->name); + if (!node) { + r = log_ENOMEM(); + goto error; + } + + nodes[i++] = node; + } + + MANAGER_FOREACH_PEER(p, m) { + if (i >= m->link_cnt + m->peer_cnt) { + log_warning("overflow: skipping peer %s", + p->name); + continue; + } + + node = shl_strcat("/org/freedesktop/miracle/peer/", + p->name); + if (!node) { + r = log_ENOMEM(); + goto error; + } + + nodes[i++] = node; + } + + node = strdup("/org/freedesktop/miracle"); + if (!node) { + r = log_ENOMEM(); + goto error; + } + + nodes[i++] = node; + nodes[i] = NULL; + *out = nodes; + + return 0; + +error: + while (i--) + free(nodes[i]); + free(nodes); + return r; +} + +int manager_dbus_connect(struct manager *m) +{ + int r; + + r = sd_bus_add_object_vtable(m->bus, + "/org/freedesktop/miracle", + "org.freedesktop.miracle.Manager", + manager_dbus_vtable, + m); + if (r < 0) + goto error; + + r = sd_bus_add_node_enumerator(m->bus, + "/org/freedesktop/miracle", + manager_dbus_enumerate, + m); + if (r < 0) + goto error; + + r = sd_bus_add_fallback_vtable(m->bus, + "/org/freedesktop/miracle/link", + "org.freedesktop.miracle.Link", + link_dbus_vtable, + link_dbus_find, + m); + if (r < 0) + goto error; + + r = sd_bus_add_fallback_vtable(m->bus, + "/org/freedesktop/miracle/peer", + "org.freedesktop.miracle.Peer", + peer_dbus_vtable, + peer_dbus_find, + m); + if (r < 0) + goto error; + + r = sd_bus_add_object_manager(m->bus, "/org/freedesktop/miracle"); + if (r < 0) + goto error; + + r = sd_bus_request_name(m->bus, "org.freedesktop.miracle", 0); + if (r < 0) { + log_error("cannot claim org.freedesktop.miracle bus-name: %d", + r); + goto error_silent; + } + + return 0; + +error: + log_ERR(r); +error_silent: + manager_dbus_disconnect(m); + return r; +} + +void manager_dbus_disconnect(struct manager *m) +{ + if (!m || !m->bus) + return; + + sd_bus_release_name(m->bus, "org.freedesktop.miracle"); + sd_bus_remove_object_manager(m->bus, "/org/freedesktop/miracle"); + sd_bus_remove_fallback_vtable(m->bus, + "/org/freedesktop/miracle/peer", + "org.freedesktop.miracle.Peer", + peer_dbus_vtable, + peer_dbus_find, + m); + sd_bus_remove_fallback_vtable(m->bus, + "/org/freedesktop/miracle/link", + "org.freedesktop.miracle.Link", + link_dbus_vtable, + link_dbus_find, + m); + sd_bus_remove_node_enumerator(m->bus, + "/org/freedesktop/miracle", + manager_dbus_enumerate, + m); + sd_bus_remove_object_vtable(m->bus, + "/org/freedesktop/miracle", + "org.freedesktop.miracle.Manager", + manager_dbus_vtable, + m); +} diff --git a/src/miracled-link.c b/src/miracled-link.c new file mode 100644 index 0000000..0b5c054 --- /dev/null +++ b/src/miracled-link.c @@ -0,0 +1,272 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * 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. + */ + +#define LOG_SUBSYSTEM "link" + +#include +#include +#include +#include +#include "miracled.h" +#include "miracled-wifi.h" +#include "shl_dlist.h" +#include "shl_log.h" +#include "shl_util.h" + +static const char *link_type_to_str_table[LINK_CNT] = { + [LINK_VIRTUAL] = "virtual", + [LINK_WIFI] = "wifi", +}; + +const char *link_type_to_str(unsigned int type) +{ + if (type >= LINK_CNT) + return NULL; + + return link_type_to_str_table[type]; +} + +unsigned int link_type_from_str(const char *str) +{ + unsigned int i; + + if (!str) + return LINK_CNT; + + for (i = 0; i < LINK_CNT; ++i) + if (link_type_to_str_table[i] && + !strcmp(link_type_to_str_table[i], str)) + return i; + + return LINK_CNT; +} + +int link_make_name(unsigned int type, const char *interface, char **out) +{ + const char *tname; + char *name, *res; + size_t tlen, ilen; + + tname = link_type_to_str(type); + if (!tname || !interface) + return -EINVAL; + + /* hard-coded maximum of 255 just to be safe */ + tlen = strlen(tname); + ilen = strlen(interface); + if (!tlen || tlen > 255 || !ilen || ilen > 255) + return -EINVAL; + + if (!out) + return 0; + + name = shl_strjoin(tname, ":", interface, NULL); + if (!name) + return log_ENOMEM(); + + res = sd_bus_label_escape(name); + free(name); + if (!res) + return log_ENOMEM(); + + *out = res; + return 0; +} + +/* + * Wifi Handling + */ + +static void link_wifi_event_fn(struct wifi *w, void *data, + struct wifi_event *ev) +{ + struct link *l = data; + struct peer *p; + + switch (ev->type) { + case WIFI_HUP: + /* destroy this link */ + link_free(l); + break; + case WIFI_DEV_FOUND: + peer_new_wifi(l, ev->dev_found.dev, NULL); + break; + case WIFI_DEV_LOST: + p = wifi_dev_get_data(ev->dev_lost.dev); + if (!p) + break; + + peer_free(p); + break; + case WIFI_DEV_PROVISION: + case WIFI_DEV_CONNECT: + case WIFI_DEV_DISCONNECT: + p = wifi_dev_get_data(ev->dev_lost.dev); + if (!p) + break; + + peer_process_wifi(p, ev); + break; + default: + log_debug("unhandled WIFI event: %u", ev->type); + break; + } +} + +static int link_wifi_init(struct link *l) +{ + struct wifi_dev *d; + int r; + char *path; + + r = wifi_new(l->m->event, link_wifi_event_fn, l, &l->w); + if (r < 0) + return r; + + path = shl_strcat("/run/wpa_supplicant/", l->interface); + if (!path) + return log_ENOMEM(); + + r = wifi_open(l->w, path); + free(path); + + if (r < 0) + return r; + + for (d = wifi_get_devs(l->w); d; d = wifi_dev_next(d)) + peer_new_wifi(l, d, NULL); + + return 0; +} + +static void link_wifi_destroy(struct link *l) +{ + wifi_close(l->w); + wifi_free(l->w); +} + +/* + * Link Handling + */ + +int link_new(struct manager *m, + unsigned int type, + const char *interface, + struct link **out) +{ + size_t hash = 0; + char *name; + struct link *l; + int r; + + if (!m) + return log_EINVAL(); + + r = link_make_name(type, interface, &name); + if (r < 0) + return r; + + if (shl_htable_lookup_str(&m->links, name, &hash, NULL)) { + free(name); + return -EALREADY; + } + + log_debug("new link: %s", name); + + l = calloc(1, sizeof(*l)); + if (!l) { + free(name); + return log_ENOMEM(); + } + + l->m = m; + l->type = type; + l->name = name; + shl_dlist_init(&l->peers); + + l->interface = strdup(interface); + if (!l->interface) { + r = log_ENOMEM(); + goto error; + } + + l->friendly_name = strdup(""); + if (!l->friendly_name) { + r = log_ENOMEM(); + goto error; + } + + switch (l->type) { + case LINK_VIRTUAL: + break; + case LINK_WIFI: + r = link_wifi_init(l); + if (r < 0) + goto error; + break; + } + + r = shl_htable_insert_str(&m->links, &l->name, &hash); + if (r < 0) { + log_vERR(r); + goto error; + } + + ++m->link_cnt; + log_info("new managed link: %s", l->name); + + if (out) + *out = l; + + return 0; + +error: + link_free(l); + return r; +} + +void link_free(struct link *l) +{ + struct peer *p; + + if (!l) + return; + + log_debug("free link: %s", l->name); + + while ((p = LINK_FIRST_PEER(l))) + peer_free(p); + + if (shl_htable_remove_str(&l->m->links, l->name, NULL, NULL)) { + log_info("remove managed link: %s", l->name); + --l->m->link_cnt; + } + + link_wifi_destroy(l); + free(l->friendly_name); + free(l->name); + free(l->interface); + free(l); +} diff --git a/src/miracled-peer.c b/src/miracled-peer.c new file mode 100644 index 0000000..1f55f83 --- /dev/null +++ b/src/miracled-peer.c @@ -0,0 +1,159 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * 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. + */ + +#define LOG_SUBSYSTEM "peer" + +#include +#include +#include +#include +#include "miracled.h" +#include "miracled-wifi.h" +#include "shl_log.h" + +static int peer_new(struct link *l, struct peer **out) +{ + unsigned int id; + size_t hash = 0; + char *name; + struct peer *p; + int r; + + if (!l) + return log_EINVAL(); + + id = ++l->m->peer_ids; + r = peer_make_name(id, &name); + if (r < 0) + return r; + + if (shl_htable_lookup_str(&l->m->peers, name, &hash, NULL)) { + free(name); + return -EALREADY; + } + + log_debug("new peer: %s", name); + + p = calloc(1, sizeof(*p)); + if (!p) { + free(name); + return log_ENOMEM(); + } + + p->l = l; + p->id = id; + p->name = name; + + r = shl_htable_insert_str(&l->m->peers, &p->name, &hash); + if (r < 0) { + log_vERR(r); + goto error; + } + + ++l->m->peer_cnt; + shl_dlist_link(&l->peers, &p->list); + log_info("new peer: %s@%s", p->name, l->name); + + if (out) + *out = p; + + return 0; + +error: + peer_free(p); + return r; +} + +int peer_new_wifi(struct link *l, struct wifi_dev *d, struct peer **out) +{ + int r; + struct peer *p; + + r = peer_new(l, &p); + if (r < 0) + return r; + + p->d = d; + wifi_dev_set_data(p->d, p); + + if (out) + *out = p; + + return 0; +} + +void peer_free(struct peer *p) +{ + if (!p) + return; + + log_debug("free peer: %s", p->name); + + if (shl_htable_remove_str(&p->l->m->peers, p->name, NULL, NULL)) { + log_info("remove managed peer: %s@%s", p->name, p->l->name); + --p->l->m->peer_cnt; + shl_dlist_unlink(&p->list); + } + + wifi_dev_set_data(p->d, NULL); + + free(p->name); + free(p); +} + +void peer_process_wifi(struct peer *p, struct wifi_event *ev) +{ + if (!p || !p->d) + return; + + switch (ev->type) { + case WIFI_DEV_PROVISION: + break; + case WIFI_DEV_CONNECT: + break; + case WIFI_DEV_DISCONNECT: + break; + default: + log_debug("unhandled WIFI event: %u", ev->type); + break; + } +} + +int peer_make_name(unsigned int id, char **out) +{ + char buf[64] = { }; + char *name; + + if (!out) + return 0; + + snprintf(buf, sizeof(buf) - 1, "%u", id); + name = sd_bus_label_escape(buf); + if (!name) + return log_ENOMEM(); + + *out = name; + return 0; +} diff --git a/src/miracled-wifi.c b/src/miracled-wifi.c new file mode 100644 index 0000000..9d334f5 --- /dev/null +++ b/src/miracled-wifi.c @@ -0,0 +1,1238 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * 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. + */ + +#define LOG_SUBSYSTEM "wifi" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "miracle.h" +#include "miracled-wifi.h" +#include "shl_dlist.h" +#include "shl_log.h" + +struct wifi { + sd_event *event; + wifi_event_t event_fn; + void *data; + char *reply_buf; + size_t reply_buf_size; + + struct wfd_wpa_ctrl *wpa; + sd_event_source *wpa_source; + struct shl_dlist devs; + + bool discoverable : 1; + bool hup : 1; +}; + +struct wifi_dev { + unsigned long ref; + void *data; + + struct shl_dlist list; + struct wifi *w; + + char mac[WFD_WPA_EVENT_MAC_STRLEN]; + char pin[WIFI_PIN_STRLEN + 1]; + unsigned int provision; + + char *ifname; + unsigned int role; + + int dhcp_comm; + pid_t dhcp_pid; + sd_event_source *dhcp_comm_source; + sd_event_source *dhcp_pid_source; + char *local_addr; + char *remote_addr; + + bool public : 1; /* is device known to caller? */ + bool connected : 1; /* is device properly connected? */ +}; + +static int wifi_dev_new(struct wifi *w, const char *mac, + struct wifi_dev **out); +static void wifi_dev_lost(struct wifi_dev *d); +static int wifi_dev_start(struct wifi_dev *d, const char *ifname, + unsigned int role); +static void wifi_dev_stop(struct wifi_dev *d); + +/* + * Management Helpers + */ + +static struct wifi_dev *wifi_find_dev_by_mac(struct wifi *w, const char *mac) +{ + struct shl_dlist *i; + struct wifi_dev *d; + + shl_dlist_for_each(i, &w->devs) { + d = shl_dlist_entry(i, struct wifi_dev, list); + if (!strcasecmp(d->mac, mac)) + return d; + } + + return NULL; +} + +static struct wifi_dev *wifi_find_dev_by_ifname(struct wifi *w, + const char *ifname) +{ + struct shl_dlist *i; + struct wifi_dev *d; + + shl_dlist_for_each(i, &w->devs) { + d = shl_dlist_entry(i, struct wifi_dev, list); + if (d->ifname && !strcmp(d->ifname, ifname)) + return d; + } + + return NULL; +} + +static void wifi_raise(struct wifi *w, struct wifi_event *ev) +{ + w->event_fn(w, w->data, ev); +} + +static void wifi_hup(struct wifi *w) +{ + struct wifi_event wev = { }; + + if (!wifi_is_open(w)) + return; + + log_info("HUP on wpa_supplicant socket"); + wifi_close(w); + wev.type = WIFI_HUP; + wifi_raise(w, &wev); +} + +static void wifi_show_dev(struct wifi *w, struct wifi_dev *d) +{ + struct wifi_event wev = { }; + + if (d->public) + return; + d->public = true; + + wev.type = WIFI_DEV_FOUND; + wev.dev_found.dev = d; + wifi_raise(w, &wev); +} + +static void wifi_hide_dev(struct wifi *w, struct wifi_dev *d) +{ + struct wifi_event wev = { }; + + if (!d->public) + return; + d->public = false; + + wev.type = WIFI_DEV_LOST; + wev.dev_lost.dev = d; + wifi_raise(w, &wev); +} + +static void wifi_pbc_req(struct wifi *w, struct wifi_dev *d) +{ + struct wifi_event wev = { }; + + wev.type = WIFI_DEV_PROVISION; + wev.dev_provision.dev = d; + wev.dev_provision.type = WIFI_PROVISION_PBC; + wifi_raise(w, &wev); +} + +static void wifi_display_req(struct wifi *w, struct wifi_dev *d, + const char *pin) +{ + struct wifi_event wev = { }; + + wev.type = WIFI_DEV_PROVISION; + wev.dev_provision.dev = d; + wev.dev_provision.type = WIFI_PROVISION_DISPLAY; + strncpy(wev.dev_provision.pin, pin, WIFI_PIN_STRLEN); + wifi_raise(w, &wev); +} + +static void wifi_pin_req(struct wifi *w, struct wifi_dev *d) +{ + struct wifi_event wev = { }; + + wev.type = WIFI_DEV_PROVISION; + wev.dev_provision.dev = d; + wev.dev_provision.type = WIFI_PROVISION_PIN; + wifi_raise(w, &wev); +} + +/* + * WPA Queries + */ + +static int wifi_request_ok(struct wifi *w, const char *req) +{ + return wfd_wpa_ctrl_request_ok(w->wpa, req, strlen(req), -1); +} + +static int wifi_requestv_ok(struct wifi *w, const char *format, va_list args) +{ + int r; + char *req; + + r = vasprintf(&req, format, args); + if (r < 0) + return log_ENOMEM(); + + r = wifi_request_ok(w, req); + free(req); + return r; +} + +static int wifi_requestf_ok(struct wifi *w, const char *format, ...) +{ + int r; + va_list args; + + va_start(args, format); + r = wifi_requestv_ok(w, format, args); + va_end(args); + return r; +} + +static ssize_t wifi_request(struct wifi *w, const char *req) +{ + int r; + size_t siz; + + siz = w->reply_buf_size; + r = wfd_wpa_ctrl_request(w->wpa, req, strlen(req), w->reply_buf, + &siz, -1); + if (r < 0) + return r; + + return siz; +} + +static ssize_t wifi_request_retry(struct wifi *w, const char *req) +{ + size_t siz; + char *buf; + + do { + siz = wifi_request(w, req); + if (siz + 1 < w->reply_buf_size) + return siz; + + /* try resizing the buffer for future requests */ + siz = w->reply_buf_size * 2; + if (siz <= w->reply_buf_size) + return log_ENOMEM(); + + buf = realloc(w->reply_buf, siz); + if (!buf) + return log_ENOMEM(); + + w->reply_buf = buf; + w->reply_buf_size = siz; + } while (true); +} + +static ssize_t wifi_requestv_retry(struct wifi *w, const char *format, + va_list args) +{ + int r; + char *req; + ssize_t l; + + r = vasprintf(&req, format, args); + if (r < 0) + return log_ENOMEM(); + + l = wifi_request_retry(w, req); + free(req); + return l; +} + +static ssize_t wifi_requestf_retry(struct wifi *w, const char *format, ...) +{ + ssize_t l; + va_list args; + + va_start(args, format); + l = wifi_requestv_retry(w, format, args); + va_end(args); + return l; +} + +static int wifi_parse_peer(struct wifi *w, size_t len, struct wifi_dev **out) +{ + char buf[512]; + char *pos, *next; + int x1, x2, x3, x4, x5, x6, r; + struct wifi_dev *d; + + if (!strncmp(w->reply_buf, "FAIL\n", 5)) + return -EAGAIN; + + pos = strchr(w->reply_buf, '\n'); + if (pos) + *pos = 0; + + /* verify mac-address */ + r = sscanf(w->reply_buf, "%2x:%2x:%2x:%2x:%2x:%2x", + &x1, &x2, &x3, &x4, &x5, &x6); + if (r != 6) { + log_debug("invalid P2P_PEER response: %s", w->reply_buf); + return -EINVAL; + } + sprintf(buf, "%2x:%2x:%2x:%2x:%2x:%2x", x1, x2, x3, x4, x5, x6); + + d = wifi_find_dev_by_mac(w, buf); + if (!d) { + r = wifi_dev_new(w, buf, &d); + if (r < 0) + return r; + } + + /* parse additional information */ + next = pos; + while (next) { + pos = ++next; + next = strchr(pos, '\n'); + if (next) + *next = 0; + + log_debug("add info: %s", pos); + } + + *out = d; + return 0; +} + +static int wifi_read_peer(struct wifi *w, const char *mac, + struct wifi_dev **out) +{ + ssize_t l; + + l = wifi_requestf_retry(w, "P2P_PEER %s", mac); + if (l < 0) { + log_error("cannot issue P2P_PEER: %d", (int)l); + return (int)l; + } + + return wifi_parse_peer(w, l, out); +} + +static int wifi_read_next_peer(struct wifi *w, const char *prev, + struct wifi_dev **out) +{ + ssize_t l; + + l = wifi_requestf_retry(w, "P2P_PEER NEXT-%s", prev); + if (l < 0) { + log_error("cannot issue P2P_PEER: %d", (int)l); + return (int)l; + } + + return wifi_parse_peer(w, l, out); +} + +/* + * WPA Event Parsers + */ + +static void wifi_event_p2p_find_stopped(struct wifi *w, char *msg, + struct wfd_wpa_event *ev) +{ + if (!w->discoverable) + return; + + w->discoverable = false; +} + +static void wifi_event_p2p_device_found(struct wifi *w, char *msg, + struct wfd_wpa_event *ev) +{ + struct wifi_dev *d; + int r; + + log_debug("received P2P-DEVICE-FOUND event: %s", + ev->p.p2p_device_found.peer_mac); + + r = wifi_read_peer(w, ev->p.p2p_device_found.peer_mac, &d); + if (r < 0) + return; + + wifi_show_dev(w, d); +} + +static void wifi_event_p2p_device_lost(struct wifi *w, char *msg, + struct wfd_wpa_event *ev) +{ + struct wifi_dev *d; + + d = wifi_find_dev_by_mac(w, ev->p.p2p_device_lost.peer_mac); + if (!d) { + log_debug("stray P2P-DEVICE-LOST event: %s", msg); + return; + } + + log_debug("received P2P-DEVICE-LOST event: %s", + ev->p.p2p_device_lost.peer_mac); + wifi_dev_lost(d); + wifi_hide_dev(w, d); + wifi_dev_unref(d); +} + +static void wifi_event_p2p_prov_disc_pbc_req(struct wifi *w, char *msg, + struct wfd_wpa_event *ev) +{ + struct wifi_dev *d; + + d = wifi_find_dev_by_mac(w, ev->p.p2p_prov_disc_pbc_req.peer_mac); + if (!d) { + log_debug("stray P2P-PROV-DISC-PBC-REQ event: %s", msg); + return; + } + + log_debug("received P2P-PROV-DISC-PBC-REQ event: %s", + ev->p.p2p_prov_disc_pbc_req.peer_mac); + + d->pin[0] = 0; + d->provision = WIFI_PROVISION_PBC, + wifi_pbc_req(w, d); +} + +static void wifi_event_p2p_prov_disc_show_pin(struct wifi *w, char *msg, + struct wfd_wpa_event *ev) +{ + struct wifi_dev *d; + + d = wifi_find_dev_by_mac(w, ev->p.p2p_prov_disc_show_pin.peer_mac); + if (!d) { + log_debug("stray P2P-PROV-DISC-SHOW-PIN event: %s", msg); + return; + } + + log_debug("received P2P-PROV-DISC-SHOW-PIN event: %s:%s", + ev->p.p2p_prov_disc_show_pin.pin, + ev->p.p2p_prov_disc_show_pin.peer_mac); + + strncpy(d->pin, ev->p.p2p_prov_disc_show_pin.pin, WIFI_PIN_STRLEN); + d->provision = WIFI_PROVISION_DISPLAY, + wifi_display_req(w, d, d->pin); +} + +static void wifi_event_p2p_prov_disc_enter_pin(struct wifi *w, char *msg, + struct wfd_wpa_event *ev) +{ + struct wifi_dev *d; + + d = wifi_find_dev_by_mac(w, ev->p.p2p_prov_disc_enter_pin.peer_mac); + if (!d) { + log_debug("stray P2P-PROV-DISC-ENTER-PIN event: %s", msg); + return; + } + + log_debug("received P2P-PROV-DISC-ENTER-PIN event: %s", + ev->p.p2p_prov_disc_enter_pin.peer_mac); + + d->pin[0] = 0; + d->provision = WIFI_PROVISION_PIN, + wifi_pin_req(w, d); +} + +static void wifi_event_p2p_go_neg_success(struct wifi *w, char *msg, + struct wfd_wpa_event *ev) +{ + struct wifi_dev *d; + + d = wifi_find_dev_by_mac(w, ev->p.p2p_go_neg_success.peer_mac); + if (!d) { + log_debug("stray P2P-GO-NEG-SUCCESS event: %s", msg); + return; + } + + log_debug("received P2P-GO-NEG-SUCCESS: %u:%s", + ev->p.p2p_go_neg_success.role, d->mac); +} + +static void wifi_event_p2p_group_started(struct wifi *w, char *msg, + struct wfd_wpa_event *ev) +{ + struct wifi_dev *d; + + d = wifi_find_dev_by_mac(w, ev->p.p2p_group_started.go_mac); + if (!d) { + log_debug("stray P2P-GROUP-STARTED event: %s", msg); + return; + } + + log_debug("received P2P-GROUP-STARTED: %s:%u:%s", + ev->p.p2p_group_started.ifname, + ev->p.p2p_group_started.role, d->mac); + + if (d->ifname) { + if (strcmp(d->ifname, ev->p.p2p_group_started.ifname)) + log_warning("ifname mismatch on group-starte: d.%s, e.%s", + d->ifname, ev->p.p2p_group_started.ifname); + if (d->role != ev->p.p2p_group_started.role) + log_warning("role mismatch on group-start: d.%u, e.%u", + d->role, ev->p.p2p_group_started.role); + return; + } + + wifi_dev_start(d, ev->p.p2p_group_started.ifname, + ev->p.p2p_group_started.role); +} + +static void wifi_event_p2p_group_removed(struct wifi *w, char *msg, + struct wfd_wpa_event *ev) +{ + struct wifi_dev *d; + + d = wifi_find_dev_by_ifname(w, ev->p.p2p_group_removed.ifname); + if (!d) { + log_debug("stray P2P-GROUP-REMOVED event: %s", msg); + return; + } + + log_debug("received P2P-GROUP-REMOVED: %s:%u:%s", + ev->p.p2p_group_removed.ifname, + ev->p.p2p_group_removed.role, d->mac); + + if (d->role != ev->p.p2p_group_removed.role) + log_warning("role mismatch on group-remove: d.%u, e.%u", + d->role, ev->p.p2p_group_removed.role); + + wifi_dev_stop(d); +} + +static void wifi_event_ctrl_event_terminating(struct wifi *w, char *msg, + struct wfd_wpa_event *ev) +{ + log_debug("received CTRL-EVENT-TERMINATING"); + w->hup = true; +} + +static void wifi_wpa_event_fn(struct wfd_wpa_ctrl *ctrl, void *data, + void *buf, size_t len) +{ + struct wifi *w = data; + struct wfd_wpa_event ev; + int r; + + wfd_wpa_event_init(&ev); + r = wfd_wpa_event_parse(&ev, buf); + if (r < 0) { + log_error("cannot parse wpa-event (%d): %s", r, (char*)buf); + return; + } + + switch (ev.type) { + case WFD_WPA_EVENT_P2P_FIND_STOPPED: + wifi_event_p2p_find_stopped(w, buf, &ev); + break; + case WFD_WPA_EVENT_P2P_DEVICE_FOUND: + wifi_event_p2p_device_found(w, buf, &ev); + break; + case WFD_WPA_EVENT_P2P_DEVICE_LOST: + wifi_event_p2p_device_lost(w, buf, &ev); + break; + case WFD_WPA_EVENT_P2P_PROV_DISC_PBC_REQ: + wifi_event_p2p_prov_disc_pbc_req(w, buf, &ev); + break; + case WFD_WPA_EVENT_P2P_PROV_DISC_SHOW_PIN: + wifi_event_p2p_prov_disc_show_pin(w, buf, &ev); + break; + case WFD_WPA_EVENT_P2P_PROV_DISC_ENTER_PIN: + wifi_event_p2p_prov_disc_enter_pin(w, buf, &ev); + break; + case WFD_WPA_EVENT_P2P_GO_NEG_SUCCESS: + wifi_event_p2p_go_neg_success(w, buf, &ev); + break; + case WFD_WPA_EVENT_P2P_GROUP_STARTED: + wifi_event_p2p_group_started(w, buf, &ev); + break; + case WFD_WPA_EVENT_P2P_GROUP_REMOVED: + wifi_event_p2p_group_removed(w, buf, &ev); + break; + case WFD_WPA_EVENT_CTRL_EVENT_SCAN_STARTED: + /* ignore */ + break; + case WFD_WPA_EVENT_CTRL_EVENT_TERMINATING: + wifi_event_ctrl_event_terminating(w, buf, &ev); + break; + case WFD_WPA_EVENT_UNKNOWN: + /* fallthrough */ + default: + log_debug("unhandled wpa-event: %s", (char*)buf); + break; + } + + wfd_wpa_event_reset(&ev); +} + +static int wifi_wpa_fd_fn(struct sd_event_source *source, int fd, + uint32_t mask, void *data) +{ + struct wifi *w = data; + int r; + + r = wfd_wpa_ctrl_dispatch(w->wpa, 0); + if (r < 0) { + log_debug("dispatching wpa_supplicant messages failed: %d", r); + wifi_hup(w); + } else if (w->hup) { + wifi_hup(w); + } + + return 0; +} + +/* + * Wifi Object Management + */ + +int wifi_new(sd_event *event, wifi_event_t event_fn, void *data, + struct wifi **out) +{ + struct wifi *w; + int r; + + if (!event || !event_fn || !out) + return log_EINVAL(); + + w = calloc(1, sizeof(*w)); + if (!w) + return log_ENOMEM(); + + shl_dlist_init(&w->devs); + w->event = sd_event_ref(event); + w->event_fn = event_fn; + w->data = data; + + w->reply_buf_size = 4096; + w->reply_buf = calloc(1, w->reply_buf_size); + if (!w->reply_buf) { + r = log_ENOMEM(); + goto error; + } + + r = wfd_wpa_ctrl_new(wifi_wpa_event_fn, w, &w->wpa); + if (r < 0) + goto error; + + r = sd_event_add_io(w->event, + wfd_wpa_ctrl_get_fd(w->wpa), + EPOLLHUP | EPOLLERR | EPOLLIN, + wifi_wpa_fd_fn, + w, + &w->wpa_source); + if (r < 0) { + log_vERR(r); + goto error; + } + + *out = w; + return 0; + +error: + wifi_free(w); + return r; +} + +void wifi_free(struct wifi *w) +{ + if (!w) + return; + + wifi_close(w); + sd_event_source_unref(w->wpa_source); + wfd_wpa_ctrl_unref(w->wpa); + free(w->reply_buf); + sd_event_unref(w->event); + free(w); +} + +void wifi_set_data(struct wifi *w, void *data) +{ + if (!w) + return; + + w->data = data; +} + +void *wifi_get_data(struct wifi *w) +{ + return w ? w->data : NULL; +} + +bool wifi_is_open(struct wifi *w) +{ + return w && wfd_wpa_ctrl_is_open(w->wpa); +} + +static int wifi_read_all_peers(struct wifi *w) +{ + int r; + struct wifi_dev *d = NULL; + + while (true) { + if (!d) + r = wifi_read_peer(w, "FIRST", &d); + else + r = wifi_read_next_peer(w, d->mac, &d); + if (r < 0) + break; + + d->public = true; + } + + return (r == -EAGAIN) ? 0 : r; +} + +int wifi_open(struct wifi *w, const char *wpa_path) +{ + int r; + + if (!w || !wpa_path) + return log_EINVAL(); + if (wifi_is_open(w)) + return -EALREADY; + + log_debug("open wifi on: %s", wpa_path); + + r = wfd_wpa_ctrl_open(w->wpa, wpa_path); + if (r < 0) { + log_error("cannot open wpa_supplicant socket %s: %d", + wpa_path, r); + goto error; + } + + r = wifi_read_all_peers(w); + if (r < 0) + goto error; + + return 0; + +error: + wifi_close(w); + return r; +} + +void wifi_close(struct wifi *w) +{ + struct wifi_dev *d; + + if (!w) + return; + + if (wfd_wpa_ctrl_is_open(w->wpa)) { + log_debug("close wifi"); + wifi_set_discoverable(w, false); + } + + while (!shl_dlist_empty(&w->devs)) { + d = shl_dlist_first_entry(&w->devs, struct wifi_dev, list); + wifi_dev_lost(d); + wifi_dev_unref(d); + } + + wfd_wpa_ctrl_close(w->wpa); + w->wpa = NULL; +} + +int wifi_set_discoverable(struct wifi *w, bool on) +{ + int r; + + if (!w || !wifi_is_open(w)) + return log_EINVAL(); + if (w->discoverable == on) + return 0; + + if (on) { + r = wifi_request_ok(w, "P2P_FIND"); + if (r < 0) { + log_warning("cannot issue P2P_FIND: %d", r); + return r; + } + } else { + r = wifi_request_ok(w, "P2P_STOP_FIND"); + if (r < 0) { + log_warning("cannot issue P2P_STOP_FIND: %d", r); + return r; + } + } + + w->discoverable = on; + return 0; +} + +struct wifi_dev *wifi_get_devs(struct wifi *w) +{ + if (!w || shl_dlist_empty(&w->devs)) + return NULL; + + return shl_dlist_first_entry(&w->devs, struct wifi_dev, list); +} + +struct wifi_dev *wifi_dev_next(struct wifi_dev *d) +{ + if (!d || !d->w || d->list.next == &d->w->devs) + return NULL; + + return shl_dlist_entry(d->list.next, struct wifi_dev, list); +} + +/* + * Wifi Device + */ + +static int wifi_dev_new(struct wifi *w, const char *mac, + struct wifi_dev **out) +{ + struct wifi_dev *d; + + log_debug("new device: %s", mac); + + d = calloc(1, sizeof(*d)); + if (!d) + return log_ENOMEM(); + + d->ref = 1; + shl_dlist_link(&w->devs, &d->list); + d->w = w; + + strncpy(d->mac, mac, sizeof(d->mac) - 1); + + d->pin[0] = 0; + d->provision = WIFI_PROVISION_CNT; + + d->role = WFD_WPA_EVENT_ROLE_CNT; + d->dhcp_comm = -1; + + *out = d; + return 0; +} + +static void wifi_dev_set_connected(struct wifi_dev *d, bool set, bool event) +{ + struct wifi_event wev = { }; + + if (d->connected == set) + return; + if (!wifi_dev_is_running(d)) + return log_vEINVAL(); + + if (set) { + d->connected = true; + + wev.type = WIFI_DEV_CONNECT; + wev.dev_connect.dev = d; + if (event) + wifi_raise(d->w, &wev); + } else { + d->connected = false; + wev.type = WIFI_DEV_DISCONNECT; + wev.dev_disconnect.dev = d; + if (event) + wifi_raise(d->w, &wev); + } +} + +static int wifi_dev_spawn_dhcp_client(struct wifi_dev *d) +{ + char *argv[64], loglevel[64], commfd[64]; + int i, r, fds[2]; + pid_t pid; + sigset_t mask; + + r = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds); + if (r < 0) + return log_ERRNO(); + + pid = fork(); + if (pid < 0) { + close(fds[0]); + close(fds[1]); + return log_ERRNO(); + } else if (!pid) { + /* child */ + + close(fds[0]); + sprintf(loglevel, "%u", log_max_sev); + sprintf(commfd, "%d", fds[1]); + sigemptyset(&mask); + sigprocmask(SIG_SETMASK, &mask, NULL); + + /* redirect stdout to stderr */ + dup2(2, 1); + + i = 0; + argv[i++] = (char*) BUILD_BINDIR "/miracle-dhcp"; + argv[i++] = "--log-level"; + argv[i++] = loglevel; + argv[i++] = "--netdev"; + argv[i++] = d->ifname; + argv[i++] = "--comm-fd"; + argv[i++] = commfd; + argv[i] = NULL; + + execve(argv[0], argv, environ); + _exit(1); + } + + close(fds[1]); + d->dhcp_comm = fds[0]; + d->dhcp_pid = pid; + + return 0; +} + +static int wifi_dev_comm_fn(sd_event_source *source, int fd, uint32_t mask, + void *data) +{ + struct wifi_dev *d = data; + char buf[512], *t; + ssize_t l; + + l = recv(fd, buf, sizeof(buf) - 1, MSG_DONTWAIT); + if (l < 0) { + l = -errno; + if (l == -EAGAIN || l == -EINTR) + return 0; + + log_vERRNO(); + goto error; + } else if (!l) { + log_error("HUP on dhcp comm socket"); + goto error; + } else if (l > sizeof(buf) - 1) { + l = sizeof(buf) - 1; + } + + buf[l] = 0; + log_debug("dhcp-comm: %s", buf); + + /* we only parse "X:" right now */ + if (l < 3 || buf[1] != ':' || !buf[2]) + return 0; + + t = strdup(&buf[2]); + if (!t) { + log_vENOMEM(); + return 0; + } + + switch (buf[0]) { + case 'L': + free(d->local_addr); + d->local_addr = t; + break; + case 'G': + free(d->remote_addr); + d->remote_addr = t; + break; + default: + free(t); + break; + } + + if (d->local_addr && d->remote_addr) { + /* got DHCP lease, connection is established */ + wifi_dev_set_connected(d, true, true); + } + + return 0; + +error: + wifi_dev_stop(d); + return 0; +} + +static int wifi_dev_pid_fn(sd_event_source *source, const siginfo_t *info, + void *data) +{ + struct wifi_dev *d = data; + + log_error("DHCP client/server for %s died, stopping connection", + d->mac); + wifi_dev_stop(d); + + return 0; +} + +static int wifi_dev_start(struct wifi_dev *d, const char *ifname, + unsigned int role) +{ + int r; + + if (d->ifname) + return 0; + if (!ifname || role >= WFD_WPA_EVENT_ROLE_CNT) + return log_EINVAL(); + + d->ifname = strdup(ifname); + if (!d->ifname) + return log_ENOMEM(); + + d->role = role; + + switch (d->role) { + case WFD_WPA_EVENT_ROLE_GO: + break; + case WFD_WPA_EVENT_ROLE_CLIENT: + r = wifi_dev_spawn_dhcp_client(d); + if (r < 0) { + log_error("cannot spawn DHCP client for: %s:%s", + ifname, d->mac); + goto error; + } + break; + default: + log_error("unknown wpa-role: %u", d->role); + goto error; + } + + r = sd_event_add_io(d->w->event, + d->dhcp_comm, + EPOLLHUP | EPOLLERR | EPOLLIN, + wifi_dev_comm_fn, + d, + &d->dhcp_comm_source); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = sd_event_add_child(d->w->event, + d->dhcp_pid, + WEXITED, + wifi_dev_pid_fn, + d, + &d->dhcp_pid_source); + if (r < 0) { + log_vERR(r); + goto error; + } + + return 0; + +error: + wifi_dev_stop(d); + return r; +} + +static void wifi_dev_stop(struct wifi_dev *d) +{ + int r; + pid_t rp; + + if (!d->ifname) + return; + + wifi_dev_set_connected(d, false, true); + wifi_requestf_ok(d->w, "P2P_GROUP_REMOVE %s", d->ifname); + + free(d->local_addr); + d->local_addr = NULL; + free(d->remote_addr); + d->remote_addr = NULL; + + if (d->dhcp_pid > 0) { + sd_event_source_unref(d->dhcp_pid_source); + d->dhcp_pid_source = NULL; + + log_debug("killing DHCP pid:%d and waiting for exit..", + d->dhcp_pid); + r = kill(d->dhcp_pid, SIGTERM); + if (r >= 0) + rp = waitpid(d->dhcp_pid, NULL, 0); + if (r < 0 || rp != d->dhcp_pid) { + r = kill(d->dhcp_pid, SIGKILL); + if (r >= 0) + waitpid(d->dhcp_pid, &r, 0); + } + d->dhcp_pid = 0; + } + + if (d->dhcp_comm >= 0) { + sd_event_source_unref(d->dhcp_comm_source); + d->dhcp_comm_source = NULL; + close(d->dhcp_comm); + d->dhcp_comm = -1; + } + + free(d->ifname); + d->ifname = NULL; + d->role = WFD_WPA_EVENT_ROLE_CNT; +} + +static void wifi_dev_lost(struct wifi_dev *d) +{ + if (!wifi_dev_is_available(d)) + return; + + log_debug("lost device: %s", d->mac); + + wifi_dev_stop(d); + shl_dlist_unlink(&d->list); + d->w = NULL; +} + +void wifi_dev_ref(struct wifi_dev *d) +{ + if (!d || !d->ref) + return; + + ++d->ref; +} + +void wifi_dev_unref(struct wifi_dev *d) +{ + if (!d || !d->ref || --d->ref) + return; + + wifi_dev_set_connected(d, false, false); + wifi_dev_lost(d); + free(d); +} + +void wifi_dev_set_data(struct wifi_dev *d, void *data) +{ + if (!d) + return; + + d->data = data; +} + +void *wifi_dev_get_data(struct wifi_dev *d) +{ + return d ? d->data : NULL; +} + +bool wifi_dev_is_available(struct wifi_dev *d) +{ + return d && d->w; +} + +bool wifi_dev_is_running(struct wifi_dev *d) +{ + return d && d->ifname; +} + +bool wifi_dev_is_ready(struct wifi_dev *d) +{ + return d && d->connected; +} + +void wifi_dev_allow(struct wifi_dev *d, const char *pin) +{ + int r; + + if (!wifi_dev_is_available(d) || wifi_dev_is_running(d)) + return; + if (d->provision == WIFI_PROVISION_CNT) + return; + + r = 0; + switch (d->provision) { + case WIFI_PROVISION_PBC: + r = wifi_requestf_ok(d->w, + "P2P_CONNECT %s pbc display go_intent=0", + d->mac); + break; + case WIFI_PROVISION_DISPLAY: + if (!*d->pin) { + log_vEINVAL(); + break; + } + + r = wifi_requestf_ok(d->w, + "P2P_CONNECT %s %s display go_intent=0", + d->mac, d->pin); + break; + case WIFI_PROVISION_PIN: + if (!pin || !*pin) { + log_vEINVAL(); + break; + } + + r = wifi_requestf_ok(d->w, + "P2P_CONNECT %s %s display go_intent=0", + d->mac, pin); + break; + } + + if (r < 0) + log_warning("cannot issue P2P_CONNECT on dev_allow(): %d", r); + + d->provision = WIFI_PROVISION_CNT; +} + +void wifi_dev_reject(struct wifi_dev *d) +{ + if (!wifi_dev_is_available(d) || wifi_dev_is_running(d)) + return; + if (d->provision == WIFI_PROVISION_CNT) + return; + + wifi_request_ok(d->w, "P2P_CANCEL"); + d->provision = WIFI_PROVISION_CNT; +} + +int wifi_dev_connect(struct wifi_dev *d, unsigned int provision, + const char *pin) +{ + if (!wifi_dev_is_available(d)) + return log_EINVAL(); + if (wifi_dev_is_running(d)) + return 0; + + return -EINVAL; +} + +void wifi_dev_disconnect(struct wifi_dev *d) +{ + if (!wifi_dev_is_running(d)) + return; + + wifi_dev_set_connected(d, false, false); + wifi_dev_stop(d); +} diff --git a/src/miracled-wifi.h b/src/miracled-wifi.h new file mode 100644 index 0000000..10a1772 --- /dev/null +++ b/src/miracled-wifi.h @@ -0,0 +1,125 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * 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. + */ + +#include +#include +#include +#include +#include "miracle.h" + +#ifndef MIRACLED_WIFI_H +#define MIRACLED_WIFI_H + +struct wifi; +struct wifi_dev; + +/* wifi */ + +struct wifi; +struct wifi_dev; + +enum wifi_event_type { + WIFI_HUP, + WIFI_DEV_FOUND, + WIFI_DEV_LOST, + WIFI_DEV_PROVISION, + WIFI_DEV_CONNECT, + WIFI_DEV_DISCONNECT, +}; + +enum wifi_provision_type { + WIFI_PROVISION_PBC, + WIFI_PROVISION_DISPLAY, + WIFI_PROVISION_PIN, + WIFI_PROVISION_CNT, +}; + +/* WPS pins are fixed to 8 chars (+1 terminating zero) */ +#define WIFI_PIN_STRLEN (8 + 1) + +struct wifi_event { + unsigned int type; + + union { + struct wifi_event_dev_found { + struct wifi_dev *dev; + } dev_found; + + struct wifi_event_dev_lost { + struct wifi_dev *dev; + } dev_lost; + + struct wifi_event_dev_provision { + struct wifi_dev *dev; + unsigned int type; + char pin[WIFI_PIN_STRLEN]; + } dev_provision; + + struct wifi_event_dev_connect { + struct wifi_dev *dev; + } dev_connect; + + struct wifi_event_dev_disconnect { + struct wifi_dev *dev; + } dev_disconnect; + }; +}; + +typedef void (*wifi_event_t) (struct wifi *w, void *data, + struct wifi_event *ev); + +int wifi_new(sd_event *event, wifi_event_t event_fn, void *data, + struct wifi **out); +void wifi_free(struct wifi *w); +void wifi_set_data(struct wifi *w, void *data); +void *wifi_get_data(struct wifi *w); + +bool wifi_is_open(struct wifi *w); +int wifi_open(struct wifi *w, const char *wpa_path); +void wifi_close(struct wifi *w); + +int wifi_set_discoverable(struct wifi *w, bool on); + +struct wifi_dev *wifi_get_devs(struct wifi *w); +struct wifi_dev *wifi_dev_next(struct wifi_dev *d); + +/* wifi device */ + +void wifi_dev_ref(struct wifi_dev *d); +void wifi_dev_unref(struct wifi_dev *d); +void wifi_dev_set_data(struct wifi_dev *d, void *data); +void *wifi_dev_get_data(struct wifi_dev *d); + +bool wifi_dev_is_available(struct wifi_dev *d); +bool wifi_dev_is_running(struct wifi_dev *d); +bool wifi_dev_is_ready(struct wifi_dev *d); + +void wifi_dev_allow(struct wifi_dev *d, const char *pin); +void wifi_dev_reject(struct wifi_dev *d); +int wifi_dev_connect(struct wifi_dev *d, unsigned int provision, + const char *pin); +void wifi_dev_disconnect(struct wifi_dev *d); + +#endif /* MIRACLED_WIFI_H */ diff --git a/src/miracled.c b/src/miracled.c new file mode 100644 index 0000000..2956d84 --- /dev/null +++ b/src/miracled.c @@ -0,0 +1,285 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "miracled.h" +#include "shl_htable.h" +#include "shl_log.h" + +/* + * Peer Handling + */ + +struct peer *manager_find_peer(struct manager *m, const char *name) +{ + char **elem; + bool res; + + res = shl_htable_lookup_str(&m->peers, name, NULL, &elem); + if (!res) + return NULL; + + return peer_from_htable(elem); +} + +/* + * Link Handling + */ + +struct link *manager_find_link(struct manager *m, const char *name) +{ + char **elem; + bool res; + + res = shl_htable_lookup_str(&m->links, name, NULL, &elem); + if (!res) + return NULL; + + return link_from_htable(elem); +} + +/* + * Manager Handling + */ + +static int manager_signal_fn(sd_event_source *source, + const struct signalfd_siginfo *ssi, + void *data) +{ + struct manager *m = data; + + if (ssi->ssi_signo == SIGCHLD) { + log_debug("caught SIGCHLD for %d", (int)ssi->ssi_pid); + return 0; + } + + log_notice("caught signal %d, exiting..", (int)ssi->ssi_signo); + sd_event_exit(m->event, 0); + + return 0; +} + +static void manager_free(struct manager *m) +{ + unsigned int i; + struct link *l; + + if (!m) + return; + + while ((l = MANAGER_FIRST_LINK(m))) + link_free(l); + + shl_htable_clear_str(&m->links, NULL, NULL); + shl_htable_clear_str(&m->peers, NULL, NULL); + + manager_dbus_disconnect(m); + for (i = 0; m->sigs[i]; ++i) + sd_event_source_unref(m->sigs[i]); + sd_bus_unref(m->bus); + sd_event_unref(m->event); + free(m); +} + +static int manager_new(struct manager **out) +{ + struct manager *m; + static const int sigs[] = { + SIGINT, SIGTERM, SIGQUIT, SIGHUP, SIGPIPE, SIGCHLD, 0 + }; + unsigned int i; + sigset_t mask; + int r; + + m = calloc(1, sizeof(*m)); + if (!m) + return log_ENOMEM(); + + shl_htable_init_str(&m->links); + shl_htable_init_str(&m->peers); + + r = sd_event_default(&m->event); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = sd_event_set_watchdog(m->event, true); + if (r < 0) { + log_vERR(r); + goto error; + } + + r = sd_bus_default_system(&m->bus); + if (r < 0) { + log_error("cannot connect to system bus: %d", r); + goto error; + } + + r = sd_bus_attach_event(m->bus, m->event, 0); + if (r < 0) { + log_vERR(r); + goto error; + } + + for (i = 0; sigs[i]; ++i) { + sigemptyset(&mask); + sigaddset(&mask, sigs[i]); + sigprocmask(SIG_BLOCK, &mask, NULL); + + r = sd_event_add_signal(m->event, + sigs[i], + manager_signal_fn, + m, + &m->sigs[i]); + if (r < 0) { + log_vERR(r); + goto error; + } + } + + r = manager_dbus_connect(m); + if (r < 0) + goto error; + + *out = m; + return 0; + +error: + manager_free(m); + return r; +} + +static int manager_run(struct manager *m) +{ + return sd_event_loop(m->event); +} + +static int help(void) +{ + printf("%s [OPTIONS...] ...\n\n" + "Wifi-Display Daemon.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --log-level Maximum level for log messages\n" + " --log-time Prefix log-messages with timestamp\n" + "\n" + " --netdev Network device to run on\n" + " --wpa-rundir wpa_supplicant runtime dir [default: /run/wpa_supplicant]\n" + , program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) +{ + enum { + ARG_VERSION = 0x100, + ARG_LOG_LEVEL, + ARG_LOG_TIME, + }; + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + { "log-time", no_argument, NULL, ARG_LOG_TIME }, + {} + }; + int c; + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + switch (c) { + case 'h': + return help(); + case ARG_VERSION: + puts(PACKAGE_STRING); + return 0; + case ARG_LOG_LEVEL: + log_max_sev = atoi(optarg); + break; + case ARG_LOG_TIME: + log_init_time(); + break; + case '?': + return -EINVAL; + } + } + + if (optind < argc) { + log_error("unparsed remaining arguments starting with: %s", + argv[optind]); + return -EINVAL; + } + + log_format(LOG_DEFAULT_BASE, NULL, LOG_INFO, + "miracled - revision %s %s %s", + "some-rev-TODO-xyz", __DATE__, __TIME__); + + return 1; +} + +int main(int argc, char **argv) +{ + struct manager *m = NULL; + int r; + + r = parse_argv(argc, argv); + if (r < 0) + return EXIT_FAILURE; + if (!r) + return EXIT_SUCCESS; + + r = manager_new(&m); + if (r < 0) + goto finish; + + r = sd_notify(false, "READY=1\n" + "STATUS=Running.."); + if (r < 0) { + log_vERR(r); + goto finish; + } + + r = manager_run(m); + +finish: + sd_notify(false, "STATUS=Exiting.."); + manager_free(m); + + log_debug("exiting.."); + return abs(r); +} diff --git a/src/miracled.h b/src/miracled.h new file mode 100644 index 0000000..e6617ab --- /dev/null +++ b/src/miracled.h @@ -0,0 +1,131 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * 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. + */ + +#include +#include +#include +#include +#include +#include "miracle.h" +#include "shl_dlist.h" +#include "shl_htable.h" + +#ifndef MIRACLED_H +#define MIRACLED_H + +struct manager; +struct link; +struct peer; +struct wifi; +struct wifi_dev; +struct wifi_event; + +/* peer */ + +struct peer { + struct shl_dlist list; + struct link *l; + unsigned int id; + char *name; + + struct wifi_dev *d; +}; + +#define peer_from_htable(_p) \ + shl_htable_offsetof((_p), struct peer, name) +#define peer_from_list(_p) \ + shl_dlist_entry((_p), struct peer, list) + +int peer_new_wifi(struct link *l, struct wifi_dev *d, struct peer **out); +void peer_free(struct peer *p); + +void peer_process_wifi(struct peer *p, struct wifi_event *ev); + +int peer_make_name(unsigned int id, char **out); + +/* link */ + +enum link_type { + LINK_VIRTUAL, + LINK_WIFI, + LINK_CNT, +}; + +struct link { + struct manager *m; + unsigned int type; + char *interface; + char *name; + char *friendly_name; + + struct shl_dlist peers; + + struct wifi *w; +}; + +#define link_from_htable(_l) \ + shl_htable_offsetof((_l), struct link, name) +#define LINK_FIRST_PEER(_l) (shl_dlist_empty(&(_l)->peers) ? \ + NULL : peer_from_list((_l)->peers.next)) + +const char *link_type_to_str(unsigned int type); +unsigned int link_type_from_str(const char *str); +int link_make_name(unsigned int type, const char *interface, char **out); + +int link_new(struct manager *m, + unsigned int type, + const char *interface, + struct link **out); +void link_free(struct link *l); + +/* manager */ + +struct manager { + sd_event *event; + sd_bus *bus; + sd_event_source *sigs[_NSIG]; + + unsigned int peer_ids; + + size_t link_cnt; + size_t peer_cnt; + struct shl_htable links; + struct shl_htable peers; +}; + +#define MANAGER_FIRST_LINK(_m) \ + SHL_HTABLE_FIRST_MACRO(&(_m)->links, link_from_htable) +#define MANAGER_FOREACH_LINK(_i, _m) \ + SHL_HTABLE_FOREACH_MACRO(_i, &(_m)->links, link_from_htable) +#define MANAGER_FOREACH_PEER(_i, _m) \ + SHL_HTABLE_FOREACH_MACRO(_i, &(_m)->peers, peer_from_htable) + +int manager_dbus_connect(struct manager *m); +void manager_dbus_disconnect(struct manager *m); + +struct link *manager_find_link(struct manager *m, const char *name); +struct peer *manager_find_peer(struct manager *m, const char *name); + +#endif /* MIRACLED_H */ diff --git a/src/shl_dlist.h b/src/shl_dlist.h new file mode 100644 index 0000000..9d6f6fc --- /dev/null +++ b/src/shl_dlist.h @@ -0,0 +1,138 @@ +/* + * SHL - Double Linked List + * + * Copyright (c) 2010-2013 David Herrmann + * Dedicated to the Public Domain + */ + +/* + * A simple double linked list implementation + * This list API does not provide type-safety! It is a simple circular + * double-linked list. Objects need to embed "struct shl_dlist". This is used to + * link and iterate a list. You can get the object back from a shl_dlist pointer + * via shl_dlist_entry(). This breaks any type-safety, though. You need to make + * sure you call this on the right objects. + */ + +#ifndef SHL_DLIST_H +#define SHL_DLIST_H + +#include +#include +#include + +/* miscellaneous */ + +#define shl_dlist_offsetof(pointer, type, member) ({ \ + const typeof(((type*)0)->member) *__ptr = (pointer); \ + (type*)(((char*)__ptr) - offsetof(type, member)); \ + }) + +/* double linked list */ + +struct shl_dlist { + struct shl_dlist *next; + struct shl_dlist *prev; +}; + +#define SHL_DLIST_INIT(head) { &(head), &(head) } + +static inline void shl_dlist_init(struct shl_dlist *list) +{ + list->next = list; + list->prev = list; +} + +static inline void shl_dlist__link(struct shl_dlist *prev, + struct shl_dlist *next, + struct shl_dlist *n) +{ + next->prev = n; + n->next = next; + n->prev = prev; + prev->next = n; +} + +static inline void shl_dlist_link(struct shl_dlist *head, + struct shl_dlist *n) +{ + return shl_dlist__link(head, head->next, n); +} + +static inline void shl_dlist_link_tail(struct shl_dlist *head, + struct shl_dlist *n) +{ + return shl_dlist__link(head->prev, head, n); +} + +static inline void shl_dlist__unlink(struct shl_dlist *prev, + struct shl_dlist *next) +{ + next->prev = prev; + prev->next = next; +} + +static inline void shl_dlist_unlink(struct shl_dlist *e) +{ + shl_dlist__unlink(e->prev, e->next); + e->prev = NULL; + e->next = NULL; +} + +static inline bool shl_dlist_empty(struct shl_dlist *head) +{ + return head->next == head; +} + +static inline struct shl_dlist *shl_dlist_first(struct shl_dlist *head) +{ + return head->next; +} + +static inline struct shl_dlist *shl_dlist_last(struct shl_dlist *head) +{ + return head->prev; +} + +#define shl_dlist_entry(ptr, type, member) \ + shl_dlist_offsetof((ptr), type, member) + +#define shl_dlist_first_entry(head, type, member) \ + shl_dlist_entry(shl_dlist_first(head), type, member) + +#define shl_dlist_last_entry(head, type, member) \ + shl_dlist_entry(shl_dlist_last(head), type, member) + +#define shl_dlist_for_each(iter, head) \ + for (iter = (head)->next; iter != (head); iter = iter->next) + +#define shl_dlist_for_each_but_one(iter, start, head) \ + for (iter = ((start)->next == (head)) ? \ + (start)->next->next : \ + (start)->next; \ + iter != (start); \ + iter = (iter->next == (head) && (start) != (head)) ? \ + iter->next->next : \ + iter->next) + +#define shl_dlist_for_each_safe(iter, tmp, head) \ + for (iter = (head)->next, tmp = iter->next; iter != (head); \ + iter = tmp, tmp = iter->next) + +#define shl_dlist_for_each_reverse(iter, head) \ + for (iter = (head)->prev; iter != (head); iter = iter->prev) + +#define shl_dlist_for_each_reverse_but_one(iter, start, head) \ + for (iter = ((start)->prev == (head)) ? \ + (start)->prev->prev : \ + (start)->prev; \ + iter != (start); \ + iter = (iter->prev == (head) && (start) != (head)) ? \ + iter->prev->prev : \ + iter->prev) + +#define shl_dlist_for_each_reverse_safe(iter, tmp, head) \ + for (iter = (head)->prev, tmp = iter->prev; iter != (head); \ + iter = tmp, tmp = iter->prev) + +#endif /* SHL_DLIST_H */ diff --git a/src/shl_htable.c b/src/shl_htable.c new file mode 100644 index 0000000..8127ec6 --- /dev/null +++ b/src/shl_htable.c @@ -0,0 +1,464 @@ +/* + * SHL - Dynamic hash-table + * + * Written-by: Rusty Russell + * Adjusted-by: David Herrmann + * Licensed under LGPLv2+ - see LICENSE_htable file for details + */ + +/* + * Please see ccan/htable/_info at: + * https://github.com/rustyrussell/ccan/tree/master/ccan/htable + * for information on the hashtable algorithm. This file copies the code inline + * and is released under the same conditions. + * + * At the end of the file you can find some helpers to use this htable to store + * objects with "unsigned long" or "char*" keys. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "shl_htable.h" + +#define COLD __attribute__((cold)) + +struct htable { + /* KEEP IN SYNC WITH "struct shl_htable_int" */ + size_t (*rehash)(const void *elem, void *priv); + void *priv; + unsigned int bits; + size_t elems, deleted, max, max_with_deleted; + /* These are the bits which are the same in all pointers. */ + uintptr_t common_mask, common_bits; + uintptr_t perfect_bit; + uintptr_t *table; +}; + +#define HTABLE_INITIALIZER(name, rehash, priv) \ + { rehash, priv, 0, 0, 0, 0, 0, -1, 0, 0, &name.perfect_bit } + +struct htable_iter { + size_t off; +}; + +/* + * INLINE COPY OF ccan/htable.c + */ + +/* We use 0x1 as deleted marker. */ +#define HTABLE_DELETED (0x1) + +/* We clear out the bits which are always the same, and put metadata there. */ +static inline uintptr_t get_extra_ptr_bits(const struct htable *ht, + uintptr_t e) +{ + return e & ht->common_mask; +} + +static inline void *get_raw_ptr(const struct htable *ht, uintptr_t e) +{ + return (void *)((e & ~ht->common_mask) | ht->common_bits); +} + +static inline uintptr_t make_hval(const struct htable *ht, + const void *p, uintptr_t bits) +{ + return ((uintptr_t)p & ~ht->common_mask) | bits; +} + +static inline bool entry_is_valid(uintptr_t e) +{ + return e > HTABLE_DELETED; +} + +static inline uintptr_t get_hash_ptr_bits(const struct htable *ht, + size_t hash) +{ + /* Shuffling the extra bits (as specified in mask) down the + * end is quite expensive. But the lower bits are redundant, so + * we fold the value first. */ + return (hash ^ (hash >> ht->bits)) + & ht->common_mask & ~ht->perfect_bit; +} + +static void htable_init(struct htable *ht, + size_t (*rehash)(const void *elem, void *priv), + void *priv) +{ + struct htable empty = HTABLE_INITIALIZER(empty, NULL, NULL); + *ht = empty; + ht->rehash = rehash; + ht->priv = priv; + ht->table = &ht->perfect_bit; +} + +static void htable_clear(struct htable *ht, + void (*free_cb) (void *entry, void *ctx), + void *ctx) +{ + size_t i; + + if (ht->table != &ht->perfect_bit) { + if (free_cb) { + for (i = 0; i < (size_t)1 << ht->bits; ++i) { + if (entry_is_valid(ht->table[i])) + free_cb(get_raw_ptr(ht, ht->table[i]), + ctx); + } + } + + free((void *)ht->table); + } + + htable_init(ht, ht->rehash, ht->priv); +} + +size_t shl_htable_this_or_next(struct shl_htable *htable, size_t i) +{ + struct htable *ht = (void*)&htable->htable; + + if (ht->table != &ht->perfect_bit) + for ( ; i < (size_t)1 << ht->bits; ++i) + if (entry_is_valid(ht->table[i])) + return i; + + return SIZE_MAX; +} + +void *shl_htable_get_entry(struct shl_htable *htable, size_t i) +{ + struct htable *ht = (void*)&htable->htable; + + if (i < (size_t)1 << ht->bits) + if (entry_is_valid(ht->table[i])) + return get_raw_ptr(ht, ht->table[i]); + + return NULL; +} + +static void htable_visit(struct htable *ht, + void (*visit_cb) (void *elem, void *ctx), + void *ctx) +{ + size_t i; + + if (visit_cb && ht->table != &ht->perfect_bit) { + for (i = 0; i < (size_t)1 << ht->bits; ++i) { + if (entry_is_valid(ht->table[i])) + visit_cb(get_raw_ptr(ht, ht->table[i]), ctx); + } + } +} + +static size_t hash_bucket(const struct htable *ht, size_t h) +{ + return h & ((1 << ht->bits)-1); +} + +static void *htable_val(const struct htable *ht, + struct htable_iter *i, size_t hash, uintptr_t perfect) +{ + uintptr_t h2 = get_hash_ptr_bits(ht, hash) | perfect; + + while (ht->table[i->off]) { + if (ht->table[i->off] != HTABLE_DELETED) { + if (get_extra_ptr_bits(ht, ht->table[i->off]) == h2) + return get_raw_ptr(ht, ht->table[i->off]); + } + i->off = (i->off + 1) & ((1 << ht->bits)-1); + h2 &= ~perfect; + } + return NULL; +} + +static void *htable_firstval(const struct htable *ht, + struct htable_iter *i, size_t hash) +{ + i->off = hash_bucket(ht, hash); + return htable_val(ht, i, hash, ht->perfect_bit); +} + +static void *htable_nextval(const struct htable *ht, + struct htable_iter *i, size_t hash) +{ + i->off = (i->off + 1) & ((1 << ht->bits)-1); + return htable_val(ht, i, hash, 0); +} + +/* This does not expand the hash table, that's up to caller. */ +static void ht_add(struct htable *ht, const void *new, size_t h) +{ + size_t i; + uintptr_t perfect = ht->perfect_bit; + + i = hash_bucket(ht, h); + + while (entry_is_valid(ht->table[i])) { + perfect = 0; + i = (i + 1) & ((1 << ht->bits)-1); + } + ht->table[i] = make_hval(ht, new, get_hash_ptr_bits(ht, h)|perfect); +} + +static COLD bool double_table(struct htable *ht) +{ + unsigned int i; + size_t oldnum = (size_t)1 << ht->bits; + uintptr_t *oldtable, e; + + oldtable = ht->table; + ht->table = calloc(1 << (ht->bits+1), sizeof(size_t)); + if (!ht->table) { + ht->table = oldtable; + return false; + } + ht->bits++; + ht->max = ((size_t)3 << ht->bits) / 4; + ht->max_with_deleted = ((size_t)9 << ht->bits) / 10; + + /* If we lost our "perfect bit", get it back now. */ + if (!ht->perfect_bit && ht->common_mask) { + for (i = 0; i < sizeof(ht->common_mask) * CHAR_BIT; i++) { + if (ht->common_mask & ((size_t)1 << i)) { + ht->perfect_bit = (size_t)1 << i; + break; + } + } + } + + if (oldtable != &ht->perfect_bit) { + for (i = 0; i < oldnum; i++) { + if (entry_is_valid(e = oldtable[i])) { + void *p = get_raw_ptr(ht, e); + ht_add(ht, p, ht->rehash(p, ht->priv)); + } + } + free(oldtable); + } + ht->deleted = 0; + return true; +} + +static COLD void rehash_table(struct htable *ht) +{ + size_t start, i; + uintptr_t e; + + /* Beware wrap cases: we need to start from first empty bucket. */ + for (start = 0; ht->table[start]; start++); + + for (i = 0; i < (size_t)1 << ht->bits; i++) { + size_t h = (i + start) & ((1 << ht->bits)-1); + e = ht->table[h]; + if (!e) + continue; + if (e == HTABLE_DELETED) + ht->table[h] = 0; + else if (!(e & ht->perfect_bit)) { + void *p = get_raw_ptr(ht, e); + ht->table[h] = 0; + ht_add(ht, p, ht->rehash(p, ht->priv)); + } + } + ht->deleted = 0; +} + +/* We stole some bits, now we need to put them back... */ +static COLD void update_common(struct htable *ht, const void *p) +{ + unsigned int i; + uintptr_t maskdiff, bitsdiff; + + if (ht->elems == 0) { + /* Always reveal one bit of the pointer in the bucket, + * so it's not zero or HTABLE_DELETED (1), even if + * hash happens to be 0. Assumes (void *)1 is not a + * valid pointer. */ + for (i = sizeof(uintptr_t)*CHAR_BIT - 1; i > 0; i--) { + if ((uintptr_t)p & ((uintptr_t)1 << i)) + break; + } + + ht->common_mask = ~((uintptr_t)1 << i); + ht->common_bits = ((uintptr_t)p & ht->common_mask); + ht->perfect_bit = 1; + return; + } + + /* Find bits which are unequal to old common set. */ + maskdiff = ht->common_bits ^ ((uintptr_t)p & ht->common_mask); + + /* These are the bits which go there in existing entries. */ + bitsdiff = ht->common_bits & maskdiff; + + for (i = 0; i < (size_t)1 << ht->bits; i++) { + if (!entry_is_valid(ht->table[i])) + continue; + /* Clear the bits no longer in the mask, set them as + * expected. */ + ht->table[i] &= ~maskdiff; + ht->table[i] |= bitsdiff; + } + + /* Take away those bits from our mask, bits and perfect bit. */ + ht->common_mask &= ~maskdiff; + ht->common_bits &= ~maskdiff; + ht->perfect_bit &= ~maskdiff; +} + +static bool htable_add(struct htable *ht, size_t hash, const void *p) +{ + if (ht->elems+1 > ht->max && !double_table(ht)) + return false; + if (ht->elems+1 + ht->deleted > ht->max_with_deleted) + rehash_table(ht); + assert(p); + if (((uintptr_t)p & ht->common_mask) != ht->common_bits) + update_common(ht, p); + + ht_add(ht, p, hash); + ht->elems++; + return true; +} + +static void htable_delval(struct htable *ht, struct htable_iter *i) +{ + assert(i->off < (size_t)1 << ht->bits); + assert(entry_is_valid(ht->table[i->off])); + + ht->elems--; + ht->table[i->off] = HTABLE_DELETED; + ht->deleted++; +} + +/* + * Wrapper code to make it easier to use this hash-table as map. + */ + +void shl_htable_init(struct shl_htable *htable, + bool (*compare) (const void *a, const void *b), + size_t (*rehash)(const void *elem, void *priv), + void *priv) +{ + struct htable *ht = (void*)&htable->htable; + + htable->compare = compare; + htable_init(ht, rehash, priv); +} + +void shl_htable_clear(struct shl_htable *htable, + void (*free_cb) (void *elem, void *ctx), + void *ctx) +{ + struct htable *ht = (void*)&htable->htable; + + htable_clear(ht, free_cb, ctx); +} + +void shl_htable_visit(struct shl_htable *htable, + void (*visit_cb) (void *elem, void *ctx), + void *ctx) +{ + struct htable *ht = (void*)&htable->htable; + + htable_visit(ht, visit_cb, ctx); +} + +bool shl_htable_lookup(struct shl_htable *htable, const void *obj, size_t hash, + void **out) +{ + struct htable *ht = (void*)&htable->htable; + struct htable_iter i; + void *c; + + for (c = htable_firstval(ht, &i, hash); + c; + c = htable_nextval(ht, &i, hash)) { + if (htable->compare(obj, c)) { + if (out) + *out = c; + return true; + } + } + + return false; +} + +int shl_htable_insert(struct shl_htable *htable, const void *obj, size_t hash) +{ + struct htable *ht = (void*)&htable->htable; + bool b; + + b = htable_add(ht, hash, (void*)obj); + return b ? 0 : -ENOMEM; +} + +bool shl_htable_remove(struct shl_htable *htable, const void *obj, size_t hash, + void **out) +{ + struct htable *ht = (void*)&htable->htable; + struct htable_iter i; + void *c; + + for (c = htable_firstval(ht, &i, hash); + c; + c = htable_nextval(ht, &i, hash)) { + if (htable->compare(obj, c)) { + if (out) + *out = c; + htable_delval(ht, &i); + return true; + } + } + + return false; +} + +/* + * Helpers + */ + +bool shl_htable_compare_uint(const void *a, const void *b) +{ + return *(const unsigned int*)a == *(const unsigned int*)b; +} + +size_t shl_htable_rehash_uint(const void *elem, void *priv) +{ + return (size_t)*(const unsigned int*)elem; +} + +bool shl_htable_compare_ulong(const void *a, const void *b) +{ + return *(const unsigned long*)a == *(const unsigned long*)b; +} + +size_t shl_htable_rehash_ulong(const void *elem, void *priv) +{ + return (size_t)*(const unsigned long*)elem; +} + +bool shl_htable_compare_str(const void *a, const void *b) +{ + if (!*(char**)a || !*(char**)b) + return *(char**)a == *(char**)b; + else + return !strcmp(*(char**)a, *(char**)b); +} + +/* DJB's hash function */ +size_t shl_htable_rehash_str(const void *elem, void *priv) +{ + const char *str = *(char**)elem; + size_t hash = 5381; + + for ( ; str && *str; ++str) + hash = (hash << 5) + hash + (size_t)*str; + + return hash; +} diff --git a/src/shl_htable.h b/src/shl_htable.h new file mode 100644 index 0000000..05f412a --- /dev/null +++ b/src/shl_htable.h @@ -0,0 +1,304 @@ +/* + * SHL - Dynamic hash-table + * + * Copyright (c) 2010-2013 David Herrmann + * Licensed under LGPLv2+ - see LICENSE_htable file for details + */ + +/* + * Dynamic hash-table + * Implementation of a self-resizing hashtable to store arbitrary objects. + * Entries are not allocated by the table itself but are user-allocated. A + * single entry can be stored multiple times in the hashtable. No + * maintenance-members need to be embedded in user-allocated objects. However, + * the key (and optionally the hash) must be stored in the objects. + * + * Uses internally the htable from CCAN. See LICENSE_htable. + */ + +#ifndef SHL_HTABLE_H +#define SHL_HTABLE_H + +#include +#include +#include +#include +#include + +/* miscellaneous */ + +#define shl_htable_offsetof(pointer, type, member) ({ \ + const typeof(((type*)0)->member) *__ptr = (pointer); \ + (type*)(((char*)__ptr) - offsetof(type, member)); \ + }) + +/* htable */ + +struct shl_htable_int { + size_t (*rehash)(const void *elem, void *priv); + void *priv; + unsigned int bits; + size_t elems, deleted, max, max_with_deleted; + /* These are the bits which are the same in all pointers. */ + uintptr_t common_mask, common_bits; + uintptr_t perfect_bit; + uintptr_t *table; +}; + +struct shl_htable { + bool (*compare) (const void *a, const void *b); + struct shl_htable_int htable; +}; + +#define SHL_HTABLE_INIT(_obj, _compare, _rehash, _priv) \ + { \ + .compare = (_compare), \ + .htable = { \ + .rehash = (_rehash), \ + .priv = (_priv), \ + .bits = 0, \ + .elems = 0, \ + .deleted = 0, \ + .max = 0, \ + .max_with_deleted = 0, \ + .common_mask = -1, \ + .common_bits = 0, \ + .perfect_bit = 0, \ + .table = &(_obj).htable.perfect_bit \ + } \ + } + +void shl_htable_init(struct shl_htable *htable, + bool (*compare) (const void *a, const void *b), + size_t (*rehash)(const void *elem, void *priv), + void *priv); +void shl_htable_clear(struct shl_htable *htable, + void (*free_cb) (void *elem, void *ctx), + void *ctx); +void shl_htable_visit(struct shl_htable *htable, + void (*visit_cb) (void *elem, void *ctx), + void *ctx); +bool shl_htable_lookup(struct shl_htable *htable, const void *obj, size_t hash, + void **out); +int shl_htable_insert(struct shl_htable *htable, const void *obj, size_t hash); +bool shl_htable_remove(struct shl_htable *htable, const void *obj, size_t hash, + void **out); + +size_t shl_htable_this_or_next(struct shl_htable *htable, size_t i); +void *shl_htable_get_entry(struct shl_htable *htable, size_t i); + +#define SHL_HTABLE_FOREACH(_iter, _ht) for ( \ + size_t htable__i = shl_htable_this_or_next((_ht), 0); \ + (_iter = shl_htable_get_entry((_ht), htable__i)); \ + htable__i = shl_htable_this_or_next((_ht), htable__i + 1) \ + ) + +#define SHL_HTABLE_FOREACH_MACRO(_iter, _ht, _accessor) for ( \ + size_t htable__i = shl_htable_this_or_next((_ht), 0); \ + (_iter = shl_htable_get_entry((_ht), htable__i), \ + _iter = _iter ? _accessor((void*)_iter) : NULL); \ + htable__i = shl_htable_this_or_next((_ht), htable__i + 1) \ + ) + +#define SHL_HTABLE_FIRST(_ht) \ + shl_htable_get_entry((_ht), shl_htable_this_or_next((_ht), 0)) + +#define SHL_HTABLE_FIRST_MACRO(_ht, _accessor) ({ \ + void *htable__i = shl_htable_get_entry((_ht), \ + shl_htable_this_or_next((_ht), 0)); \ + htable__i ? _accessor(htable__i) : NULL; }) + +/* uint htables */ + +#if SIZE_MAX < UINT_MAX +# error "'size_t' is smaller than 'unsigned int'" +#endif + +bool shl_htable_compare_uint(const void *a, const void *b); +size_t shl_htable_rehash_uint(const void *elem, void *priv); + +#define SHL_HTABLE_INIT_UINT(_obj) \ + SHL_HTABLE_INIT((_obj), shl_htable_compare_uint, \ + shl_htable_rehash_uint, \ + NULL) + +static inline void shl_htable_init_uint(struct shl_htable *htable) +{ + shl_htable_init(htable, shl_htable_compare_uint, + shl_htable_rehash_uint, NULL); +} + +static inline void shl_htable_clear_uint(struct shl_htable *htable, + void (*cb) (unsigned int *elem, + void *ctx), + void *ctx) +{ + shl_htable_clear(htable, (void (*) (void*, void*))cb, ctx); +} + +static inline void shl_htable_visit_uint(struct shl_htable *htable, + void (*cb) (unsigned int *elem, + void *ctx), + void *ctx) +{ + shl_htable_visit(htable, (void (*) (void*, void*))cb, ctx); +} + +static inline bool shl_htable_lookup_uint(struct shl_htable *htable, + unsigned int key, + unsigned int **out) +{ + return shl_htable_lookup(htable, (const void*)&key, (size_t)key, + (void**)out); +} + +static inline int shl_htable_insert_uint(struct shl_htable *htable, + const unsigned int *key) +{ + return shl_htable_insert(htable, (const void*)key, (size_t)*key); +} + +static inline bool shl_htable_remove_uint(struct shl_htable *htable, + unsigned int key, + unsigned int **out) +{ + return shl_htable_remove(htable, (const void*)&key, (size_t)key, + (void**)out); +} + +/* ulong htables */ + +#if SIZE_MAX < ULONG_MAX +# error "'size_t' is smaller than 'unsigned long'" +#endif + +bool shl_htable_compare_ulong(const void *a, const void *b); +size_t shl_htable_rehash_ulong(const void *elem, void *priv); + +#define SHL_HTABLE_INIT_ULONG(_obj) \ + SHL_HTABLE_INIT((_obj), shl_htable_compare_ulong, \ + shl_htable_rehash_ulong, \ + NULL) + +static inline void shl_htable_init_ulong(struct shl_htable *htable) +{ + shl_htable_init(htable, shl_htable_compare_ulong, + shl_htable_rehash_ulong, NULL); +} + +static inline void shl_htable_clear_ulong(struct shl_htable *htable, + void (*cb) (unsigned long *elem, + void *ctx), + void *ctx) +{ + shl_htable_clear(htable, (void (*) (void*, void*))cb, ctx); +} + +static inline void shl_htable_visit_ulong(struct shl_htable *htable, + void (*cb) (unsigned long *elem, + void *ctx), + void *ctx) +{ + shl_htable_visit(htable, (void (*) (void*, void*))cb, ctx); +} + +static inline bool shl_htable_lookup_ulong(struct shl_htable *htable, + unsigned long key, + unsigned long **out) +{ + return shl_htable_lookup(htable, (const void*)&key, (size_t)key, + (void**)out); +} + +static inline int shl_htable_insert_ulong(struct shl_htable *htable, + const unsigned long *key) +{ + return shl_htable_insert(htable, (const void*)key, (size_t)*key); +} + +static inline bool shl_htable_remove_ulong(struct shl_htable *htable, + unsigned long key, + unsigned long **out) +{ + return shl_htable_remove(htable, (const void*)&key, (size_t)key, + (void**)out); +} + +/* string htables */ + +bool shl_htable_compare_str(const void *a, const void *b); +size_t shl_htable_rehash_str(const void *elem, void *priv); + +#define SHL_HTABLE_INIT_STR(_obj) \ + SHL_HTABLE_INIT((_obj), shl_htable_compare_str, \ + shl_htable_rehash_str, \ + NULL) + +static inline void shl_htable_init_str(struct shl_htable *htable) +{ + shl_htable_init(htable, shl_htable_compare_str, + shl_htable_rehash_str, NULL); +} + +static inline void shl_htable_clear_str(struct shl_htable *htable, + void (*cb) (char **elem, + void *ctx), + void *ctx) +{ + shl_htable_clear(htable, (void (*) (void*, void*))cb, ctx); +} + +static inline void shl_htable_visit_str(struct shl_htable *htable, + void (*cb) (char **elem, + void *ctx), + void *ctx) +{ + shl_htable_visit(htable, (void (*) (void*, void*))cb, ctx); +} + +static inline size_t shl_htable_hash_str(struct shl_htable *htable, + const char *str, size_t *hash) +{ + size_t h; + + if (hash && *hash) { + h = *hash; + } else { + h = htable->htable.rehash((const void*)&str, NULL); + if (hash) + *hash = h; + } + + return h; +} + +static inline bool shl_htable_lookup_str(struct shl_htable *htable, + const char *str, size_t *hash, + char ***out) +{ + size_t h; + + h = shl_htable_hash_str(htable, str, hash); + return shl_htable_lookup(htable, (const void*)&str, h, (void**)out); +} + +static inline int shl_htable_insert_str(struct shl_htable *htable, + char **str, size_t *hash) +{ + size_t h; + + h = shl_htable_hash_str(htable, *str, hash); + return shl_htable_insert(htable, (const void*)str, h); +} + +static inline bool shl_htable_remove_str(struct shl_htable *htable, + const char *str, size_t *hash, + char ***out) +{ + size_t h; + + h = shl_htable_hash_str(htable, str, hash); + return shl_htable_remove(htable, (const void*)&str, h, (void **)out); +} + +#endif /* SHL_HTABLE_H */ diff --git a/src/shl_log.c b/src/shl_log.c new file mode 100644 index 0000000..723fb53 --- /dev/null +++ b/src/shl_log.c @@ -0,0 +1,239 @@ +/* + * SHL - Log/Debug Interface + * + * Copyright (c) 2010-2013 David Herrmann + * Dedicated to the Public Domain + */ + +#include +#include +#include +#include +#include +#include +#include +#include "shl_log.h" + +/* + * Locking + * Dummies to implement locking. If we ever want lock-protected logging, these + * need to be provided by the user. + */ + +static inline void log_lock() +{ +} + +static inline void log_unlock() +{ +} + +/* + * Time Management + * We print seconds and microseconds since application start for each + * log-message in case log_init_time() has been called. + */ + +static struct timeval log__ftime; + +static bool log__have_time(void) +{ + return !(log__ftime.tv_sec == 0 && log__ftime.tv_usec == 0); +} + +void log_init_time(void) +{ + if (!log__have_time()) + gettimeofday(&log__ftime, NULL); +} + +static void log__time(long long *sec, long long *usec) +{ + struct timeval t; + + /* In case this is called in parallel to log_init_time(), we need to + * catch negative time-diffs. Other than that, this can be called + * unlocked. */ + + gettimeofday(&t, NULL); + *sec = t.tv_sec - log__ftime.tv_sec; + *usec = (long long)t.tv_usec - (long long)log__ftime.tv_usec; + if (*usec < 0) { + *sec -= 1; + if (*sec < 0) + *sec = 0; + *usec = 1000000 + *usec; + } +} + +/* + * Default Values + * Several logging-parameters may be omitted by applications. To provide sane + * default values we provide constants here. + * + * LOG_SUBSYSTEM: By default no subsystem is specified + */ + +const char *LOG_SUBSYSTEM = NULL; + +/* + * Max Severity + * Messages with severities between log_max_sev and LOG_SEV_NUM (exclusive) + * are not logged, but discarded. + */ + +unsigned int log_max_sev = LOG_NOTICE; + +/* + * Forward declaration so we can use the locked-versions in other functions + * here. Be careful to avoid deadlocks, though. + * Also set default log-subsystem to "log" for all logging inside this API. + */ + +static void log__submit(const char *file, + int line, + const char *func, + const char *subs, + unsigned int sev, + const char *format, + va_list args); + +#define LOG_SUBSYSTEM "log" + +/* + * Basic logger + * The log__submit function writes the message into the current log-target. It + * must be called with log__mutex locked. + * By default the current time elapsed since the first message was logged is + * prepended to the message. file, line and func information are appended to the + * message if sev == LOG_DEBUG. + * The subsystem, if not NULL, is prepended as "SUBS: " to the message and a + * newline is always appended by default. Multiline-messages are not allowed. + */ + +static const char *log__sev2str[LOG_SEV_NUM] = { + [LOG_DEBUG] = "DEBUG", + [LOG_INFO] = "INFO", + [LOG_NOTICE] = "NOTICE", + [LOG_WARNING] = "WARNING", + [LOG_ERROR] = "ERROR", + [LOG_CRITICAL] = "CRITICAL", + [LOG_ALERT] = "ALERT", + [LOG_FATAL] = "FATAL", +}; + +static void log__submit(const char *file, + int line, + const char *func, + const char *subs, + unsigned int sev, + const char *format, + va_list args) +{ + int saved_errno = errno; + const char *prefix = NULL; + FILE *out; + long long sec, usec; + + out = stderr; + log__time(&sec, &usec); + + if (sev < LOG_SEV_NUM && sev > log_max_sev) + return; + + if (sev < LOG_SEV_NUM) + prefix = log__sev2str[sev]; + + if (prefix) { + if (subs) { + if (log__have_time()) + fprintf(out, "[%.4lld.%.6lld] %s: %s: ", + sec, usec, prefix, subs); + else + fprintf(out, "%s: %s: ", prefix, subs); + } else { + if (log__have_time()) + fprintf(out, "[%.4lld.%.6lld] %s: ", + sec, usec, prefix); + else + fprintf(out, "%s: ", prefix); + } + } else { + if (subs) { + if (log__have_time()) + fprintf(out, "[%.4lld.%.6lld] %s: ", + sec, usec, subs); + else + fprintf(out, "%s: ", subs); + } else { + if (log__have_time()) + fprintf(out, "[%.4lld.%.6lld] ", sec, usec); + } + } + + errno = saved_errno; + vfprintf(out, format, args); + + if (sev == LOG_DEBUG || sev <= LOG_WARNING) { + if (!func) + func = ""; + if (!file) + file = ""; + if (line < 0) + line = 0; + fprintf(out, " (%s() in %s:%d)\n", func, file, line); + } else { + fprintf(out, "\n"); + } +} + +void log_submit(const char *file, + int line, + const char *func, + const char *subs, + unsigned int sev, + const char *format, + va_list args) +{ + int saved_errno = errno; + + log_lock(); + errno = saved_errno; + log__submit(file, line, func, subs, sev, format, args); + log_unlock(); + + errno = saved_errno; +} + +void log_format(const char *file, + int line, + const char *func, + const char *subs, + unsigned int sev, + const char *format, + ...) +{ + int saved_errno = errno; + va_list list; + + va_start(list, format); + log_lock(); + errno = saved_errno; + log__submit(file, line, func, subs, sev, format, list); + log_unlock(); + va_end(list); + + errno = saved_errno; +} + +void log_llog(void *data, + const char *file, + int line, + const char *func, + const char *subs, + unsigned int sev, + const char *format, + va_list args) +{ + log_submit(file, line, func, subs, sev, format, args); +} diff --git a/src/shl_log.h b/src/shl_log.h new file mode 100644 index 0000000..ffeb1e4 --- /dev/null +++ b/src/shl_log.h @@ -0,0 +1,204 @@ +/* + * SHL - Log/Debug Interface + * + * Copyright (c) 2010-2013 David Herrmann + * Dedicated to the Public Domain + */ + +/* + * Log/Debug Interface + * This interface provides basic logging to stderr. + * + * Define BUILD_ENABLE_DEBUG before including this header to enable + * debug-messages for this file. + */ + +#ifndef SHL_LOG_H +#define SHL_LOG_H + +#include +#include +#include +#include + +enum log_severity { + LOG_FATAL = 0, + LOG_ALERT = 1, + LOG_CRITICAL = 2, + LOG_ERROR = 3, + LOG_WARNING = 4, + LOG_NOTICE = 5, + LOG_INFO = 6, + LOG_DEBUG = 7, + LOG_SEV_NUM, +}; + +/* + * Max Severity + * Messages with severities between log_max_sev and LOG_SEV_NUM (exclusive) + * are not logged, but discarded. + * Default: LOG_NOTICE + */ + +extern unsigned int log_max_sev; + +/* + * Timestamping + * Call this to initialize timestamps and cause all log-messages to be prefixed + * with a timestamp. If not called, no timestamps are added. + */ + +void log_init_time(void); + +/* + * Log-Functions + * These functions pass a log-message to the log-subsystem. Handy helpers are + * provided below. You almost never use these directly. + * + * log_submit: + * Submit the message to the log-subsystem. This is the backend of all other + * loggers. + * + * log_format: + * Same as log_submit but first converts the arguments into a va_list object. + * + * log_llog: + * Same as log_submit but used as connection to llog. + * + * log_dummyf: + * Dummy logger used for gcc var-arg validation. + */ + +__attribute__((format(printf, 6, 0))) +void log_submit(const char *file, + int line, + const char *func, + const char *subs, + unsigned int sev, + const char *format, + va_list args); + +__attribute__((format(printf, 6, 7))) +void log_format(const char *file, + int line, + const char *func, + const char *subs, + unsigned int sev, + const char *format, + ...); + +__attribute__((format(printf, 7, 0))) +void log_llog(void *data, + const char *file, + int line, + const char *func, + const char *subs, + unsigned int sev, + const char *format, + va_list args); + +static inline __attribute__((format(printf, 2, 3))) +void log_dummyf(unsigned int sev, const char *format, ...) +{ +} + +/* + * Default values + * All helpers automatically pick-up the file, line, func and subsystem + * parameters for a log-message. file, line and func are generated with + * __FILE__, __LINE__ and __func__ and should almost never be replaced. + * The subsystem is by default an empty string. To overwrite this, add this + * line to the top of your source file: + * #define LOG_SUBSYSTEM "mysubsystem" + * Then all following log-messages will use this string as subsystem. You can + * define it before or after including this header. + * + * If you want to change one of these, you need to directly use log_submit and + * log_format. If you want the defaults for file, line and func you can use: + * log_format(LOG_DEFAULT_BASE, subsys, sev, format, ...); + * If you want all default values, use: + * log_format(LOG_DEFAULT, sev, format, ...); + * + * If you want to change a single value, this is the default line that is used + * internally. Adjust it to your needs: + * log_format(__FILE__, __LINE__, __func__, LOG_SUBSYSTEM, LOG_ERROR, + * "your format string: %s %d", "some args", 5, ...); + * + * log_printf is the same as log_format(LOG_DEFAULT, sev, format, ...) and is + * the most basic wrapper that you can use. + */ + +#ifndef LOG_SUBSYSTEM +extern const char *LOG_SUBSYSTEM; +#endif + +#define LOG_DEFAULT_BASE __FILE__, __LINE__, __func__ +#define LOG_DEFAULT LOG_DEFAULT_BASE, LOG_SUBSYSTEM + +#define log_printf(sev, format, ...) \ + log_format(LOG_DEFAULT, (sev), (format), ##__VA_ARGS__) + +/* + * Helpers + * These pick up all the default values and submit the message to the + * log-subsystem. The log_debug() function produces zero-code if + * BUILD_ENABLE_DEBUG is not defined. Therefore, it can be heavily used for + * debugging and will not have any side-effects. + * Even if disabled, parameters are evaluated! So it only produces zero code + * if there are no side-effects and the compiler can optimized it away. + */ + +#ifdef BUILD_ENABLE_DEBUG + #define log_debug(format, ...) \ + log_printf(LOG_DEBUG, (format), ##__VA_ARGS__) +#else + #define log_debug(format, ...) \ + log_dummyf(LOG_DEBUG, (format), ##__VA_ARGS__) +#endif + +#define log_info(format, ...) \ + log_printf(LOG_INFO, (format), ##__VA_ARGS__) +#define log_notice(format, ...) \ + log_printf(LOG_NOTICE, (format), ##__VA_ARGS__) +#define log_warning(format, ...) \ + log_printf(LOG_WARNING, (format), ##__VA_ARGS__) +#define log_error(format, ...) \ + log_printf(LOG_ERROR, (format), ##__VA_ARGS__) +#define log_critical(format, ...) \ + log_printf(LOG_CRITICAL, (format), ##__VA_ARGS__) +#define log_alert(format, ...) \ + log_printf(LOG_ALERT, (format), ##__VA_ARGS__) +#define log_fatal(format, ...) \ + log_printf(LOG_FATAL, (format), ##__VA_ARGS__) + +#define log_EINVAL() \ + (log_error("invalid arguments"), -EINVAL) +#define log_vEINVAL() \ + ((void)log_EINVAL()) + +#define log_EFAULT() \ + (log_error("internal operation failed"), -EFAULT) +#define log_vEFAULT() \ + ((void)log_EFAULT()) + +#define log_ENOMEM() \ + (log_error("out of memory"), -ENOMEM) +#define log_vENOMEM() \ + ((void)log_ENOMEM()) + +#define log_EPIPE() \ + (log_error("fd closed unexpectedly"), -EPIPE) +#define log_vEPIPE() \ + ((void)log_EPIPE()) + +#define log_ERRNO() \ + (log_error("syscall failed (%d): %m", errno), -errno) +#define log_vERRNO() \ + ((void)log_ERRNO()) + +#define log_ERR(_r) \ + (errno = -(_r), log_error("syscall failed (%d): %m", (_r)), (_r)) +#define log_vERR(_r) \ + ((void)log_ERR(_r)) + +#endif /* SHL_LOG_H */ diff --git a/src/shl_macro.h b/src/shl_macro.h new file mode 100644 index 0000000..ae855df --- /dev/null +++ b/src/shl_macro.h @@ -0,0 +1,219 @@ +/* + * SHL - Macros + * + * Copyright (c) 2011-2013 David Herrmann + * Dedicated to the Public Domain + */ + +/* + * Macros + */ + +#ifndef SHL_MACRO_H +#define SHL_MACRO_H + +#include +#include +#include +#include +#include +#include + +/* sanity checks required for some macros */ +#if __SIZEOF_POINTER__ != 4 && __SIZEOF_POINTER__ != 8 +#error "Pointer size is neither 4 nor 8 bytes" +#endif + +/* gcc attributes; look them up for more information */ +#define _shl_printf_(_a, _b) __attribute__((__format__(printf, _a, _b))) +#define _shl_alloc_(...) __attribute__((__alloc_size__(__VA_ARGS__))) +#define _shl_sentinel_ __attribute__((__sentinel__)) +#define _shl_noreturn_ __attribute__((__noreturn__)) +#define _shl_unused_ __attribute__((__unused__)) +#define _shl_pure_ __attribute__((__pure__)) +#define _shl_const_ __attribute__((__const__)) +#define _shl_deprecated_ __attribute__((__deprecated__)) +#define _shl_packed_ __attribute__((__packed__)) +#define _shl_malloc_ __attribute__((__malloc__)) +#define _shl_weak_ __attribute__((__weak__)) +#define _shl_likely_(_val) (__builtin_expect(!!(_val), 1)) +#define _shl_unlikely_(_val) (__builtin_expect(!!(_val), 0)) +#define _shl_public_ __attribute__((__visibility__("default"))) +#define _shl_hidden_ __attribute__((__visibility__("hidden"))) +#define _shl_weakref_(_val) __attribute__((__weakref__(#_val))) +#define _shl_cleanup_(_val) __attribute__((__cleanup__(_val))) + +/* 2-level stringify helper */ +#define SHL__STRINGIFY(_val) #_val +#define SHL_STRINGIFY(_val) SHL__STRINGIFY(_val) + +/* 2-level concatenate helper */ +#define SHL__CONCATENATE(_a, _b) _a ## _b +#define SHL_CONCATENATE(_a, _b) SHL__CONCATENATE(_a, _b) + +/* unique identifier with prefix */ +#define SHL_UNIQUE(_prefix) SHL_CONCATENATE(_prefix, __COUNTER__) + +/* array element count */ +#define SHL_ARRAY_LENGTH(_array) (sizeof(_array)/sizeof(*(_array))) + +/* get parent pointer by container-type, member and member-pointer */ +#define shl_container_of(_ptr, _type, _member) \ + ({ \ + const typeof( ((_type *)0)->_member ) *__mptr = (_ptr); \ + (_type *)( (char *)__mptr - offsetof(_type, _member) ); \ + }) + +/* return maximum of two values and do strict type checking */ +#define shl_max(_a, _b) \ + ({ \ + typeof(_a) __a = (_a); \ + typeof(_b) __b = (_b); \ + (void) (&__a == &__b); \ + __a > __b ? __a : __b; \ + }) + +/* same as shl_max() but perform explicit cast beforehand */ +#define shl_max_t(_type, _a, _b) \ + ({ \ + _type __a = (_type)(_a); \ + _type __b = (_type)(_b); \ + __a > __b ? __a : __b; \ + }) + +/* return minimum of two values and do strict type checking */ +#define shl_min(_a, _b) \ + ({ \ + typeof(_a) __a = (_a); \ + typeof(_b) __b = (_b); \ + (void) (&__a == &__b); \ + __a < __b ? __a : __b; \ + }) + +/* same as shl_min() but perform explicit cast beforehand */ +#define shl_min_t(_type, _a, _b) \ + ({ \ + _type __a = (_type)(_a); \ + _type __b = (_type)(_b); \ + __a < __b ? __a : __b; \ + }) + +/* clamp value between low and high barriers */ +#define shl_clamp(_val, _low, _high) \ + ({ \ + typeof(_val) __v = (_val); \ + typeof(_low) __l = (_low); \ + typeof(_high) __h = (_high); \ + (void) (&__v == &__l); \ + (void) (&__v == &__h); \ + ((__v > __h) ? __h : ((__v < __l) ? __l : __v)); \ + }) + +/* align to next higher power-of-2 (except for: 0 => 0, overflow => 0) */ +static inline size_t SHL_ALIGN_POWER2(size_t u) +{ + return 1ULL << ((sizeof(u) * 8ULL) - __builtin_clzll(u - 1ULL)); +} + +/* zero memory or type */ +#define shl_memzero(_ptr, _size) (memset((_ptr), 0, (_size))) +#define shl_zero(_ptr) (shl_memzero(&(_ptr), sizeof(_ptr))) + +/* ptr <=> uint casts */ +#define SHL_PTR_TO_TYPE(_type, _ptr) ((_type)((uintptr_t)(_ptr))) +#define SHL_TYPE_TO_PTR(_type, _int) ((void*)((uintptr_t)(_int))) +#define SHL_PTR_TO_INT(_ptr) SHL_PTR_TO_TYPE(int, (_ptr)) +#define SHL_INT_TO_PTR(_ptr) SHL_TYPE_TO_PTR(int, (_ptr)) +#define SHL_PTR_TO_UINT(_ptr) SHL_PTR_TO_TYPE(unsigned int, (_ptr)) +#define SHL_UINT_TO_PTR(_ptr) SHL_TYPE_TO_PTR(unsigned int, (_ptr)) +#define SHL_PTR_TO_LONG(_ptr) SHL_PTR_TO_TYPE(long, (_ptr)) +#define SHL_LONG_TO_PTR(_ptr) SHL_TYPE_TO_PTR(long, (_ptr)) +#define SHL_PTR_TO_ULONG(_ptr) SHL_PTR_TO_TYPE(unsigned long, (_ptr)) +#define SHL_ULONG_TO_PTR(_ptr) SHL_TYPE_TO_PTR(unsigned long, (_ptr)) +#define SHL_PTR_TO_S32(_ptr) SHL_PTR_TO_TYPE(int32_t, (_ptr)) +#define SHL_S32_TO_PTR(_ptr) SHL_TYPE_TO_PTR(int32_t, (_ptr)) +#define SHL_PTR_TO_U32(_ptr) SHL_PTR_TO_TYPE(uint32_t, (_ptr)) +#define SHL_U32_TO_PTR(_ptr) SHL_TYPE_TO_PTR(uint32_t, (_ptr)) +#define SHL_PTR_TO_S64(_ptr) SHL_PTR_TO_TYPE(int64_t, (_ptr)) +#define SHL_S64_TO_PTR(_ptr) SHL_TYPE_TO_PTR(int64_t, (_ptr)) +#define SHL_PTR_TO_U64(_ptr) SHL_PTR_TO_TYPE(uint64_t, (_ptr)) +#define SHL_U64_TO_PTR(_ptr) SHL_TYPE_TO_PTR(uint64_t, (_ptr)) + +/* compile-time assertions */ +#define shl_assert_cc(_expr) static_assert(_expr, #_expr) + +/* + * Safe Multiplications + * Multiplications are subject to overflows. These helpers guarantee that the + * multiplication can be done safely and return -ERANGE if not. + * + * Note: This is horribly slow for ull/uint64_t as we need a division to test + * for overflows. Take that into account when using these. For smaller integers, + * we can simply use an upcast-multiplication which gcc should be smart enough + * to optimize. + */ + +#define SHL__REAL_MULT(_max, _val, _factor) \ + ({ \ + (_factor == 0 || *(_val) <= (_max) / (_factor)) ? \ + ((*(_val) *= (_factor)), 0) : \ + -ERANGE; \ + }) + +#define SHL__UPCAST_MULT(_type, _max, _val, _factor) \ + ({ \ + _type v = *(_val) * (_type)(_factor); \ + (v <= (_max)) ? \ + ((*(_val) = v), 0) : \ + -ERANGE; \ + }) + +static inline int shl_mult_ull(unsigned long long *val, + unsigned long long factor) +{ + return SHL__REAL_MULT(ULLONG_MAX, val, factor); +} + +static inline int shl_mult_ul(unsigned long *val, unsigned long factor) +{ +#if ULONG_MAX < ULLONG_MAX + return SHL__UPCAST_MULT(unsigned long long, ULONG_MAX, val, factor); +#else + shl_assert_cc(sizeof(unsigned long) == sizeof(unsigned long long)); + return shl_mult_ull((unsigned long long*)val, factor); +#endif +} + +static inline int shl_mult_u(unsigned int *val, unsigned int factor) +{ +#if UINT_MAX < ULONG_MAX + return SHL__UPCAST_MULT(unsigned long, UINT_MAX, val, factor); +#elif UINT_MAX < ULLONG_MAX + return SHL__UPCAST_MULT(unsigned long long, UINT_MAX, val, factor); +#else + shl_assert_cc(sizeof(unsigned int) == sizeof(unsigned long long)); + return shl_mult_ull(val, factor); +#endif +} + +static inline int shl_mult_u64(uint64_t *val, uint64_t factor) +{ + return SHL__REAL_MULT(UINT64_MAX, val, factor); +} + +static inline int shl_mult_u32(uint32_t *val, uint32_t factor) +{ + return SHL__UPCAST_MULT(uint_fast64_t, UINT32_MAX, val, factor); +} + +static inline int shl_mult_u16(uint16_t *val, uint16_t factor) +{ + return SHL__UPCAST_MULT(uint_fast32_t, UINT16_MAX, val, factor); +} + +static inline int shl_mult_u8(uint8_t *val, uint8_t factor) +{ + return SHL__UPCAST_MULT(uint_fast16_t, UINT8_MAX, val, factor); +} + +#endif /* SHL_MACRO_H */ diff --git a/src/shl_util.c b/src/shl_util.c new file mode 100644 index 0000000..6a5db70 --- /dev/null +++ b/src/shl_util.c @@ -0,0 +1,323 @@ +/* + * SHL - Utility Helpers + * + * Copyright (c) 2011-2013 David Herrmann + * Dedicated to the Public Domain + */ + +/* + * Utility Helpers + */ + +#include +#include +#include +#include +#include +#include +#include +#include "shl_macro.h" +#include "shl_util.h" + +/* + * Strict atoi() + * These helpers implement a strict version of atoi() (or strtol()). They only + * parse digit/alpha characters. No whitespace or other characters are parsed. + * The unsigned-variants explicitly forbid leading +/- signs. Use the signed + * variants to allow these. + * Base-prefix parsing is only done if base=0 is requested. Otherwise, + * base-prefixes are forbidden. + * The input string must be ASCII compatbile (which includes UTF8). + * + * We also always check for overflows and return errors (but continue parsing!) + * so callers can catch it correctly. + * + * Additionally, we allow "length" parameters so strings do not necessarily have + * to be zero-terminated. We have wrappers which skip this by passing strlen(). + */ + +int shl_ctoi(char ch, unsigned int base) +{ + unsigned int v; + + switch (ch) { + case '0'...'9': + v = ch - '0'; + break; + case 'a'...'z': + v = ch - 'a' + 10; + break; + case 'A'...'Z': + v = ch - 'A' + 10; + break; + default: + return -EINVAL; + } + + if (v >= base) + return -EINVAL; + + return v; +} + +/* figure out base and skip prefix */ +static unsigned int shl__skip_base(const char **str, size_t *len) +{ + if (*len > 1) { + if ((*str)[0] == '0') { + if (shl_ctoi((*str)[1], 8) >= 0) { + *str += 1; + *len -= 1; + return 8; + } + } + } + + if (*len > 2) { + if ((*str)[0] == '0' && (*str)[1] == 'x') { + if (shl_ctoi((*str)[2], 16) >= 0) { + *str += 2; + *len -= 2; + return 16; + } + } + } + + return 10; +} + +int shl_atoi_ulln(const char *str, + size_t len, + unsigned int base, + const char **next, + unsigned long long *out) +{ + bool huge; + uint32_t val1; + unsigned long long val2; + size_t pos; + int r, c; + + /* We use u32 as storage first so we have fast mult-overflow checks. We + * cast up to "unsigned long long" once we exceed UINT32_MAX. Overflow + * checks will get pretty slow for non-power2 bases, though. */ + + huge = false; + val1 = 0; + val2 = 0; + r = 0; + + if (base > 36) { + if (next) + *next = str; + if (out) + *out = 0; + return -EINVAL; + } + + if (base == 0) + base = shl__skip_base(&str, &len); + + for (pos = 0; pos < len; ++pos) { + c = shl_ctoi(str[pos], base); + if (c < 0) + break; + + /* skip calculations on error */ + if (r < 0) + continue; + + if (!huge) { + val2 = val1; + r = shl_mult_u32(&val1, base); + if (r >= 0 && val1 + c >= val1) + val1 += c; + else + huge = true; + } + + if (huge) { + r = shl_mult_ull(&val2, base); + if (r >= 0 && val2 + c >= val2) + val2 += c; + } + } + + if (next) + *next = (char*)&str[pos]; + if (out) { + if (r < 0) + *out = ULLONG_MAX; + else if (huge) + *out = val2; + else + *out = val1; + } + + return r; +} + +int shl_atoi_uln(const char *str, + size_t len, + unsigned int base, + const char **next, + unsigned long *out) +{ + unsigned long long val; + int r; + + r = shl_atoi_ulln(str, len, base, next, &val); + if (r >= 0 && val > ULONG_MAX) + r = -ERANGE; + + if (out) + *out = shl_min(val, (unsigned long long)ULONG_MAX); + + return r; +} + +int shl_atoi_un(const char *str, + size_t len, + unsigned int base, + const char **next, + unsigned int *out) +{ + unsigned long long val; + int r; + + r = shl_atoi_ulln(str, len, base, next, &val); + if (r >= 0 && val > UINT_MAX) + r = -ERANGE; + + if (out) + *out = shl_min(val, (unsigned long long)UINT_MAX); + + return r; +} + +int shl_atoi_zn(const char *str, + size_t len, + unsigned int base, + const char **next, + size_t *out) +{ + unsigned long long val; + int r; + + r = shl_atoi_ulln(str, len, base, next, &val); + if (r >= 0 && val > SIZE_MAX) + r = -ERANGE; + + if (out) + *out = shl_min(val, (unsigned long long)SIZE_MAX); + + return r; +} + +/* + * Greedy Realloc + * The greedy-realloc helpers simplify power-of-2 buffer allocations. If you + * have a dynamic array, simply use shl_greedy_realloc() for re-allocations + * and it makes sure your buffer-size is always a multiple of 2 and is big + * enough for your new entries. + * Default size is 64, but you can initialize your buffer to a bigger default + * if you need. + */ + +void *shl_greedy_realloc(void **mem, size_t *size, size_t need) +{ + size_t nsize; + void *p; + + if (*size >= need) + return *mem; + + nsize = SHL_ALIGN_POWER2(shl_max_t(size_t, 64U, need)); + if (nsize == 0) + return NULL; + + p = realloc(*mem, nsize); + if (!p) + return NULL; + + *mem = p; + *size = nsize; + return p; +} + +void *shl_greedy_realloc0(void **mem, size_t *size, size_t need) +{ + size_t prev = *size; + uint8_t *p; + + p = shl_greedy_realloc(mem, size, need); + if (!p) + return NULL; + + if (*size > prev) + shl_memzero(&p[prev], *size - prev); + + return p; +} + +/* + * String Helpers + */ + +char *shl_strcat(const char *first, const char *second) +{ + size_t flen, slen; + char *str; + + if (!first) + first = ""; + if (!second) + second = ""; + + flen = strlen(first); + slen = strlen(second); + if (flen + slen + 1 <= flen) + return NULL; + + str = malloc(flen + slen + 1); + if (!str) + return NULL; + + strcpy(str, first); + strcpy(&str[flen], second); + + return str; +} + +char *shl_strjoin(const char *first, ...) { + va_list args; + size_t len, l; + const char *arg; + char *str, *p; + + va_start(args, first); + + for (arg = first, len = 0; arg; arg = va_arg(args, const char*)) { + l = strlen(arg); + if (len + l < len) + return NULL; + + len += l; + } + + va_end(args); + + str = malloc(len + 1); + if (!str) + return NULL; + + va_start(args, first); + + for (arg = first, p = str; arg; arg = va_arg(args, const char*)) + p = stpcpy(p, arg); + + va_end(args); + + *p = 0; + return str; +} diff --git a/src/shl_util.h b/src/shl_util.h new file mode 100644 index 0000000..d197ce8 --- /dev/null +++ b/src/shl_util.h @@ -0,0 +1,96 @@ +/* + * SHL - Utility Helpers + * + * Copyright (c) 2011-2013 David Herrmann + * Dedicated to the Public Domain + */ + +/* + * Utility Helpers + */ + +#ifndef SHL_UTIL_H +#define SHL_UTIL_H + +#include +#include +#include +#include +#include + +/* strict atoi */ + +int shl_ctoi(char ch, unsigned int base); + +int shl_atoi_ulln(const char *str, + size_t len, + unsigned int base, + const char **next, + unsigned long long *out); +int shl_atoi_uln(const char *str, + size_t len, + unsigned int base, + const char **next, + unsigned long *out); +int shl_atoi_un(const char *str, + size_t len, + unsigned int base, + const char **next, + unsigned int *out); +int shl_atoi_zn(const char *str, + size_t len, + unsigned int base, + const char **next, + size_t *out); + +static inline int shl_atoi_ull(const char *str, + unsigned int base, + const char **next, + unsigned long long *out) +{ + return shl_atoi_ulln(str, strlen(str), base, next, out); +} + +static inline int shl_atoi_ul(const char *str, + unsigned int base, + const char **next, + unsigned long *out) +{ + return shl_atoi_uln(str, strlen(str), base, next, out); +} + +static inline int shl_atoi_u(const char *str, + unsigned int base, + const char **next, + unsigned int *out) +{ + return shl_atoi_un(str, strlen(str), base, next, out); +} + +static inline int shl_atoi_z(const char *str, + unsigned int base, + const char **next, + size_t *out) +{ + return shl_atoi_zn(str, strlen(str), base, next, out); +} + +/* greedy alloc */ + +void *shl_greedy_realloc(void **mem, size_t *size, size_t need); +void *shl_greedy_realloc0(void **mem, size_t *size, size_t need); + +/* string helpers */ + +char *shl_strcat(const char *first, const char *second); +char *shl_strjoin(const char *first, ...); + +static inline char *shl_startswith(const char *str, const char *prefix) +{ + if (!strncmp(str, prefix, strlen(prefix))) + return (char*)str + strlen(prefix); + else + return NULL; +} + +#endif /* SHL_UTIL_H */