From eace71629216e78db4f3590b7eee9b35653e9d8b Mon Sep 17 00:00:00 2001 From: Packit Date: Aug 20 2020 10:43:02 +0000 Subject: iscsi-initiator-utils-6.2.0.878 base --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2483d0f --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*.o +cscope.files +cscope.out +TAGS +tags +*.swp +*.so +*.so.* +libopeniscsiusr/docs/man/ +# ^ This folder is used for kernel-doc generated manpages. +libopeniscsiusr/tests/test_context +libopeniscsiusr/tests/test_session +libopeniscsiusr/tests/test_iface +libopeniscsiusr/tests/test_node diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..f11db8b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,33 @@ +language: c +compiler: gcc +os: linux +dist: bionic + +env: + global: + # This is an excrypted setting of COVERITY_SCAN_TOKEN= + # Travis-CI has the private key to decrypt this and set the environment + # variable with the token needed for Coverity API access + - secure: kUIw3pil4akjNycH+R2mq8oUszQrt/TCwkQb20oBRXoqsRzYLF6I9VT5wdAERjPsD7BDSiWLxLnisYrdRAs/tQ9h3xvnOJQAX8wKvVF/B883kOwOdI17DkY/dKdzWT+LbojjaXCHZf2yaKVAjibRPO2E8J9zsvpuDLDlon7zD123Amnb/XrSVJH4jefscs1OXFVtaMIKNs7AQPoPK7V9oMZoIbBF5NhYSPpytlf5/VL6ePQlXdd4xAiQR+dg5PvEbMquJh4GvcTo5DzOAJN8L9nGvlGlH5YKxHo7tkOpKFRnCATyenbpVaUBTb/TzA9OsVmxfSr/WrzLLXQxCwfOG6ktZp+1+ANRuL5wLCtDJLooGCw4Wxsicgw69snBoFPWoPW8w4osNQAaGINZnPKM2WSFxq2DBrqHrccGk1lze7upNHikBrwSTgv+SklmZUfcDQjWYxC2owH21BHhVTV6hgNShwS5q9gwWtSWAHc4f34Qvn1gjdV2/791Skk/t4GgTSmt0j0ZVcfsQJaZBvDwkot1yDJ0u/3av+ElMAmCb/9wG3dSqUXv9/V6mAutNb243igIZko6Vmpcosp2bJKXyeVbRkgbMoIfH2VQf9n1sbb01+V9lZsk9usIZZYRcW9Bz6d2H+ooDrM/ha1kQjVqMDP2EyCdBKmQjr8iAi9E4yQ= + +addons: + apt: + update: true + packages: + - libsystemd-dev + - libkmod-dev + - libmount-dev + - libisns-dev + - openssl + - flex + - bison + coverity_scan: + project: + name: open-iscsi/open-iscsi + notification_email: cleech@redhat.com + build_command: make + branch_pattern: coverity_scan + +before_install: echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- + +script: if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then make; fi diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/Changelog b/Changelog new file mode 100644 index 0000000..0e43c6a --- /dev/null +++ b/Changelog @@ -0,0 +1,698 @@ +open-iscsi-2.0-877 - open-iscsi-2.0.878 + +# output from "git shortlog --no-merges 2.0.877..HEAD" + +Daniel Schaefer (1): + Add target to install systemd units + +Lee Duncan (18): + iscsistart is not installed + Fix i586 build issues with string length overflow. + Use pkg-config in Makefiles for newer libraries. + Updated iscsiadm man page: add "onboot" handling. + Fix output for iscsiadm node/iface print level P1 + When displaying interfaces, skip "iface.example" + Fix node print return value when no nodes. + Fix printing of node database again. + Fix output of node printing for multiple paths. + Stop using /var directory for PIDfile and locks + Improve daemon synchronization, fix err msgs + Fix pipe notification code + Add systemd support for iscsiuio + Make iscsid systemd usage optional + Fix possible discovery hang when timing out + Handle systemd disablement correctly in iscsiuio + The iscsi login/logout service requires iscsid. + Remove redundant Requires= from iscsi.service + +Manish Rangankar (3): + iscsiuio: Do not flush tx queue on each uio interrupt. + qedi: Set buf_size in case of ICMP and ARP packet. + qedi: Use uio BD index instead on buffer index. + +Nilesh Javali (3): + iscsiuio: v0.7.8.5 + iscsiuio: allow processing of iscsid requests in DHCP failure condition + iscsiuio: update version to 0.7.8.6 + +Xiubo Li (1): + rec update: disable the idbm_lock in read/write when updating the rec + +fredvx (1): + Add Restart=on-failure option to iscsid.service + +igo95862 (2): + Make iscsid.service a requirement. + Fixed iscsi.service considering every signal and exit code as successful. Now only code 21 (no objects found to execute on) and normal exit conditions are valid. + +open-iscsi-2.0-876 - open-iscsi-2.0.877 + +Antoine de Maleprade (1): + iscsid: fix logging level when starting daemon + +Cathy Zhou (1): + Reduce delays to improve iscsi boot performance + +Chris Leech (16): + libopeniscsiusr: fixes err on prefix_len + vlan setting sync across ipv4/ipv6 for be2iscsi + iscsid logging blank messages at level EMERG + iscsistart: prevent unix socket cross-talk + libopeniscsiusr: hosts can have multiple ifaces + libopeniscsiusr: clear errno before calling strtoll + libopeniscsiusr: setup ipv6 records based on iface name + libopeniscsiusr: use asprintf and remove PATH_MAX stack buffers + Merge pull request #110 from cleech/libopeniscsiusr_fixes + Merge pull request #111 from cleech/for_upstream + Merge pull request #106 from phmccarty/lib-symlinks + enable MaxOutstandingR2T negotiation + Merge pull request #114 from njavali/iscsiuio-bug-fixes + Merge pull request #115 from cleech/for_upstream + Merge pull request #120 from maxnet/va + Merge pull request #122 from njavali/iscsiuio-bug-fixes + +Christian Ehrhardt (2): + iscsiuio: avoid loosing bad rc in nic_nl_open + iscsiuio: fail on nic_nl_open failing + +Floris Bos (1): + context.h: add missing stdarg.h include + +Gris Ge (8): + libopeniscsiusr: Fix iscsi_sessions_free() on 0 se_count. + libopeniscsiusr: Add full iscsi interface support. + Makefile: Trivial change on aligning output. + libopeniscsiusr: Fix incorrect debug message for iface query. + libopeniscsiusr: Add node query support + libopeniscsiusr: Fix iscsi_iface_get() on default interfaces. + iscsiadm: Use libopeniscsiusr in `iscsiadm -m iface -P1` + libopeniscsiusr: Fix compile error on GCC 8. + +Khem Raj (6): + libopeniscsiusr: Include limit.h for PATH_MAX + libopeniscsiusr: Add CFLAGS to linker cmdline + qedi.c: Removed unused linux/ethtool.h + idbm.c: Include fcnl.h for O_RDWR and O_CREAT definitions + bnx2x.c: Reorder the includes to avoid duplicate defines with musl + fwparam_ppc.c: Do not use __compar_fn_t + +Lee Duncan (55): + Merge pull request #91 from njavali/iscsiuio-bug-fixes + Merge pull request #92 from az0uz/master + Use correct size when copying nic name. + Do not overload global sysfs_path locally. + libopeniscsiusr: ensure sysfs pathname doesn't overflow. + Ensure sysfs pathname doesn't overflow. + Merge pull request #89 from kraj/kraj/musl-fixes + Merge pull request #94 from gonzoleeman/gcc-8-fixes + Merge branch 'master' into api + Merge pull request #93 from cathay4t/api + Keep iscsi_if in sync with kernel version. + Add a TODO item on iscsi_if.h. + Merge pull request #98 from gonzoleeman/update-iscsi_if-to-latest + Add error message for new ISCSI_ERR_NOP_TIMEDOUT + Merge pull request #99 from gonzoleeman/handle-new-kernel-error-code + Allow a host_id value of zero. + Merge pull request #100 from gonzoleeman/libopeniscsiusr-allow-zero-host_id + Merge pull request #101 from gonzoleeman/fix-qedi-iface-name + Fix iscsiuio segfault when shutting down. + Remove unused file fwparam_ibft_sysfs.c. + Merge pull request #96 from cathay4t/api + Merge pull request #103 from cathy-zhou/perf_fix + Plugging a memory leak from discovery. + Merge pull request #123 from gonzoleeman/fix-discovery-leak + Allow reading sysfs "port" to fail gracefully. + Fix incorrect sysfs logic for port and ip address. + Fix reading of sysfs signed integers when negative. + Create a new error for "target not connected". + Fix bug in error message when reading sysfs numbers. + Handle ENOTCONN error separately when reading sysfs values. + Limit session relogin attempts using config value. + Merge pull request #127 from smoser/fix/iscsid-pidfile-write + Merge pull request #129 from cpaelzer/cleanup-nic_nl_open-usage + Merge pull request #131 from gonzoleeman/fix-reconnect-forever + Removed unused value 'one'. + Include stdio.h for use of snprintf(). + Fix qsort() comparator function call. + Merge pull request #132 from gonzoleeman/small-cleanups + Do not allow multiple sessions when nr_sessions=1 + Merge pull request #136 from gonzoleeman/no-parallel-sessions + When reopen_max=0 retry reopening forever. + Merge pull request #137 from gonzoleeman/add-no-timeout-relogin-option + Use libkmod instead of fork/exec of modprobe. + Merge pull request #138 from gonzoleeman/remove-fork-exec-for-modprobe + Update GPLv2 License information. + Merge pull request #139 from gonzoleeman/fix-fsf-address + Make reconnect to session on startup forever default. + Merge pull request #141 from gonzoleeman/master + Restore space in node-mode level 0 output + Merge pull request #142 from gonzoleeman/fix-mode-node-level-0-print + Merge pull request #4 from open-iscsi/master + Added service file for iscsi logins + Use sd_notify() to tell systemd when iscsid is ready. + Update systemd unit files for iscsid + Merge pull request #143 from gonzoleeman/use-sd_notify-for-systemd + +Manish Rangankar (3): + iscsiuio: Add inter-host mutex while doing xmit + iscsid: Update boot gateway information during sync_session. + iscsiuio: Release xmit_mutex in error code path. + +Nilesh Javali (4): + iscsiuio: allow ARP for non-matching src and dst addresses + iscsiuio: v0.7.8.4 + iscsiadm: get transport_name correctly for offload iface + iscsiuio: limit retries of performing dhcpv6 before declaring dhcp failure + +Patrick McCarty (1): + Fix installation of libopeniscsiusr symlinks + +Scott Moser (2): + Close file handles when writing pid files. + Better error message and failure if netlink socket fails. + + +open-iscsi-2.0-875 - open-iscsi-2.0.876 + +Chris Leech (2): + delete old kernel code + delete unused BSD stub code + +Gris Ge (6): + Remove white spaces. + Fix memory leak of session_info_print_tree() in usr/session_info.c. + Introducing iSCSI userspace library. + libopeniscsiusr: Add iSCSI session support. + libopeniscsiusr: Add basic iface support into iscsi session. + libopeniscsiusr: Use libopeniscsiusr in iscsiadm + +Khazhismel Kumykov (1): + iscsi_if.h: use attribute instead of '__packed' + +Lee Duncan (37): + Fix duplicate define of __bitwise + Fix compiler warning: possible string truncation + Replace deprecated _SVID_SOURCE with _DEFAULT_SOURCE + Rename local strings.[ch] to local_strings.[ch] + Fix compiler warnings about string overflows in prom_parse + Ignore library file for iscsiuio/src + Fix undefined call to writev(): include + Remove unused variables. No functional change. + Include to properly define minor() + Declare inline best_match_bufcmp() as static. + Merge pull request #69 from gonzoleeman/new-compiler-fixes + Merge pull request #3 from open-iscsi/master + Check for root peer user for iscsiuio IPC + iscsiuio should ignore bogus iscsid broadcast packets + Ensure all fields in iscsiuio IPC response are set + Do not double-close IPC file stream to iscsid + Ensure strings from peer are copied correctly. + Skip useless strcopy, and validate CIDR length + Check iscsiuio ping data length for validity + tell git to ignore the iscsiuio binary + Merge pull request #72 from gonzoleeman/iscsiuio-fixes + Automate logging into iSCSI FW targets. + Ignore common build output files + Merge pull request #78 from gonzoleeman/updates/ignore-standard-build-files + Merge pull request #79 from gonzoleeman/updates/add-iscsi_fw_login + Merge pull request #80 from cleech/master + Cleanup iscsiuio master Makefile template. + Update iscsid.conf attribute iscsid.startup. + Merge pull request #81 from gonzoleeman/updates/iscsid.conf-changes-v2 + Merge pull request #82 from gonzoleeman/updates/iscsiuio-Makefile.am-updates + Add in tracking IP prefix length, in addition to mask. + Add some scripts and manpages to the top Makefile. + Merge pull request #83 from gonzoleeman/fixes/update-top-level-makefile + Merge pull request #84 from gonzoleeman/fixes/add_ip_prefix + Discovery via non-tcp transport needs "ipc" value + Merge pull request #85 from gonzoleeman/fixes/ipc-should-not-be-null + Do not set LDFLAGS directly in usr/Makefile + remove kernel subdir from clean Makefile target + + +open-iscsi-2.0-874 - open-iscsi-2.0.875 + +Andrew Patterson (3): + iscsiuio must be present to use hardware offload for bnx2{,x} + iscsistart: move offload discovery/setup to fw_get_targets() + iscsiuio: fix long options + +Chris Leech (8): + fix timeout setting on discoverydb commands + Merge pull request #41 from cleech/master + Merge pull request #40 from Akrog/feature/autoscan_en + Merge pull request #42 from m4z/uniform-headings + Merge pull request #43 from m4z/whitespace + Merge pull request #44 from m4z/typos + Merge pull request #45 from m4z/punctuation + Merge pull request #49 from Akrog/fix/autoscan_en + +Christopher 'm4z' Holm (7): + Unify README headings. + Cleanup README whitespace. + Fix a bunch of typos and a bit of wording. + Improve README punctuation. + Merge branch 'master' into punctuation + Unify invocation examples, option documentation, and more. + Add actual "iscsiadm --help" output (on openSUSE). + +Christopher Holm (1): + Merge branch 'master' into uniform-invocation-examples + +Edward Kigwana (2): + Update bnx2.c + Update bnx2x.c + +Gorka Eguileor (2): + Allow disabling auto LUN scans + Fix manual LUN scans feature + +Hannes Reinecke (1): + Use timeout when waiting for responses from iscsid + +Lee Duncan (17): + iBFT 'origin' is an enum, not a string + Merge pull request #32 from gonzoleeman/master + iscsid: treat SIGTERM like "iscsiadm -k 0" + Make event_loop_stop volatile for safer access + Merge pull request #34 from gonzoleeman/master + Merge pull request #1 from open-iscsi/master + Fix coredump when printing session info. + Merge pull request #52 from gonzoleeman/master + Merge pull request #53 from gonzoleeman/master + Merge pull request #54 from njavali/iscsiuio-bug-fixes + Merge pull request #57 from y011/patch-1 + Merge pull request #2 from open-iscsi/master + Clear errno before calling strtoull. + Merge pull request #60 from gonzoleeman/strtoull-fix + Merge pull request #61 from ekigwana/master + Merge pull request #46 from m4z/uniform-invocation-examples + Merge pull request #55 from apatters/bnx2-software-ibft-support + +Neal Wise (1): + Fixed typo for spelling of 'default' + +Nilesh Javali (5): + iscsid: Changes to support the new qedi transport + iscsiuio: Add support for the new qedi transport + iscsiuio: v0.7.8.3 + iscsiuio: fix dhcpv6 transaction-id mismatch error + iscsiuio: serialize xmit_mutex lock to prevent iscsiuio seg fault + +Nilesh Javili (1): + iscsid: Add qedi ping transport hook + + +open-iscsi-2.0-873 - open-iscsi-2.0.874 + +Adam Jackson (6): + actor: Mark actor_check static + actor: simplify actor_check + actor: s/ACTOR_TICKS/actor_jiffies/ + actor: Remove ACTOR_TICKS_10MS() + actor: Unobfuscate ACTOR_MAX_LOOPS + actor: Simplify actor_poll a little + +Adheer Chandravanshi (26): + Manpage changes for flashnode submode support for host mode. + README changes for flashnode submode support for host mode. + iscsiadm: Check for mode is not required when creating params list + iscsiadm: Correctly check for invalid hostno and flashnode index + flashnode: Add support to set ISCSI_FLASHNODE_CHAP_OUT_IDX param + iscsiadm: Use '-x' option instead of '-v' to specify chap_tbl_idx + iscsiadm: Man page changes to use -x option for chap_tbl_idx + README changes to use long option --index instead of --flashnode_idx + iscsiadm: Add support to set CHAP entry using host chap mode + iscsi tools: Correctly get username_in and password_in flashnode params + README changes for adding support to set CHAP entry + iscsi tools: Setup iface conf file with all iface attrs exported in sysfs + iscsi_if.h: Additional parameters for network param settings + iscsi tools: iface params should be updated for node_rec as well. + iscsi tools: Let default type of iface be ipv4 + iscsi tools: Show iface params based on iface type + iscsi tools: Fix the iscsiadm help options for host mode + Man page correction for host mode options of iscsiadm + iscsiadm: Fix the compile time warning + iscsiuio: Correct the handling of Multi Function mode + iscsiuio: Add QLogic Vendor ID to support newer NX2 HBAs + iscsid: Changes to support ping through iscsiuio + iscsiuio: Add ping support through iscsiuio + iscsiadm: let ping be tried after iface config is initialized + iscsiuio: Wait for iface to be ready before issuing the ping + iscsiuio: Get the library to use based on uio sysfs name + +Andy Grover (20): + Update README for removal of DBM requirement + Fix build warnings for unused variables + Fix warning about possibly-uninitialized variable + Fix bad sizeof in memset + Fix missing header + iscsiuio: Fix warning about non-matching types + iscsiuio: Fix strict-aliasing warning with struct mac_address + iscsiuio: Resolve strict aliasing issue in iscsiuio/src/unix/nic.c + iscsiuio: Fix aliasing issue with IPV6_IS_ADDR_UNSPECIFIED + iscsiuio: Use attribute(unused) for variables that are unused but needed + iscsiuio: Use attribute(unused) for *icmpv6_hdr + iscsiuio: Change nic_disable to return void + iscsiuio: Remove set but unused variables + iscsiuio: Check return value from nic_queue_tx_packet + Remove actor_init and rename actor_new to actor_init + Make running actors event-driven + Wake up to reap children + Fix incorrect list operation leading to out-of-order items on pend_list + Prevent spinning over poll() when reconnecting to an inaccessible target + Add some more debug logging to actor.c + +Anish Bhatt (1): + iscsiadm : make iface.ipaddress optional in iface configs for transports that don't have a hard requirement on it. + +Chris Leech (19): + iscsiadm: Fix the hostno check for stats submode of host mode + fix regression in iscsi_tcp iface binding + guard against NULL ptr during discovery from unexpected event + add discovery as a valid mode in iscsiadm.8 + iscsid: fix order of setting uid/gid and drop supplementary groups + iscsiuio CFLAGS fixes + iscsiuio systemd socket activation support + iscsid safe session logout + iscsid: don't re-read config file for every session logout + make use of all 24 bits of ISID qualifier space + iscsi_tcp set SO_LINGER to abort connection for error handling + iscsiadm: fix parallel rescan handling of exit codes + iscsistart: support booting over a VLAN + iscsid: safe_logout fix device path canonicalization by using libmount cache + iscsid: make safe_logut session checks apply for flashnode session + remove sysfs attr_list + Merge pull request #25 from cleech/master + Merge pull request #29 from chris-se/debian-patches + Replace open-iscsi.org with open-iscsi.com in docs + +Christian Hesse (1): + typo in man iscsiadm(8) + +Christian Seiler (8): + buildsys: make 'make clean' idempotent + buildsys: respect CFLAGS and LDFLAGS from the outside + Remove outdated Debian packaging code. + Reformat man page synopsis sections + Build system: sort object file lists + iscsiuio: Make builds reproducible if SOURCE_DATE_EPOCH is set + Additional spelling fixes + iscsiuio/Makefile.am: fix typo introduced by reproducibility patch + +Christophe Vu-Brugier (1): + Fix typos in iscsiadm man page + +Christopher Unkel (1): + Fix typo in man page. + +Duane Northcutt (1): + iscsid: Fix double close of mgmt ipc fd + +Eddie Wai (13): + ISCSISTART: Bring up the corresponding network interface for iboot + ISCSID: Passing more net params from ibft to iface + ISCSID: Modified the Makefile for iscsiuio compilation + ISCSID: Added iscsiuio source to the open-iscsi pkg + ISCSIUIO: Updated iscsiuio to version 0.7.8.1b for perf optimization + ISCSID: Added the extraction of the session boot info + ISCSID: Added iface content override fix + ISCSIUIO: Added tx doorbell override mechanism + ISCSIUIO: Added fix for the iface.subnet_mask decoding for IPv6 + ISCSIUIO: Added fix for the ARP cache flush mechanism + ISCSIUIO: Updated RELEASE note and version + ISCSIUIO: Removed the auto-generated COPYING file + ISCSIUIO: Fixed a pthread resc leak from excessive session recovery + +Frank Fegert (2): + Prevent iscsiuio from segfaulting due to un-lock of a not locked mutex. + iscsiuio: ensure unlock of mutex in case of an error. + +Hannes Reinecke (14): + iscsiuio: Remove autogenerated files from tracking + iscsiuio: Update automake files + iscsiuio: Add .gitignore files + Remove unused variable 'path' + Parse 'origin' value from iBFT + Added new utility script to generate initiator name + Added new util script to aid in CNA setup + Code cleanup: no functional changes + Represent DHCP "origin" as an enum, not a string. + fwparam_ibft: Check iBFT target and NIC flags + Allow modifications for iface.gateway and iface.subnet_mask + iscsiuio: Do not memcpy identical locations + iscsiuio: Clear memory after allocation + iscsiuio: fixup race condition + +Harish Zunjarrao (6): + iscsi_if.h: Remove numbers used for network parameter settings + iscsi tools: Use macro to set IPv4/IPv6 IP addresses + iscsi tools: Use single function to enable/disable network parameters + iscsi tools: Use single function to set integer network parameters + iscsi tools: Ignore network parameter if not enabled/disabled + iscsi tools: Additional parameters for network settings + +Heinrich Schuchardt (1): + Kernel include path + +Jan Vesely (2): + iscsid: Fix strlen parameter + iscsiuio: Change socket bind to use the same struct size as iscsid + +Jim Ramsay (1): + iscsi tools: Convert '-r' argument to an integer before checking if it is a path + +John Soni Jose (2): + be2iscsi: Fix MaxXmitDataLenght of the driver. + Fix StatSN in Open-iSCSI Stack. + +Kamalneet Singh (1): + fix typo + +Lalit Chandivade (3): + iscsi_tool: Add offload host statistics support. + README: Updated for host statistics. + iscsiadm.8: Updated man page for host statistics. + +Lee Duncan (13): + PATCH 1 of 1] correctly check return value of nice() + iscsiadm: return error when login fails + Fix discovery error return without return value + Add missing DESTDIR + isns: Add docs for deregistering discovery domains. + Supply strings for newly-added error numbers + Allow setting host params to return EAGAIN errors. + Remove duplicate newlines in log messages. + Fix iBFT target flags check. + Use system-wide open-isns, not internal version. + ARP table too small when switches involved. + Merge pull request #22 from frank-fegert/master + Merge pull request #26 from cvubrugier/master + +Manish Rangankar (1): + iscsiadm: Initialize param_count in set_host_chap_info + +Mark Karpeles (1): + fixed typo in iscsi_discovery usage() + +Mike Christie (22): + iscsid: fix iscsid segfault during qla4xxx login + iscsi tools: fix compile error when OFFLOAD_BOOT_SUPPORT defined + iscsi tools: fix get_random_bytes error handling + ISCSID: Added socket communication hooks for uip + From: Adheer Chandravanshi + Allow firmware mode to use debug flag + iscsiadm: bind ifaces to portals found using isns + iscsid/iscsiadm: add support for emulex one connect storage + Make rescan run in parallel + iscsi tools: sync iscsi_if.h with kernel space + ISCSISTART: Saved ibft boot info to the session + iscsi tools: Bug fix on IPC address copy (version 2) + ISCSIUIO: Updated the configure file to reflect the new version + iscsiuio: fix compilation + iscsi tools: set non negotiated params early. + iscsid: Fix handling of iscsi async events. + iscsid: retry login for ISCSI_ERR_HOST_NOT_FOUND + iscsid: don't round up when modifying padding len + iscsi: remove local copy of open-isns + iscsid: make sure actor is delated before rescheduling + iscsid/iscsiuio: remove uio poll + iscsid: fix iscsi_host_set_net_params return code + +Ritesh Raj Sarraf (1): + Spelling and escaping error fixes. + +Salvatore Bonaccorso (1): + Fix small typo in iscsid.conf + +Tomasz Torcz (3): + iscsid,iscsiadm: fix abstract socket length in bind() call + iscsid: implement systemd-compatible socket activation + iscsid: add example unit files for systemd + +Vikas Chaudhary (3): + iscsi tools: Print additional session info for flashnode session + iscsiadm: Added document for description of iface attributes + iscsiuio: Rebranding iscsiuio + +Ville Skyttä (2): + man page syntax fixes + Spelling fixes + +mikechristie (3): + Merge pull request #11 from deepankar/master + Merge pull request #8 from dscunkel/master + Merge pull request #9 from xypron/kernel_source_path + +open-iscsi-2.0-872 - open-iscsi-2.0.873 + +Ales Kozumplik (1): + fwparam_sysfs: fix pathname manipulation error in fwparam_sysfs_boot_info. + +Eddie Wai (3): + ISCSID: Fixed a race condition in the INVALID_HOST path + ISCSID: Fixed iface update for the new iface net config params + ISCSIADM: Included the new iface net params to the node creation + +Hannes Reinecke (6): + iscsid sends SIGTERM to PID 0 + iscsid: Implement --no-pid-file + Keep startup mode in sync when specified in config file + Allow LOCK_DIR to be set via CFLAGS + Allow 'onboot' as loginall parameter + boot.suse: Update with latest fixes + +Jim Ramsay (12): + Add specific session information to session_rec_t + Add support for multiple sessions per iface to iscsid + Add multiple sessions per iface commandline syntax + Add new node.session.nr_sessions config parameter + Implement leading-login support + Fix dcb_app.c compile error with old kernels + Check all ifaces during discovery even if some timeout + iscsid: In foreground mode, treat SIGINT like SIGTERM + Revise bind_conn_to_iface logic + iscsi tools: Fix warnings reported by gcc-4.5.2 + fwparam_ibft: Fix warnings reported by gcc-4.5.2 + open-isns: Fix warnings reported by gcc-4.5.2 + +Karen Xie (1): + open-iscsi: add transport cxgb4i + +Lalit Chandivade (2): + iscsiadm: add netconfig support + iscsi tools: manage qla4xxx iscsi sessions with iscsiadm + +Manish Rangankar (1): + iscsid: Fixed iscsid restart issue for offload iSCSI login + +Mark Rustad (7): + Add some consts to char * parameters that are not changed + Add dcb_app.h for DCB support + Add dcb_app.c for DCB support + Add initial DCB support + iscsid: Fix netdev check + Remove redundant initialization + iscsid: Add IEEE DCB support + +Mike Christie (86): + iscsid: remove bogus debug log msg in isns_disc_new_portals + isns: Fix endless loop when pollhup is returned + iscsi tools: fix multi pdu sendtargets discovery sequences + iscsi boot: fix iscsi_boot sysfs parsing + Use pass through interface for sendtargets (take4) Currently offload cards like bnx2i, be2iscsi, cxgb3i must use a normal eth for discovery. This patch allows us to do discovery using the iscsi class passthrough interface. + Add userspace/tools iscsi error code defs + iscsi tools: fix iscsiadm exit codes + iscsid: modify data drop + iscsi doc: document iscsiadm host argument + iscsid: add new auth error code + iscsi tools: convert discovery code to iscsi error codes + iscsi tools: document iface rp_filter use + iscsi tools: disable isns dsa code + iscsi tools: support hostnames in node mode + iscsi tools: fix discovery return code + iscsiadm: fix offload discovery retry + iscsid: fix signal handler debug msg + iscsiadm: fix discovery exit code + iscsi tools: fix dcbnl.h compile error + iscsid: retry initial connect + iscsi tools/kernel: switch make defaults + iscsi tools: fix comment about sysfs lookup failures + iscsi tools: fix netlink bug allocation + iscsi tools: Don't try to bind offload EPs to sockets + iscsi tools: fix oom_adj use + iscsi tools: fix bnx2i boot due to MAC mismatch + iscsiadm: fix discoverydb help + Update SUSE init script + iscsi tools: revert commit c440cbe7ba2464f8baadedb55b00754c36773c2c + Add a TODO + TODO: mark down Jose as working on idr + Update TODO + iscsi tools: fix iname sysfs handling + iscsi tools: handle compile warnings about unused variables + iscsiadm: print kernel iface info + iscsi tools: add tgt reset to session info and fix unknown values + iscsi tools: fix default iface binding setup + iscsi tools: fix iscsiadm return value on failed login + iscsi tools: don't build with openssl + iscsi tools: check NULL pointer first and add limit check in str_remove_initial + iscsi tools: fix README sid lookup info + iscsid: print out more informative error string for kernel errors + iscsi tools: fix netlink msg setup + iscsi tools: fix up vlan support + update todo + iscsiadm: fix printing of unknown host values + Do not run configure for open-isns on every build + iscsi tools: fix ipv6 ibft/firmware boot + iscsid: don't sync qla4xxx flash sessions + iscsid: kill session if already exists. + isns: remove rfc files. + iscsistart: allow any rec/iscsid.conf setting as arg + iscsistart: support params in offload/ibft mode + iscsi tools: never use hdr digest with iser + iscsi tools: update iscsi_if.h for host event + iscsi tools: added ping support + iscsi tools: Add support to display a host's CHAP list and delete + iscsi tools: fix conn state compilation warnings + iscsi tools: allow default to have different transort names + iscsiadm: print ping status string + iscsi tools: fix hostname with port handling + iscsi tools: have iscsid/iscsiadm load modules as needed + iscsi tools: have iscsiadm load offload modules + init: update red hat init script for module changes + iscsi tools: create def ifaces on demand + iscsi tools: iscsiadm modprobe support + iscsi tools: remove class version check + iscsi tools: remove unused len variable + iscsiadm: support multiple params in one call + iscsid: remove DCB support + iscsiadm: print port speed and link state + iscsi tools: check for loaded module before loading + iscsistart: have iscsistart use same multi param code as iscsiadm + iscsi tools: print and load boot transport + iscsiadm: load iface before checking for hostno/mac match + iscsistart: fix iface overriding + iscsiadm: make sure offload drivers are loaded in host mode + iscsi tools: have iscsi tools bring up offload net iface + iscsi tools: fix bnx2i login + iscsi tools: fix ipv6 handling + iscsistart: fix invalid param handling + iscsiadm: added command line option '--interval' + iscsi tools: fix unknown param warnings + iscsi tools: fix socket leak in transport probe + iscsi tools: remove useless NULL iface check + iscsi tools: use strlpy in net code + +Nilesh Javali (1): + iscsi tools: update documents for CHAP command + +Rahul Gupta (1): + iscsi tools: Displaying timeout and CHAP in iscisadm info + +Vikas Chaudhary (2): + iscsi tools: update documents for ping command + iscsi tools: remove un-necessary print message + +Wang Sheng-Hui (1): + usr/config.h: fix comment for struct iscsi_session_timeout_config + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7b445a5 --- /dev/null +++ b/Makefile @@ -0,0 +1,181 @@ +# +# Makefile for the Open-iSCSI Initiator +# + +# if you are packaging open-iscsi, set this variable to the location +# that you want everything installed into. +DESTDIR ?= + +prefix = /usr +exec_prefix = / +sbindir = $(exec_prefix)/sbin +bindir = $(exec_prefix)/bin +mandir = $(prefix)/share/man +etcdir = /etc +initddir = $(etcdir)/init.d +rulesdir = $(etcdir)/udev/rules.d +systemddir = $(prefix)/lib/systemd/system + +MANPAGES = doc/iscsid.8 doc/iscsiadm.8 doc/iscsi_discovery.8 \ + iscsiuio/docs/iscsiuio.8 doc/iscsi_fw_login.8 doc/iscsi-iname.8 \ + doc/iscsistart.8 +PROGRAMS = usr/iscsid usr/iscsiadm utils/iscsi-iname iscsiuio/src/unix/iscsiuio \ + usr/iscsistart +SCRIPTS = utils/iscsi_discovery utils/iscsi_fw_login utils/iscsi_offload \ + utils/iscsi-gen-initiatorname +INSTALL = install +ETCFILES = etc/iscsid.conf +IFACEFILES = etc/iface.example +RULESFILES = utils/50-iscsi-firmware-login.rules +SYSTEMDFILES = etc/systemd/iscsi.service \ + etc/systemd/iscsid.service etc/systemd/iscsid.socket \ + etc/systemd/iscsiuio.service etc/systemd/iscsiuio.socket + +export DESTDIR prefix INSTALL + +# Compatibility: parse old OPTFLAGS argument +ifdef OPTFLAGS +CFLAGS = $(OPTFLAGS) +endif + +# Export it so configure of iscsiuio will +# pick it up. +ifneq (,$(CFLAGS)) +export CFLAGS +endif + +# export systemd disablement if set +ifneq ($(NO_SYSTEMD),) +export NO_SYSTEMD +WITHOUT_ARG = --without-systemd +else +WITHOUT_ARG = +endif + +# Random comments: +# using '$(MAKE)' instead of just 'make' allows make to run in parallel +# over multiple makefile. + +all: user + +user: iscsiuio/Makefile + $(MAKE) -C libopeniscsiusr + $(MAKE) -C utils/sysdeps + $(MAKE) -C utils/fwparam_ibft + $(MAKE) -C usr + $(MAKE) -C utils + $(MAKE) -C iscsiuio + @echo + @echo "Compilation complete Output file" + @echo "----------------------------------- ----------------" + @echo "Built iSCSI daemon: usr/iscsid" + @echo "Built management application: usr/iscsiadm" + @echo "Built boot tool: usr/iscsistart" + @echo "Built iscsiuio daemon: iscsiuio/src/unix/iscsiuio" + @echo "Built libopeniscsiusr library: libopeniscsiusr/libopeniscsiusr.so" + @echo + @echo "Read README file for detailed information." + +iscsiuio/Makefile: iscsiuio/configure iscsiuio/Makefile.in + cd iscsiuio; ./configure $(WITHOUT_ARG) + +iscsiuio/configure iscsiuio/Makefile.in: iscsiuio/configure.ac iscsiuio/Makefile.am + cd iscsiuio; autoreconf --install + +force: ; + +clean: + $(MAKE) -C utils/sysdeps clean + $(MAKE) -C utils/fwparam_ibft clean + $(MAKE) -C utils clean + $(MAKE) -C usr clean + $(MAKE) -C libopeniscsiusr clean + [ ! -f iscsiuio/Makefile ] || $(MAKE) -C iscsiuio clean + [ ! -f iscsiuio/Makefile ] || $(MAKE) -C iscsiuio distclean + +# this is for safety +# now -jXXX will still be safe +# note that make may still execute the blocks in parallel +.NOTPARALLEL: install_user install_programs install_initd \ + install_initd_suse install_initd_redhat install_initd_debian \ + install_etc install_iface install_doc install_iname + +install: install_programs install_doc install_etc \ + install_initd install_iname install_iface install_libopeniscsiusr + +install_user: install_programs install_doc install_etc \ + install_initd install_iname install_iface + +install_udev_rules: + $(INSTALL) -d $(DESTDIR)$(rulesdir) + $(INSTALL) -m 644 $(RULESFILES) $(DESTDIR)/$(rulesdir) + +install_systemd: + $(INSTALL) -d $(DESTDIR)$(systemddir) + $(INSTALL) -m 644 $(SYSTEMDFILES) $(DESTDIR)/$(systemddir) + +install_programs: $(PROGRAMS) $(SCRIPTS) + $(INSTALL) -d $(DESTDIR)$(sbindir) + $(INSTALL) -m 755 $^ $(DESTDIR)$(sbindir) + +# ugh, auto-detection is evil +# Gentoo maintains their own init.d stuff +install_initd: + if [ -f /etc/debian_version ]; then \ + $(MAKE) install_initd_debian ; \ + elif [ -f /etc/redhat-release ]; then \ + $(MAKE) install_initd_redhat ; \ + elif [ -f /etc/SuSE-release ]; then \ + $(MAKE) install_initd_suse ; \ + fi + +# these are external targets to allow bypassing distribution detection +install_initd_suse: + $(INSTALL) -d $(DESTDIR)$(initddir) + $(INSTALL) -m 755 etc/initd/initd.suse \ + $(DESTDIR)$(initddir)/open-iscsi + $(INSTALL) -m 755 etc/initd/boot.suse \ + $(DESTDIR)$(initddir)/boot.open-iscsi + +install_initd_redhat: + $(INSTALL) -d $(DESTDIR)$(initddir) + $(INSTALL) -m 755 etc/initd/initd.redhat \ + $(DESTDIR)$(initddir)/open-iscsi + +install_initd_debian: + $(INSTALL) -d $(DESTDIR)$(initddir) + $(INSTALL) -m 755 etc/initd/initd.debian \ + $(DESTDIR)$(initddir)/open-iscsi + +install_iface: $(IFACEFILES) + $(INSTALL) -d $(DESTDIR)$(etcdir)/iscsi/ifaces + $(INSTALL) -m 644 $^ $(DESTDIR)$(etcdir)/iscsi/ifaces + +install_etc: $(ETCFILES) + if [ ! -f $(DESTDIR)/etc/iscsi/iscsid.conf ]; then \ + $(INSTALL) -d $(DESTDIR)$(etcdir)/iscsi ; \ + $(INSTALL) -m 644 $^ $(DESTDIR)$(etcdir)/iscsi ; \ + fi + +install_doc: $(MANPAGES) + $(INSTALL) -d $(DESTDIR)$(mandir)/man8 + $(INSTALL) -m 644 $^ $(DESTDIR)$(mandir)/man8 + +install_iname: + if [ ! -f $(DESTDIR)/etc/iscsi/initiatorname.iscsi ]; then \ + echo "InitiatorName=`$(DESTDIR)/sbin/iscsi-iname`" > $(DESTDIR)/etc/iscsi/initiatorname.iscsi ; \ + echo "***************************************************" ; \ + echo "Setting InitiatorName to `cat $(DESTDIR)/etc/iscsi/initiatorname.iscsi`" ; \ + echo "To override edit $(DESTDIR)/etc/iscsi/initiatorname.iscsi" ; \ + echo "***************************************************" ; \ + fi + +install_libopeniscsiusr: + $(MAKE) -C libopeniscsiusr install + +depend: + for dir in usr utils utils/fwparam_ibft; do \ + $(MAKE) -C $$dir $@; \ + done + +# vim: ft=make tw=72 sw=4 ts=4: diff --git a/README b/README new file mode 100644 index 0000000..2499d9a --- /dev/null +++ b/README @@ -0,0 +1,1639 @@ +================================================================= + + Linux* Open-iSCSI + +================================================================= + + Sep 29, 2016 +Contents +======== + +- 1. In This Release +- 2. Introduction +- 3. Installation +- 4. Open-iSCSI daemon +- 5. Open-iSCSI Configuration Utility +- 6. Configuration +- 7. Getting Started +- 8. Advanced Configuration +- 9. iSCSI System Info + + +1. In This Release +================== + +This file describes the Linux* Open-iSCSI Initiator. The software was +tested on AMD Opteron (TM) and Intel Xeon (TM). + +The latest development release is available at: + http://www.open-iscsi.com + +For questions, comments, contributions send e-mail to: + open-iscsi@googlegroups.com + + +1.1. Features +============= + +- highly optimized and very small-footprint data path; +- persistent configuration database; +- SendTargets discovery; +- CHAP; +- PDU header Digest; +- multiple sessions; + + +2. Introduction +=============== + +The Open-iSCSI project is a high-performance, transport independent, +multi-platform implementation of RFC3720 iSCSI. + +Open-iSCSI is partitioned into user and kernel parts. + +The kernel portion of Open-iSCSI is a from-scratch code +licensed under GPL. The kernel part implements iSCSI data path +(that is, iSCSI Read and iSCSI Write), and consists of three +loadable modules: scsi_transport_iscsi.ko, libiscsi.ko and iscsi_tcp.ko. + +User space contains the entire control plane: configuration +manager, iSCSI Discovery, Login and Logout processing, +connection-level error processing, Nop-In and Nop-Out handling, +and (in the future:) Text processing, iSNS, SLP, Radius, etc. + +The user space Open-iSCSI consists of a daemon process called +iscsid, and a management utility iscsiadm. + + +3. Installation +=============== + +As of today, the Open-iSCSI Initiator requires a host running the +Linux operating system with kernel version 2.6.16 or later. 2.6.14 and +2.6.15 are partially supported. Known issues with 2.6.14 - .15 support: + +- If the device is using a write back cache, during session logout +the cache sync command will fail. +- iscsiadm's -P 3 option will not print out scsi devices. +- iscsid will not automatically online devices. + +You need to enable "Cryptographic API" under "Cryptographic options" in the +kernel config. And you must enable "CRC32c CRC algorithm" even if +you do not use header or data digests. They are the kernel options +CONFIG_CRYPTO and CONFIG_CRYPTO_CRC32C, respectively. + +The userspace components iscsid, iscsiadm and iscsistart require the +open-isns library, which can be found here: + https://github.com/gonzoleeman/open-isns/releases + +To install the open-isns headers and library required for open-iscsi, download +the current release and run: + + ./configure + make + make install + make install_hdrs + make install_lib + +By default the kernel's iSCSI modules will be used. Running: + + make + make install + +will install the iSCSI tools iscsiadm and iscsid to /sbin. + +For 2.6.14 - 2.6.34 the modules in the kernel dir can be built and installed +by running: + + make kernel + +When building those modules the kernel source found at + /lib/modules/`uname -r`/build + +will be used to compile the open-iscsi modules. To specify a different +kernel to build against, use: + make kernel KSRC= + +or to use cross-compilation: + make kernel KSRC= KARCH="ARCH=um" + +To compile on SUSE Linux you'll have to use + + make kernel KSRC=/usr/src/linux \ + KBUILD_OUTPUT=/usr/src/linux-obj// + +where is the kernel configuration to use (eg. 'smp'). + +To install the kernel modules that were built, run: + + make install_kernel + +This will copy iscsi_tcp.ko, libiscsi_tcp.ko, libiscsi.ko and +scsi_transport_iscsi.ko to + /lib/modules/`uname -r`/kernel/drivers/scsi/ +overwriting existing iscsi modules. + +For Debian, be sure to install the linux-headers package that +corresponds to your kernel in order to compile the kernel modules +('aptitude install linux-headers-`uname -r`'). You may also wish to +run 'make -C kernel/ dpkg_divert' before installing kernel modules if +you run a Debian-provided kernel. This will use dpkg-divert(8) to +move the packaged kernel modules out of the way, and ensure that +future kernel upgrades will not overwrite them. + +Also, please be aware that the compatibility patches that enable these +iscsi modules to run on kernels older than 2.6.25 will not update the +ib_iser module; you may get warnings related to mismatched symbols on +this driver, in which case you'll be unable to load ib_iser and +open-iscsi simultaneously. + + +4. Open-iSCSI daemon +==================== + +The daemon implements control path of iSCSI protocol, plus some management +facilities. For example, the daemon could be configured to automatically +re-start discovery at startup, based on the contents of persistent +iSCSI database (see next section). + +For help, run: + iscsid --help + +The output will be similar to the following. + +Usage: iscsid [OPTION] + + -c, --config=[path] Execute in the config file (/etc/iscsi/iscsid.conf). + -f, --foreground run iscsid in the foreground + -d, --debug debuglevel print debugging information + -u, --uid=uid run as uid, default is current user + -g, --gid=gid run as gid, default is current user group + -h, --help display this help and exit + -v, --version display version and exit + + +5. Open-iSCSI Configuration Utility +=================================== + +Open-iSCSI persistent configuration is stored in a number of +directories under a configuration root directory, using a flat-file +format. This configuration root directory is /etc/iscsi by default, +but may also commonly be in /var/lib/iscsi. + +Configuration is contained in directories for: + +- nodes +- slp +- isns +- static +- fw +- send_targets +- ifaces + +The iscsiadm utility is a command-line tool to manage (update, delete, +insert, query) the persistent database. + +The utility presents set of operations that a user can perform +on iSCSI nodes, sessions, connections, and discovery records. + +Open-iSCSI does not use the term node as defined by the iSCSI RFC, +where a node is a single iSCSI initiator or target. Open-iSCSI uses the +term node to refer to a portal on a target, so tools like iscsiadm +require that --targetname and --portal argument be used when in node mode. + +For session mode, a session id (sid) is used. The sid of a session can be +found by running + iscsiadm -m session -P 1 + +The session id is not currently persistent and is partially determined by +when the session is setup. + +Note that some of the iSCSI Node and iSCSI Discovery operations +do not require iSCSI daemon (iscsid) loaded. + +For help, run: + iscsiadm --help + +The output will be similar to the following. + +iscsiadm -m discoverydb [ -hV ] [ -d debug_level ] [-P printlevel] [ -t type -p ip:port -I ifaceN ... [ -Dl ] ] | [ [ -p ip:port -t type] [ -o operation ] [ -n name ] [ -v value ] [ -lD ] ] +iscsiadm -m discovery [ -hV ] [ -d debug_level ] [-P printlevel] [ -t type -p ip:port -I ifaceN ... [ -l ] ] | [ [ -p ip:port ] [ -l | -D ] ] +iscsiadm -m node [ -hV ] [ -d debug_level ] [ -P printlevel ] [ -L all,manual,automatic ] [ -U all,manual,automatic ] [ -S ] [ [ -T targetname -p ip:port -I ifaceN ] [ -l | -u | -R | -s] ] [ [ -o operation ] [ -n name ] [ -v value ] ] +iscsiadm -m session [ -hV ] [ -d debug_level ] [ -P printlevel] [ -r sessionid | sysfsdir [ -R | -u | -s ] [ -o operation ] [ -n name ] [ -v value ] ] +iscsiadm -m iface [ -hV ] [ -d debug_level ] [ -P printlevel ] [ -I ifacename | -H hostno|MAC ] [ [ -o operation ] [ -n name ] [ -v value ] ] [ -C ping [ -a ip ] [ -b packetsize ] [ -c count ] [ -i interval ] ] +iscsiadm -m fw [ -d debug_level ] [ -l ] +iscsiadm -m host [ -P printlevel ] [ -H hostno|MAC ] [ [ -C chap [ -x chap_tbl_idx ] ] | [ -C flashnode [ -A portal_type ] [ -x flashnode_idx ] ] | [ -C stats ] ] [ [ -o operation ] [ -n name ] [ -v value ] ] +iscsiadm -k priority + + +The first parameter specifies the mode to operate in: + -m, --mode specify operational mode op = + + +Mode "discoverydb" +------------------ + + -m discoverydb --type=[type] --interface=[iface…] --portal=[ip:port] \ + --print=[N] \ + --op=[op]=[NEW | UPDATE | DELETE | NONPERSISTENT] \ + --discover + + This command will use the discovery record settings + matching the record with type=type and + portal=ip:port]. If a record does not exist, it will + create a record using the iscsid.conf discovery + settings. + + By default, it will then remove records for + portals no longer returned. And, + if a portal is returned by the target, then the + discovery command will create a new record or modify + an existing one with values from iscsi.conf and the + command line. + + [op] can be passed in multiple times to this + command, and it will alter the node DB manipulation. + + If [op] is passed in and the value is + "new", iscsiadm will add records for portals that do + not yet have records in the db. + + If [op] is passed in and the value is + "update", iscsiadm will update node records using + info from iscsi.conf and the command line for portals + that are returned during discovery and have + a record in the db. + + If [op] is passed in and the value is "delete", + iscsiadm will delete records for portals that + were not returned during discovery. + + If [op] is passed in and the value is + "nonpersistent", iscsiadm will not store + the portals found in the node DB. This is + only useful with the --login command. + + See the example section for more info. + + See below for how to setup iscsi ifaces for + software iscsi or override the system defaults. + + Multiple ifaces can be passed in during discovery. + + For the above commands, "print" is optional. If + used, N can be 0 or 1. + 0 = The old flat style of output is used. + 1 = The tree style with the inteface info is used. + + If print is not used, the old flat style is used. + + -m discoverydb --interface=[iface...] --type=[type] --portal=[ip:port] \ + --print=[N] \ + --op=[op]=[NEW | UPDATE | DELETE | NONPERSISTENT] \ + --discover --login + + This works like the previous discoverydb command + with the --login argument passed in will also + log into the portals that are found. + + -m discoverydb --portal=[ip:port] --type=[type] \ + --op=[op] [--name=[name] --value=[value]] + + Perform specific DB operation [op] for + discovery portal. It could be one of: + [new], [delete], [update] or [show]. In case of + [update], you have to provide [name] and [value] + you wish to update + + op=NEW will create a new discovery record + using the iscsid.conf discovery settings. If it + already exists, it will be overwritten using + iscsid.conf discovery settings. + + op=DELETE will delete the discovery record + and records for the targets found through + that discovery source. + + op=SHOW will display the discovery record + values. The --show argument can be used to + force the CHAP passwords to be displayed. + +Mode "discovery" +---------------- + + -m discovery --type=[type] --interface=iscsi_ifacename \ + --portal=[ip:port] --login --print=[N] \ + --op=[op]=[NEW | UPDATE | DELETE | NONPERSISTENT] + + perform [type] discovery for target portal with + ip-address [ip] and port [port]. + + This command will not use the discovery record + settings. It will use the iscsid.conf discovery + settings and it will overwrite the discovery + record with iscsid.conf discovery settings if it + exists. By default, it will then remove records for + portals no longer returned. And, + if a portal is returned by the target, then the + discovery command will create a new record or modify + an existing one with values from iscsi.conf and the + command line. + + [op] can be passed in multiple times to this + command, and it will alter the DB manipulation. + + If [op] is passed in and the value is + "new", iscsiadm will add records for portals that do + not yet have records in the db. + + If [op] is passed in and the value is + "update", iscsiadm will update node records using + info from iscsi.conf and the command line for portals + that are returned during discovery and have + a record in the db. + + If [op] is passed in and the value is "delete", + iscsiadm will delete records for portals that + were not returned during discovery. + + If [op] is passed in and the value is + "nonpersistent", iscsiadm will not store + the portals found in the node DB. + + See the example section for more info. + + See below for how to setup iscsi ifaces for + software iscsi or override the system defaults. + + Multiple ifaces can be passed in during discovery. + + -m discovery --print=[N] + + display all discovery records from internal + persistent discovery database. + +Mode "node" +----------- + + -m node display all discovered nodes from internal + persistent discovery database + + -m node --targetname=[name] --portal=[ip:port] \ + --interface=iscsi_ifacename] \ + [--login|--logout|--rescan|--stats] + -m node --targetname=[name] --portal=[ip:port] + --interface=[driver,HWaddress] \ + --op=[op] [--name=[name] --value=[value]] + -m node --targetname=[name] --portal=[ip:port] + --interface=iscsi_ifacename] \ + --print=[level] + + perform specific DB operation [op] for specific + interface on host that will connect to portal on + target. targetname, portal and interface are optional. + See below for how to setup iscsi ifaces for + software iscsi or override the system defaults. + + op could be one of: + [new], [delete], [update] or [show]. In case of + [update], you have to provide [name] and [value] + you wish to update. + [delete] - Note that if a session is using the + node record, the session will be logged out then + the record will be deleted. + + --rescan will perform a SCSI layer scan of the session + to find new LUNs. + + --stats prints the iSCSI stats for the session. + + Print level can be 0 to 1. + + -m node --logoutall=[all|manual|automatic] + Logout "all" the running sessions or just the ones + with a node startup value manual or automatic. + Nodes marked as ONBOOT are skipped. + + -m node --loginall=[all|manual|automatic] + Login "all" the running sessions or just the ones + with a node startup value manual or automatic. + Nodes marked as ONBOOT are skipped. + +Mode "session" +-------------- + + -m session display all active sessions and connections + + -m session --sid=[sid] [ --print=level | --rescan | --logout ] + --op=[op] [--name=[name] --value=[value]] + + perform operation for specific session with + session id sid. If no sid is given, the operation + will be performed on all running sessions if possible. + --logout and --op work like they do in node mode, + but in session mode targetname and portal info + is not passed in. + + Print level can be 0 to 2. + 1 = Print basic session info like node we are + connected to and whether we are connected. + 2 = Print iscsi params used. + 3 = Print SCSI info like LUNs, device state. + + If no sid and no operation is given print out the + running sessions. + +Mode "iface" +------------ + + -m iface --interface=iscsi_ifacename --op=[op] [--name=[name] --value=[value]] + --print=level + + perform operation on given interface with name + iscsi_ifacename. + + See below for examples. + + -m iface --interface=iscsi_ifacename -C ping --ip=[ipaddr] --packetsize=[size] + --count=[count] --interval=[interval] + +Mode "host" +----------- + + -m host [--host=hostno|MAC] --print=level -C chap --op=[SHOW] + + Display information for a specific host. The host + can be passed in by host number or by MAC address. + If a host is not passed in, then info + for all hosts is printed. + + Print level can be 0 to 4. + 1 = Print info for how like its state, MAC, and + netinfo if possible. + 2 = Print basic session info for nodes the host + is connected to. + 3 = Print iscsi params used. + 4 = Print SCSI info like LUNs, device state. + + -m host --host=hostno|MAC -C chap --op=[DELETE] --index=[chap_tbl_idx] + + Delete chap entry at the given index from chap table. + + -m host --host=hostno|MAC -C chap --op=[NEW | UPDATE] --index=[chap_tbl_idx] \ + --name=[name] --value=[value] + + Add new or update existing chap entry at the given + index with given username and password pair. If index + is not passed then entry is added at the first free + index in chap table. + + -m host --host=hostno|MAC -C flashnode + + Display list of all the targets in adapter's + flash (flash node), for the specified host, + with ip, port, tpgt and iqn. + + -m host --host=hostno|MAC -C flashnode --op=[NEW] --portal_type=[ipv4|ipv6] + + Create new flash node entry for the given host of the + specified portal_type. This returns the index of the + newly created entry on success. + + -m host --host=hostno|MAC -C flashnode --index=[flashnode_index] \ + --op=[UPDATE] --name=[name] --value=[value] + + Update the params of the speficied flash node. + The [name] and [value] pairs must be provided for the + params that need to be updated. Multiple params can + be updated using a single command. + + -m host --host=hostno|MAC -C flashnode --index=[flashnode_index] \ + --op=[SHOW | DELETE | LOGIN | LOGOUT] + + op=DELETE|LOGIN|LOGOUT will perform deletion/login/ + logout operation on the specified flash node. + + op=SHOW will list all params with the values for the + specified flash node. This is the default operation. + + See the iscsiadm example section for more info. + +Other arguments +--------------- + + -d, --debug debuglevel print debugging information + + -V, --version display version and exit + + -h, --help display this help and exit + + +5.1 iSCSI iface setup +===================== + +The next sections describe how to setup iSCSI ifaces so you can bind +a session to a NIC port when using software iscsi (section 5.1.1), and +it describes how to setup ifaces for use with offload cards from Chelsio +and Broadcom (section 5.1.2). + + +5.1.1 How to setup iSCSI interfaces (iface) for binding +======================================================= + +If you wish to allow the network susbsystem to figure out +the best path/NIC to use, then you can skip this section. For example +if you have setup your portals and NICs on different subnets, then +the following is not needed for software iscsi. + +Warning!!!!!! +This feature is experimental. The interface may change. When reporting +bugs, if you cannot do a "ping -I ethX target_portal", then check your +network settings first. Make sure the rp_filter setting is set to 0 or 2 +(see Prep section below for more info). If you cannot ping the portal, +then you will not be able to bind a session to a NIC. + +What is a scsi_host and iface for software, hardware and partial +offload iscsi? + +Software iscsi, like iscsi_tcp and iser, allocates a scsi_host per session +and does a single connection per session. As a result +/sys/class_scsi_host and /proc/scsi will report a scsi_host for +each connection/session you have logged into. Offload iscsi, like +Chelsio cxgb3i, allocates a scsi_host for each PCI device (each +port on a HBA will show up as a different PCI device so you get +a scsi_host per HBA port). + +To manage both types of initiator stacks, iscsiadm uses the interface (iface) +structure. For each HBA port or for software iscsi for each network +device (ethX) or NIC, that you wish to bind sessions to you must create +a iface config /etc/iscsi/ifaces. + +Prep +---- + +The iface binding feature requires the sysctl setting +net.ipv4.conf.default.rp_filter to be set to 0 or 2. +This can be set in /etc/sysctl.conf by having the line: + net.ipv4.conf.default.rp_filter = N + +where N is 0 or 2. Note that when setting this you may have to reboot +for the value to take effect. + + +rp_filter information from Documentation/networking/ip-sysctl.txt: + +rp_filter - INTEGER + 0 - No source validation. + 1 - Strict mode as defined in RFC3704 Strict Reverse Path + Each incoming packet is tested against the FIB and if the interface + is not the best reverse path the packet check will fail. + By default failed packets are discarded. + 2 - Loose mode as defined in RFC3704 Loose Reverse Path + Each incoming packet's source address is also tested against the FIB + and if the source address is not reachable via any interface + the packet check will fail. + +Running +------- + +The command + iscsiadm -m iface + +will report iface configurations that are setup in /etc/iscsi/ifaces: + + iface0 qla4xxx,00:c0:dd:08:63:e8,20.15.0.7,default,iqn.2005-06.com.redhat:madmax + iface1 qla4xxx,00:c0:dd:08:63:ea,20.15.0.9,default,iqn.2005-06.com.redhat:madmax + +The format is: + iface_name transport_name,hwaddress,ipaddress,net_ifacename,initiatorname + +For software iscsi, you can create the iface configs by hand, but it is +recommended that you use iscsiadm's iface mode. There is an iface.example in +/etc/iscsi/ifaces which can be used as a template for the daring. + +For each network object you wish to bind a session to, you must create +a separate iface config in /etc/iscsi/ifaces and each iface config file +must have a unique name which is less than or equal to 64 characters. + +Example +------- + +If you have NIC1 with MAC address 00:0F:1F:92:6B:BF and NIC2 with +MAC address 00:C0:DD:08:63:E7, and you wanted to do software iscsi over +TCP/IP, then in /etc/iscsi/ifaces/iface0 you would enter: + + iface.transport_name = tcp + iface.hwaddress = 00:0F:1F:92:6B:BF + +and in /etc/iscsi/ifaces/iface1 you would enter: + + iface.transport_name = tcp + iface.hwaddress = 00:C0:DD:08:63:E7 + +Warning: Do not name an iface config file "default" or "iser". +They are special values/files that are used by the iscsi tools for +backward compatibility. If you name an iface default or iser, then +the behavior is not defined. + +To use iscsiadm to create an iface0 similar to the above example, run: + iscsiadm -m iface -I iface0 --op=new + +(This will create a new empty iface config. If there was already an iface +with the name "iface0", this command will overwrite it.) + +Next, set the hwaddress: + iscsiadm -m iface -I iface0 --op=update \ + -n iface.hwaddress -v 00:0F:1F:92:6B:BF + +If you had sessions logged in, iscsiadm will not update or overwrite +an iface. You must log out first. If you have an iface bound to a node/portal +but you have not logged in, then iscsiadm will update the config and +all existing bindings. + +You should now skip to 5.1.3 to see how to log in using the iface, and for +some helpful management commands. + + +5.1.2 Setting up an iface for an iSCSI offload card +=================================================== + +This section describes how to setup ifaces for use with Chelsio, Broadcom and +QLogic cards. + +By default, iscsiadm will create an iface for each Broadcom, QLogic and Chelsio +port. The iface name will be of the form: + $transport/driver_name.$MAC_ADDRESS + +Running the following command: + iscsiadm -m iface + +will report iface configurations that are setup in /etc/iscsi/ifaces: + + default tcp,,,, + iser iser,,,, + cxgb3i.00:07:43:05:97:07 cxgb3i,00:07:43:05:97:07,,, + qla4xxx.00:0e:1e:04:8b:2e qla4xxx,00:0e:1e:04:8b:2e,,, + +The format is: + iface_name transport_name,hwaddress,ipaddress,net_ifacename,initiatorname + +iface_name: name of iface +transport_name: name of driver +hwaddress: MAC address +ipaddress: IP address to use for this port +net_iface_name: will be because change between reboots. + It is used for software iSCSI's vlan or alias binding. +initiatorname: Initiatorname to be used if you want to override the + default one in /etc/iscsi/initiatorname.iscsi. + +To display these values in a more friendly way, run: + iscsiadm -m iface -I cxgb3i.00:07:43:05:97:07 + +Example output: + # BEGIN RECORD 2.0-871 + iface.iscsi_ifacename = cxgb3i.00:07:43:05:97:07 + iface.net_ifacename = + iface.ipaddress = + iface.hwaddress = 00:07:43:05:97:07 + iface.transport_name = cxgb3i + iface.initiatorname = + # END RECORD + +Before you can use the iface, you must set the IP address for the port. +We determine the corresponding variable name that we want to update from +the output above, which is "iface.ipaddress". +Then we fill this empty variable with the value we desire, with this command: + iscsiadm -m iface -I cxgb3i.00:07:43:05:97:07 -o update \ + -n iface.ipaddress -v 20.15.0.66 + +Note for QLogic ports: After updating the iface record, you must apply or +applyall the settings for the changes to take effect: + + iscsiadm -m iface -I qla4xxx.00:0e:1e:04:8b:2e -o apply + iscsiadm -m iface -H 00:0e:1e:04:8b:2e -o applyall + +With "apply", the network settings for the specified iface will take effect. +With "applyall", the network settings for all ifaces on a specific host will +take effect. The host can be specified using the -H/--host argument by either +the MAC address of the host or the host number. + +Here is an example of setting multiple IPv6 addresses on a single iSCSI +interface port. +First interface (no need to set iface_num, it is 0 by default): + iscsiadm -m iface -I qla4xxx.00:0e:1e:04:8b:2a -o update \ + -n iface.ipaddress -v fec0:ce00:7014:0041:1111:2222:1e04:9392 + +Create the second interface if it does not exist (iface_num is mandatory here): + iscsiadm -m iface -I qla4xxx.00:0e:1e:04:8b:2a.1 -op=new + iscsiadm -m iface -I qla4xxx.00:0e:1e:04:8b:2a -o update \ + -n iface.iface_num -v 1 + iscsiadm -m iface -I qla4xxx.00:0e:1e:04:8b:2a -o update \ + -n iface.ipaddress -v fec0:ce00:7014:0041:1111:2222:1e04:9393 + iscsiadm -m iface -H 00:0e:1e:04:8b:2a --op=applyall + +Note: If there are common settings for multiple interfaces then the +settings from 0th iface would be considered valid. + +Now, we can use this iface to login into targets, which is described in the +next section. + + +5.1.3 Discoverying iSCSI targets/portals +======================================== + +Be aware that iscsiadm will use the default route to do discovery. It will +not use the iface specified. So if you are using an offload card, you will +need a separate network connection to the target for discovery purposes. +*This will be fixed in the next version of open-iscsi* + +For compatibility reasons, when you run iscsiadm to do discovery, it +will check for interfaces in /etc/iscsi/iscsi/ifaces that are using +tcp for the iface.transport, and it will bind the portals that are discovered +so that they will be logged in through those ifaces. This behavior can also +be overridden by passing in the interfaces you want to use. For the case +of offload, like with cxgb3i and bnx2i, this is required because the transport +will not be tcp. + +For example if you had defined two interfaces but only wanted to use one, +you can use the --interface/-I argument: + iscsiadm -m discoverydb -t st -p ip:port -I iface1 --discover -P 1 + +If you had defined interfaces but wanted the old behavior, where we do not +bind a session to an iface, then you can use the special iface "default": + iscsiadm -m discoverydb -t st -p ip:port -I default --discover -P 1 + +And if you did not define any interfaces in /etc/iscsi/ifaces and do +not pass anything into iscsiadm, running iscsiadm will do the default +behavior, allowing the network subsystem to decide which device to use. + +If you later want to remove the bindings for a specific target and +iface, then you can run: + iscsiadm -m node -T my_target -I iface0 --op=delete + +To do this for a specific portal on a target, run: + iscsiadm -m node -T my_target -p ip:port -I iface0 --op=delete + +If you wanted to delete all bindinds for iface0, then you can run: + iscsiadm -m node -I iface0 --op=delete + +And for equalogic targets it is sometimes useful to remove just by portal: + iscsiadm -m node -p ip:port -I iface0 --op=delete + + +Now logging into targets is the same as with software iscsi. See section 7 +for how to get started. + + +5.2 iscsiadm examples +===================== + +Usage examples using the one-letter options (see iscsiadm man page +for long options): + +Discovery mode +-------------- + +- SendTargets iSCSI Discovery using the default driver and interface and + using the discovery settings for the discovery record with the + ID [192.168.1.1:3260]: + iscsiadm -m discoverydb -t st -p 192.168.1.1:3260 --discover + + This will search /etc/iscsi/send_targets for a record with the + ID [portal = 192.168.1.1:3260 and type = sendtargets. If found it + will perform discovery using the settings stored in the record. + If a record does not exist, it will be created using the iscsid.conf + discovery settings. + + The argument to -p may also be a hostname instead of an address: + iscsiadm -m discoverydb -t st -p somehost --discover + + For the ifaces, iscsiadm will first search /etc/iscsi/ifaces for + interfaces using software iscsi. If any are found then nodes found + during discovery will be setup so that they can logged in through + those interfaces. To specify a specific iface, pass the + -I argument for each iface. + +- SendTargets iSCSI Discovery updating existing target records: + iscsiadm -m discoverydb -t sendtargets -p 192.168.1.1:3260 \ + -o update --discover + + If there is a record for targetX, and portalY exists in the DB, and + is returned during discovery, it will be updated with the info from + the iscsi.conf. No new portals will be added and stale portals + will not be removed. + +- SendTargets iSCSI Discovery deleting existing target records: + iscsiadm -m discoverydb -t sendtargets -p 192.168.1.1:3260 \ + -o delete --discover + + If there is a record for targetX, and portalY exists in the DB, but + is not returned during discovery, it will be removed from the DB. + No new portals will be added and existing portal records will not + be changed. + + Note: If a session is logged into portal we are going to delete + a record for, it will be logged out then the record will be + deleted. + +- SendTargets iSCSI Discovery adding new records: + iscsiadm -m discoverydb -t sendtargets -p 192.168.1.1:3260 \ + -o new --discover + + If there is targetX, and portalY is returned during discovery, and does + not have a record, it will be added. Existing records are not modified. + +- SendTargets iSCSI Discovery using multiple ops: + iscsiadm -m discoverydb -t sendtargets -p 192.168.1.1:3260 \ + -o new -o delete --discover + + This command will add new portals and delete records for portals + no longer returned. It will not change the record information for + existing portals. + +- SendTargets iSCSI Discovery in nonpersistent mode: + iscsiadm -m discoverydb -t sendtargets -p 192.168.1.1:3260 \ + -o nonpersistent --discover + + This command will perform discovery, but not manipulate the node DB. + +- SendTargets iSCSI Discovery with a specific interface. + If you wish to only use a subset of the interfaces in + /etc/iscsi/ifaces, then you can pass them in during discovery: + iscsiadm -m discoverydb -t sendtargets -p 192.168.1.1:3260 \ + --interface=iface0 --interface=iface1 --discover + + + Note that for software iscsi, we let the network layer select + which NIC to use for discovery, but for later logins iscsiadm + will use the NIC defined in the iface config. + + qla4xxx support is very basic and experimental. It does not store + the record info in the card's FLASH or the node DB, so you must + rerun discovery every time the driver is reloaded. + +- Manipulate SendTargets DB: Create new SendTargets discovery record or + overwrite an existing discovery record with iscsid.conf + discovery settings: + iscsiadm -m discoverydb -t sendtargets -p 192.168.1.1:3260 -o new + +- Manipulate SendTargets DB: Display discovery settings: + iscsiadm -m discoverydb -t sendtargets -p 192.168.1.1:3260 -o show + +- Manipulate SendTargets DB: Display hidden discovery settings like + CHAP passwords: + iscsiadm -m discoverydb -t sendtargets -p 192.168.1.1:3260 \ + -o show --show + +- Manipulate SendTargets DB: Set discovery setting. + iscsiadm -m discoverydb -t sendtargets -p 192.168.1.1:3260 \ + -o update -n name -v value + +- Manipulate SendTargets DB: Delete discovery record. This will also delete + the records for the targets found through the discovery source. + iscsiadm -m discoverydb -t sendtargets -p 192.168.1.1:3260 -o delete + +- Show all records in discovery database: + iscsiadm -m discovery + +- Show all records in discovery database and show the targets that were + discovered from each record: + iscsiadm -m discovery -P 1 + +Node mode +--------- + +In node mode you can specify which records you want to log +into by specifying the targetname, ip address, port or interface +(if specifying the interface it must already be setup in the node db). +iscsiadm will search the node db for records which match the values +you pass in, so if you pass in the targetname and interface, iscsiadm +will search for records with those values and operate on only them. +Passing in none of them will result in all node records being operated on. + +- iSCSI Login to all portals on every node/starget through each interface + set in the db: + iscsiadm -m node -l + +- iSCSI login to all portals on a node/target through each interface set + in the db: + iscsiadm -m node -T iqn.2005-03.com.max -l + +- iSCSI login to a specific portal through each interface set in the db: + iscsiadm -m node -T iqn.2005-03.com.max -p 192.168.0.4:3260 -l + + To specify an iPv6 address, the following can be used: + iscsiadm -m node -T iqn.2005-03.com.max \ + -p 2001:c90::211:9ff:feb8:a9e9 -l + + The above command would use the default port, 3260. To specify a + port, use the following: + iscsiadm -m node -T iqn.2005-03.com.max \ + -p [2001:c90::211:9ff:feb8:a9e9]:3260 -l + + To specify a hostname, the following can be used: + iscsiadm -m node -T iqn.2005-03.com.max -p somehost -l + +- iSCSI Login to a specific portal through the NIC setup as iface0: + iscsiadm -m node -T iqn.2005-03.com.max -p 192.168.0.4:3260 \ + -I iface0 -l + +- iSCSI Logout of all portals on every node/starget through each interface + set in the db: + iscsiadm -m node -u + + Warning: this does not check startup values like the logout/login all + option. Do not use this if you are running iscsi on your root disk. + +- iSCSI logout of all portals on a node/target through each interface set + in the db: + iscsiadm -m node -T iqn.2005-03.com.max -u + +- iSCSI logout of a specific portal through each interface set in the db: + iscsiadm -m node -T iqn.2005-03.com.max -p 192.168.0.4:3260 -u + +- iSCSI Logout of a specific portal through the NIC setup as iface0: + iscsiadm -m node -T iqn.2005-03.com.max -p 192.168.0.4:3260 \ + -I iface0 + +- Changing iSCSI parameter: + iscsiadm -m node -T iqn.2005-03.com.max -p 192.168.0.4:3260 \ + -o update -n node.cnx[0].iscsi.MaxRecvDataSegmentLength -v 65536 + + You can also change parameters for multiple records at once, by + specifying different combinations of target, portal and interface + like above. + +- Adding custom iSCSI portal: + iscsiadm -m node -o new -T iqn.2005-03.com.max \ + -p 192.168.0.1:3260,2 -I iface4 + + The -I/--interface is optional. If not passed in, "default" is used. + For tcp or iser, this would allow the network layer to decide what is + best. + + Note that for this command, the Target Portal Group Tag (TPGT) should + be passed in. If it is not passed in on the initial creation command, + then the user must run iscsiadm again to set the value. Also, + if the TPGT is not initially passed in, the old behavior of not + tracking whether the record was statically or dynamically created + is used. + +- Adding custom NIC config to multiple targets: + iscsiadm -m node -o new -I iface4 + + This command will add an interface config using the iSCSI and SCSI + settings from iscsid.conf to every target that is in the node db. + +- Removing iSCSI portal: + iscsiadm -m node -o delete -T iqn.2005-03.com.max -p 192.168.0.4:3260 + + You can also delete multiple records at once, by specifying different + combinations of target, portal and interface like above. + +- Display iSCSI portal onfiguration: + iscsiadm -m node [-o show] -T iqn.2005-03.com.max -p 192.168.0.4:3260 + + You can also display multiple records at once, by specifying different + combinations of target, portal and interface like above. + + Note: running "iscsiadm -m node" will only display the records. It + will not display the configuration info. For the latter, run: + iscsiadm -m node -o show + +- Show all node records: + iscsiadm -m node + + This will print the nodes using the old flat format where the + interface and driver are not displayed. To display that info + use the -P option with the argument "1": + iscsiadm -m node -P 1 + +Session mode +------------ + +- Display session statistics: + iscsiadm -m session -r 1 --stats + + This function also works in node mode. Instead of the "-r $sid" + argument, you would pass in the node info like targetname and/or portal, + and/or interface. + +- Perform a SCSI scan on a session + iscsiadm -m session -r 1 --rescan + + This function also works in node mode. Instead of the "-r $sid" + argument, you would pass in the node info like targetname and/or portal, + and/or interface. + + Note: Rescanning does not delete old LUNs. It will only pick up new + ones. + +- Display running sessions: + iscsiadm -m session -P 1 + +Host mode with flashnode submode +-------------------------------- + +- Display list of flash nodes for a host + iscsiadm -m host -H 6 -C flashnode + + This will print list of all the flash node entries for the given host + along with their ip, port, tpgt and iqn values. + +- Display all parameters of a flash node entry for a host + iscsiadm -m host -H 6 -C flashnode -x 0 + + This will list all the parameter name,value pairs for the + flash node entry at index 0 of host 6. + +- Add a new flash node entry for a host + iscsiadm -m host -H 6 -C flashnode -o new -A [ipv4|ipv6] + + This will add new flash node entry for the given host 6 with portal + type of either ipv4 or ipv6. The new operation returns the index of + the newly created flash node entry. + +- Update a flashnode entry + iscsiadm -m host -H 6 -C flashnode -x 1 -o update \ + -n flashnode.conn[0].ipaddress -v 192.168.1.12 \ + -n flashnode.session.targetname \ + -v iqn.2002-03.com.compellent:5000d310004b0716 + + This will update the values of ipaddress and targetname params of + the flash node entry at index 1 of host 6. + +- Login to a flash node entry + iscsiadm -m host -H 6 -C flashnode -x 1 -o login + +- Logout from a flash node entry + Logout can be performed either using the flash node index: + iscsiadm -m host -H 6 -C flashnode -x 1 -o logout + + or by using the corresponding session index: + iscsiadm -m session -r $sid -u + +- Delete a flash node entry + iscsiadm -m host -H 6 -C flashnode -x 1 -o delete + +Host mode with chap submode +--------------------------- + +- Display list of chap entries for a host + iscsiadm -m host -H 6 -C chap -o show + +- Delete a chap entry for a host + iscsiadm -m host -H 6 -C chap -o delete -x 5 + + This will delete any chap entry present at index 5. + +- Add/Update a local chap entry for a host + iscsiadm -m host -H 6 -C chap -o update -x 4 -n username \ + -v value -n password -v value + + This will update the local chap entry present at index 4. If index 4 + is free, then a new entry of type local chap will be created at that + index with given username and password values. + +- Add/Update a bidi chap entry for a host + iscsiadm -m host -H 6 -C chap -o update -x 5 -n username_in \ + -v value -n password_in -v value + + This will update the bidi chap entry present at index 5. If index 5 + is free then entry of type bidi chap will be created at that index + with given username_in and password_in values. + +Host mode with stats submode +---------------------------- + +- Display host statistics: + iscsiadm -m host -H 6 -C stats + + This will print the aggregate statistics on the host adapter port. + This includes MAC, TCP/IP, ECC & iSCSI statistics. + + +6. Configuration +================ + +The default configuration file is /etc/iscsi/iscsid.conf. This file contains +only configuration that could be overwritten by iSCSI Discovery, +or manualy updated via iscsiadm utility. Its OK if this file does not +exist, in which case compiled-in default configuration will take place +for newer discovered Target nodes. + +See the man page and the example file for the current syntax. +The manpages for iscsid, iscsiadm are in the doc subdirectory and can be +installed in the appropriate man page directories and need to be manually +copied into e.g. /usr/local/share/man8. + + +7. Getting Started +================== + +There are three steps needed to set up a system to use iSCSI storage: +7.1. iSCSI startup using the init script or manual startup. +7.2. Discover targets. +7.3. Automate target logins for future system reboots. + +The init scripts will start the iSCSI daemon and log into any +portals that are set up for automatic login (discussed in 7.2) +or discovered through the discover daemon iscsid.conf params +(discussed in 7.1.2). + +If your distro does not have an init script, then you will have to start the +daemon and log into the targets manually. + + +7.1.1 iSCSI startup using the init script +========================================= + +Red Hat or Fedora: +----------------- +To start open-iscsi in Red Hat/Fedora you can do: + + service open-iscsi start + +To get open-iscsi to automatically start at run time you may have to +run: + chkconfig --level open-iscsi on +Where are the run levels. + +And, to automatically mount a file system during startup +you must have the partition entry in /etc/fstab marked with the "_netdev" +option. For example this would mount an iscsi disk sdb: + + /dev/sdb /mnt/iscsi ext3 _netdev 0 0 + +SUSE or Debian: +--------------- +Otherwise, if there is an initd script for your distro in etc/initd that +gets installed with "make install" + + /etc/init.d/open-iscsi start + +will usually get you started. + + +7.1.2 Manual Startup +==================== + +7.1.2.1 Starting up the iSCSI daemon (iscsid) and loading modules +================================================================= + +If there is no initd script, you must start the tools by hand. First load the +iscsi modules: + modprobe -q iscsi_tcp + +After that, start iSCSI daemon process: + ./iscsid + +or alternatively, start it with debug enabled and with output +redirected to the current console: + ./iscsid -d 8 -f & + + +7.1.2.2 Logging into Targets +============================ + +Use the configuration utility, iscsiadm, to add/remove/update Discovery +records, iSCSI Node records or monitor active iSCSI sessions (see above or the +iscsiadm man files and see section 7.2 below for how to discover targets): + ./iscsiadm -m node + +will print out the nodes that have been discovered as: + + 10.15.85.19:3260,3 iqn.1992-08.com.netapp:sn.33615311 + 10.15.84.19:3260,2 iqn.1992-08.com.netapp:sn.33615311 + +The format is: + ip:port,target_portal_group_tag targetname + +If you are using the iface argument or want to see the driver +info, use the following: + ./iscsiadm -m node -P 1 + +Example output: + Target: iqn.1992-08.com.netapp:sn.33615311 + Portal: 10.15.84.19:3260,2 + Iface Name: iface2 + Portal: 10.15.85.19:3260,3 + Iface Name: iface2 + +The format is: + Target: targetname + Portal ip_address:port,tpgt + Iface: ifacename + +where targetname is the name of the target and ip_address:port is the address +and port of the portal. tpgt is the Target Portal Group Tag of +the portal, and is not used in iscsiadm commands except for static +record creation. ifacename is the name of the iscsi interface +defined in /etc/iscsi/ifaces. If no interface was defined in +/etc/iscsi/ifaces or passed in, the default behavior is used. +Default here is iscsi_tcp/tcp to be used over whichever NIC the +network layer decides is best. + +To login, take the ip, port and targetname from above and run: + ./iscsiadm -m node -T targetname -p ip:port -l + +In this example we would run: + ./iscsiadm -m node -T iqn.1992-08.com.netapp:sn.33615311 \ + -p 10.15.84.19:3260 -l + + Note: drop the portal group tag from the "iscsiadm -m node" output. + + +7.2. Discover Targets +===================== + +Once the iSCSI service is running, you can perform discovery using +SendTarget with: + iscsiadm -m discoverydb -t sendtargets -p ip:port --discover + +where "ip" is the address of the portal and port is the port. + +To use iSNS you can run the discovery command with the type as "isns" +and pass in the ip:port: + iscsiadm -m discoverydb -t isns -p ip:port --discover + +Both commands will print out the list of all discovered targets and their +portals: + + # iscsiadm -m discoverydb -t st -p 10.15.85.19:3260 --discover + 10.15.84.19:3260,2 iqn.1992-08.com.netapp:sn.33615311 + 10.15.85.19:3260,3 iqn.1992-08.com.netapp:sn.33615311 + +The format for the output is: + ip:port,tpgt targetname + +In this example, for the first target the ip address is 10.15.85.19. +The port is 3260. The target portal group is 3. The target name +is iqn.1992-08.com.netapp:sn.33615311. + +If you would also like to see the iscsi inteface which will be used +for each session then use the --print=[N]/-P [N] option: + iscsiadm -m discoverydb -t sendtargets -p ip:port -P 1 --discover + +will print: + Target: iqn.1992-08.com.netapp:sn.33615311 + Portal: 10.15.84.19:3260,2 + Iface Name: iface2 + Portal: 10.15.85.19:3260,3 + Iface Name: iface2 + +In this example, The IP address of the first portal is 10.15.84.19. +The port is 3260. The target portal group is 3. The target name +is iqn.1992-08.com.netapp:sn.33615311. The iface being used is iface2. + +While discovery targets are kept in the discovery db, they are +useful only for re-discovery. The discovered targets (a.k.a. nodes) +are stored as records in the node db. + +The discovered targets are not logged into yet. Rather than logging +into the discovered nodes (making LUs from those nodes available as +storage), it is better to automate the login to the nodes we need. + +If you wish to log into a target manually now, see section +"7.1.2.2 Logging in targets" above. + + +7.3. Automate Target Logins for Future System Statups +===================================================== + +Note: this may only work for distros with init scripts. + +To automate login to a node, use the following with the record ID +(record ID is the targetname and portal) of the node discovered in the +discovery above: + iscsiadm -m node -T targetname -p ip:port --op update -n node.startup -v automatic + +To set the automatic setting to all portals on a target through every +interface setup for each protal, the following can be run: + iscsiadm -m node -T targetname --op update -n node.startup -v automatic + +Or to set the "node.startup" attribute to "automatic" as default for +all sessions add the following to the /etc/iscsi/iscsid.conf: + node.startup = automatic + +Setting this in iscsid.conf will not affect existing nodes. It will only +affect nodes that are discovered after setting the value. + +To login to all automated nodes, simply restart the iscsi service, e.g. with: + /etc/init.d/open-iscsi restart + +On your next startup the nodes will be logged into automatically. + + +7.4 Automatic Discovery and Login +================================= + +Instead of running the iscsiadm discovery command and editing the +startup setting, iscsid can be configured so that every X seconds +it performs discovery and logs in and out of the portals returned or +no longer returned. In this mode, when iscsid starts it will check the +discovery db for iSNS records with: + discovery.isns.use_discoveryd = Yes + +and it will check for SendTargets discovery records that have the setting: + discovery.sendtargets.use_discoveryd = Yes + +If set, iscsid will perform discovery to the address every +discovery.isns.discoveryd_poll_inval or +discovery.sendtargets.discoveryd_poll_inval seconds, +and it will log into any portals found from the discovery source using +the ifaces in /etc/iscsi/ifaces. + +Note that for iSNS the poll_interval does not have to be set. If not set, +iscsid will only perform rediscovery when it gets a SCN from the server. + +# iSNS Note: +# For servers like Microsoft's where they allow SCN registrations, but do not +# send SCN events, discovery.isns.poll_interval should be set to a non zero +# value to auto discover new targets. This is also useful for servers like +# linux-isns (SLES's iSNS server) where it sometimes does not send SCN +# events in the proper format, so they may not get handled. + +Examples +-------- + +SendTargets +----------- + +- Create a SendTargets record by passing iscsiadm the "-o new" argument in + discoverydb mode. + iscsiadm -m discoverydb -t st -p 20.15.0.7:3260 -o new + + On success, this will output something like: + New discovery record for [20.15.0.7,3260] added. + +- Set the use_discoveryd setting for the record. + iscsiadm -m discoverydb -t st -p 20.15.0.7:3260 -o update \ + -n discovery.sendtargets.use_discoveryd -v Yes + +- Set the polling interval. + iscsiadm -m discoverydb -t st -p 20.15.0.7:3260 -o update \ + -n discovery.sendtargets.discoveryd_poll_inval -v 30 + +To have the new settings take effect, restart iscsid by restarting the +iscsi service. + +Note: +When iscsiadm is run with the -o new argument, it will use the +discovery.sendtargets.use_discoveryd and +discovery.sendtargets.discoveryd_poll_inval +settings in iscsid.conf for the records initial settings. So if those +are set in iscsid.conf, then you can skip the iscsiadm -o update +commands. + +iSNS +---- + +- Create an iSNS record by passing iscsiadm the "-o new" argument in + discoverydb mode. + iscsiadm -m discoverydb -t isns -p 20.15.0.7:3205 -o new + + Response on success: + New discovery record for [20.15.0.7,3205] added. + +- Set the use_discoveryd setting for the record. + iscsiadm -m discoverydb -t isns -p 20.15.0.7:3205 -o update \ + -n discovery.isns.use_discoveryd -v Yes + +- [OPTIONAL: see iSNS note above] Set the polling interval if needed. + iscsiadm -m discoverydb -t st -p 20.15.0.7:3205 -o update \ + -n discovery.isns.discoveryd_poll_inval -v 30 + +To have the new settings take effect, restart iscsid by restarting the +iscsi service. + +Note: +When iscsiadm is run with the -o new argument, it will use the +discovery.isns.use_discoveryd and discovery.isns.discoveryd_poll_inval +settings in iscsid.conf for the record's initial settings. So if those +are set in iscsid.conf, then you can skip the iscsiadm -o update +commands. + + +8. Advanced Configuration +========================= + +8.1 iSCSI settings for dm-multipath +=================================== + +When using dm-multipath, the iSCSI timers should be set so that commands +are quickly failed to the dm-multipath layer. For dm-multipath you should +then set values like queue if no path, so that IO errors are retried and +queued if all paths are failed in the multipath layer. + + +8.1.1 iSCSI ping/Nop-Out settings +================================= +To quickly detect problems in the network, the iSCSI layer will send iSCSI +pings (iSCSI NOP-Out requests) to the target. If a NOP-Out times out, the +iSCSI layer will respond by failing running commands and asking the SCSI +layer to requeue them if possible (SCSI disk commands get 5 retries if not +using multipath). If dm-multipath is being used the SCSI layer will fail +the command to the multipath layer instead of retrying. The multipath layer +will then retry the command on another path. + +To control how often a NOP-Out is sent, the following value can be set: + node.conn[0].timeo.noop_out_interval = X + +Where X is in seconds and the default is 10 seconds. To control the +timeout for the NOP-Out the noop_out_timeout value can be used: + node.conn[0].timeo.noop_out_timeout = X + +Again X is in seconds and the default is 15 seconds. + +Normally for these values you can use: + + node.conn[0].timeo.noop_out_interval = 5 + node.conn[0].timeo.noop_out_timeout = 10 + +If there are a lot of IO error messages, then the above values may be too +aggressive and you may need to increase the values for your network conditions +and workload, or you may need to check your network for possible problems. + + +8.1.2 replacement_timeout +========================= + +The next iSCSI timer that will need to be tweaked is: + +node.session.timeo.replacement_timeout = X + +Here X is in seconds. + +replacement_timeout will control how long to wait for session re-establishment +before failing pending SCSI commands and commands that are being operated on by +the SCSI layer's error handler up to a higher level like multipath or to +an application if multipath is not being used. + + +8.1.2.1 Running Commands, the SCSI Error Handler, and replacement_timeout +========================================================================= + +Remember from the Nop-out discussion that if a network problem is detected, +the running commands are failed immediately. There is one exception to this, +and that is when the SCSI layer's error handler is running. To check if +the SCSI error handler is running, iscsiadm can be run as: + iscsiadm -m session -P 3 + +You will then see: + Host Number: X State: Recovery + +When the SCSI EH is running, commands will not be failed until +node.session.timeo.replacement_timeout seconds. + +To modify the timer that starts the SCSI EH, you can either write +directly to the device's sysfs file: + echo X > /sys/block/sdX/device/timeout + +where X is in seconds. +Alternatively, on most distros you can modify the udev rule. + +To modify the udev rule open /etc/udev/rules.d/50-udev.rules, and find the +following lines: + + ACTION=="add", SUBSYSTEM=="scsi" , SYSFS{type}=="0|7|14", \ + RUN+="/bin/sh -c 'echo 60 > /sys$$DEVPATH/timeout'" + +And change the "echo 60" part of the line to the value that you want. + +The default timeout for normal File System commands is 30 seconds when udev +is not being used. If udev is used the default is the above value which +is normally 60 seconds. + + +8.1.2.2 Pending Commands and replacement_timeout +================================================ + +Commonly, the SCSI/BLOCK layer will queue 256 commands, but the path can +only take 32. When a network problem is detected, the 32 commands +in flight will be sent back to the SCSI layer immediately and because +multipath is being used, this will cause the commands to be sent to the multipath +layer for execution on another path. However, the other 96 commands that were +still in the SCSI/BLOCK queue will remain there until the session is +re-established or until node.session.timeo.replacement_timeout seconds has +gone by. After replacement_timeout seconds, the pending commands will be +failed to the multipath layer, and all new incoming commands will be +immediately failed back to the multipath layer. If a session is later +re-established, then new commands will be queued and executed. Normally, +multipathd's path tester mechanism will detect that the session has been +re-established and the path is accessible again, and it will inform +dm-multipath. + + +8.1.3 Optimal replacement_timeout Value +======================================= + +The default value for replacement_timeout is 120 seconds, but because +multipath's queue_if_no_path and no_path_retry setting can prevent IO errors +from being propagated to the application, replacement_timeout can be set to a +shorter value like 5 to 15 seconds. By setting it lower, pending IO is quickly +sent to a new path and executed while the iSCSI layer attempts +re-establishment of the session. If all paths end up being failed, then the +multipath and device mapper layer will internally queue IO based on the +multipath.conf settings, instead of the iSCSI layer. + + +8.2 iSCSI settings for iSCSI root +================================= + +When accessing the root partition directly through an iSCSI disk, the +iSCSI timers should be set so that iSCSI layer has several chances to try to +re-establish a session and so that commands are not quickly requeued to +the SCSI layer. Basically you want the opposite of when using dm-multipath. + +For this setup, you can turn off iSCSI pings by setting: + + node.conn[0].timeo.noop_out_interval = 0 + node.conn[0].timeo.noop_out_timeout = 0 + +And you can turn the replacement_timer to a very long value: + node.session.timeo.replacement_timeout = 86400 + + +9. iSCSI System Info +==================== + +To get information about the running sessions: including the session and +device state, session ids (sid) for session mode, and some of the +negotiated parameters, run: + iscsiadm -m session -P 2 + +If you are looking for something shorter, like just the sid to node mapping, +run: + iscsiadm -m session [-P 0] + +This will print the list of running sessions with the format: + driver [sid] ip:port,target_portal_group_tag targetname + +Example output of "iscsiadm -m session": + + tcp [2] 10.15.84.19:3260,2 iqn.1992-08.com.netapp:sn.33615311 + tcp [3] 10.15.85.19:3260,3 iqn.1992-08.com.netapp:sn.33615311 + +To print the hw address info use the -P option with "1": + iscsiadm -m session -P 1 + +This will print the sessions with the following format: +Target: targetname + Current Portal: portal currently logged into + Persistent Portal: portal we would fall back to if we had got + redirected during login + Iface Transport: driver/transport_name + Iface IPaddress: IP address of iface being used + Iface HWaddress: HW address used to bind session + Iface Netdev: netdev value used to bind session + SID: iscsi sysfs session id + iSCSI Connection State: iscsi state + +Note: if an older kernel is being used or if the session is not bound, +then the keyword "default" is printed to indicate that the default +network behavior is being used. + +Example output of "iscsiadm -m session -P 1": + Target: iqn.1992-08.com.netapp:sn.33615311 + Current Portal: 10.15.85.19:3260,3 + Persistent Portal: 10.15.85.19:3260,3 + Iface Transport: tcp + Iface IPaddress: 10.11.14.37 + Iface HWaddress: default + Iface Netdev: default + SID: 7 + iSCSI Connection State: LOGGED IN + Internal iscsid Session State: NO CHANGE + +The connection state is currently not available for qla4xxx. + +To get a HBA/Host view of the session, there is the host mode: + iscsiadm -m host + +This prints the list of iSCSI hosts in the system with the format: + driver [hostno] ipaddress,[hwaddress],net_ifacename,initiatorname + +Example output: + cxgb3i: [7] 10.10.15.51,[00:07:43:05:97:07],eth3 + +To print this info in a more user friendly way, the -P argument can be used: + iscsiadm -m host -P 1 + +Example output: + Host Number: 7 + State: running + Transport: cxgb3i + Initiatorname: + IPaddress: 10.10.15.51 + HWaddress: 00:07:43:05:97:07 + Netdev: eth3 + +Here, you can also see the state of the host. + +You can also pass in any value from 1 - 4 to print more info, like the +sessions running through the host, what ifaces are being used and what +devices are accessed through it. + +To print the info for a specific host, you can pass in the -H argument +with the host number: + iscsiadm -m host -P 1 -H 7 diff --git a/THANKS b/THANKS new file mode 100644 index 0000000..2b4dd68 --- /dev/null +++ b/THANKS @@ -0,0 +1,10 @@ +contribution: Wang Zhenyu +contribution: Albert Pauw +contribution: Arne Redlich +contribution: Christoph Hellwig +contribution: Mike Christie +contribution: Ming Zhang +packaging: Chad Tindel +testing: FUJITA Tomonori +testing: Harald Kubota +testing: Pascal Renauld diff --git a/TODO b/TODO new file mode 100644 index 0000000..7328180 --- /dev/null +++ b/TODO @@ -0,0 +1,379 @@ +iSCSI DEVELOPMENT HOWTO AND TODO +-------------------------------- +July 7th 2011 + + +If you are admin or user and just want to send a fix, just send the fix any +way you can. We can port the patch to the proper tree and fix up the patch +for you. Engineers that would like to do more advanced development then the +following guideline should be followed. + +Submitting Patches +------------------ +Code should follow the Linux kernel codying style doc: +http://www.kernel.org/doc/Documentation/CodingStyle + +Patches should be submitted to the open-iscsi list open-iscsi@googlegroups.com. +They should be made with "git diff" or "diff -up" or "diff -uprN", and +kernel patches must have a "Signed-off-by" line. See section 12 +http://www.kernel.org/doc/Documentation/SubmittingPatches for more +information on the the signed off line. + +Getting the Code +---------------- +Kernel patches should be made against the linux-2.6-iscsi tree. This can +be downloaded from kernel.org with git with the following commands: + +git clone git://git.kernel.org/pub/scm/linux/kernel/git/mnc/linux-2.6-iscsi.git + +Userspace patches should be made against the open-iscsi git tree: + +git clone git://git.kernel.org/pub/scm/linux/kernel/git/mnc/open-iscsi.git + + + +KERNEL TODO ITEMS +----------------- + +1. Make iSCSI log messages humanly readable. In many cases the iscsi tools +and modules will log a error number value. The most well known is conn +error 1011. Users should not have to search on google for what this means. + +We should: + +1. Write a simple table to convert the error values to a string and print +them out. + +2. Document the values, how you commonly hit them and common solutions +in the iSCSI docs. + +See scsi_transport_iscsi.c:iscsi_conn_error_event for where the evil +"detected conn error 1011" is printed. See the enum iscsi_err in iscsi_if.h +for a definition of the error code values. + +--------------------------------------------------------------------------- + +2. Implement iSCSI dev loss support. + +Currently if a session is down for longer than replacement/recovery_timeout +seconds, the iscsi layer will unblock the devices and fail IO. Other +transport, like FC and SAS, will do something similar. FC has a +fast_io_fail tmo which will unblock devices and fail IO, then it has a +dev_loss_tmo which will delete the devices accessed through that port. + +iSCSI needs to implement dev_loss_tmo behavior, because apps are beginning +to expect this behavior. An initial path was made here: +http://groups.google.com/group/open-iscsi/msg/031510ab4cecccfd?dmode=source + +Since all drivers want this behavior we want to make it common. We need to +change the patch in that link to add a dev_loss_tmo handler callback to the +scsi_transport_template struct, and add some common sysfs and helpers +functions to manage the dev_loss_tmo variable. + + +*Being worked on by Vikek S + +--------------------------------------------------------------------------- + +3. Reduce locking contention between session lock. + +The session lock is basically one big lock that protects everything +in the iscsi_session. This lock could be broken down into smaller locks +and maybe even replaced with something that would not require a lock. + +For example: + +1. The session lock serializes access to the current R2T the initiator is +handling (a R2T from the target or the initialR2T if being used). libiscsi/ +libiscsi_tcp will call iscsi_tcp_get_curr_r2t and grab the session lock in +the xmit path from the xmit thread and then in the recv path +libiscsi_tcp/iscsi_tcp will call iscsi_tcp_r2t_rsp (this function is called +with the session lock held). We could add a new per iscsi_task lock and +use that to guard the R2T. + +2. For iscsi_tcp and cxgb*i, libiscsi uses the session->cmdqueue linked list +and the session lock to queue IO from the queuecommand function (run from +scsi softirq or kblockd context) to the iscsi xmit thread. Once the task is +sent from that thread, it is deleted from the list. + +It seems we should be able to remove the linked list use here. The tasks +are all preallocated in the session->cmds array. We can access that +array and check the task->state (see fail_scsi_tasks for an example). +We just need to come up with a way to safely set the task state, +wake the xmit thread and make sure that tasks are executed in the order +that the scsi layer sent them to our queuecommand function. + +A starting point on the queueing: +We might be able to create a workqueue per processor, queue the work, +which in this case is the execution of the task, from the queuecommand, +then rely on the work queue synchronization and serialization code. +Not 100% sure about this. + +Alternative to changing the threading: +Can we figure out a way to just remove the xmit thread? We currently +cannot because the network may only be able to send 1000 bytes, but +to send the current command we need to send 2000. We cannot sleep +from the queuecommand context until another 1000 bytes frees up and for +iscsi_tcp we cannot sleep from the recv conext (this happens because we +could have got a R2T from target and are handling it from the recv path). + + +Note: that for iser and offload drivers like bnx2i and be2iscsi their +is no xmit thread used. + +Note2: cxgb*i does not actually need the xmit thread so a side project +could be to convert that driver. + + +--------------------------------------------------------------------------- + +4. Make memory access more efficient on multi-processor machines. +We are moving twords per process queues in the block layer, so it would +be a good idea to move the iscsi structs to be allocated on a per process +basis. + +--------------------------------------------------------------------------- + +5. Make blk_iopoll support (see block/blk-iopoll.c and be2iscsi for an +example) being able to round robin IO across processors or complete +on the processor it was queued on +(today it always completes the IO on the processor the softirq was raised on), +and convert bnx2i, ib_iser and cxgb*i to it. + +Not sure if it will help iscsi_tcp and cxgb, because their completion is done +from the network softirq which does polling already. With irq balancing it +can also be spread over all processors too. + +--------------------------------------------------------------------------- + +7. When userspace calls into the kernel using the iscsi netlink interface +to execute oprations like creating/destroying a session, create a connection +to a target, etc the rx_queue_mutex is held the entire time (see +iscsi_if_rx for the iscsi netlink interface entry point). This means +if the driver should block every thing will be held up. + +iscsi_tcp does not block, but some offload drivers might for a couple seconds +to 10 or 15 secs while it figures out what is going on or cleans up. This a +major problem for things like multipath where one connection blocking up the +recovery of every other connection will delay IO from re-flowing quickly. + +We should looking into breaking up the rx_queue_mutex into finer grained +locks or making it multi threaded. For the latter we could queue operations +into workqueues. + +--------------------------------------------------------------------------- + +7. Add tracing support to iscsi modules. See the scsi layer's +trace_scsi_dispatch_cmd_start for an example. + +Well, actually in general look into all the tracing stuff available +(trace_printk/ftrace, etc) and use one. + +See http://lwn.net/Articles/291091/ for some details on what is out +there. We can only use something that is upstream though. + +--------------------------------------------------------------------------- + +8. Improve the iscsi driver logging. Each driver has a different +way to control logging. We should unify them and make it manageable +by iscsiadm. So each driver would use a common format, there would +be a common kernel interface to set the logging level, etc. + +--------------------------------------------------------------------------- + +9. Implement more features from the iSCSI RFC if they are worth it. + +- Error Recovery Level (ERL) 1 support - will help tape support. +- Multi R2T support - Might improve write performance. +- OutOfOrder support - Might imrpove performance. + +--------------------------------------------------------------------------- + +10. Add support for digest/CRC offload. + +--------------------------------------------------------------------------- + +11. Finish intel IOAT support. I started this here: +http://groups.google.com/group/open-iscsi/msg/2626b8606edbe690?dmode=source +but could only test on boxes with 1 gig interfaces which showed no +difference in performance. Intel had said they saw significant throughput +gains when using 10 gig. + +--------------------------------------------------------------------------- + +12. Remove the login buffer preallocated buffer. Storage drivers must be able +to make forward process, so that they can always write out a page incase the +kernel needs to allocate the page to another process. If the connection were +to be disconnected and the initiator needed to relogin to the target at this +time, we might not be abe to allocate a page for the login commands buffer. + +To work around the problem the initiator prealloctes a 8K (sometimes +more depending on the page size) buffer for each session (see iscsi_conn_setup' +s __get_free_pages call). This is obviously very wasteful since it will be +a rare occurrence. Can we think of a way to allow multiple sessions to +be relogged in at the same time, but not have to preallocate so many +buffers? + +--------------------------------------------------------------------------- + +13. Support iSCSI over swap safely. + +Basically just need to hook iscsi_tcp into the patches that +were submitted here for NBD. + +https://lwn.net/Articles/446831/ + + +--------------------------------------------------------------------------- + + + + + +USERSPACE TODO ITEMS +-------------------- +1. The iscsi tools, iscsid, iscsiadm and iscsid, have a debug flag, -d N, that +allows the user to control the amount of output that is logged. The argument +N is a integer from 1 to 8, with 8 printing out the most output. + +The problem is that the values from 1 to 8 do not really mean much. It would +helpful if we could replace them with something that controls what exactly +the iscsi tools and kernel modules log. + +For example, if we made the debug level argument a bitmap then + +iscsiadm -m node --login -d LOGIN_ERRS,PDUS,FUNCTION + +might print out extended iscsi login error information (LOGIN_ERRS), +the iSCSI packets that were sent/receieved (PDUS), and the functions +that were run (FUNCTION). Note, the use of a bitmapp and the debug +levels are just an example. Feel free to do something else. + + +We would want to be able to have iscsiadm control the iscsi kernel +logging as well. There are interfaces like +/sys/module/libiscsi/paramters/*debug* +/sys/module/libiscsi_tcp/paramters/*debug* +/sys/module/iscsi_tcp/paramters/*debug* +/sys/module/scsi_transport_iscsi/paramters/*debug* + +but we would want to extend the debugging options to be finer grained +and we would want to make it supportable by all iscsi drivers. +(see #8 on the kernel todo). + +--------------------------------------------------------------------------- + +2. "iscsiadm -m session -P 3" can print out a lot of information about the +session, but not all configuration values are printed. + +iscsiadm should be modified to print out other settings like timeouts, +Chap settings, the iSCSI values that were requested vs negotiated for, etc. + +--------------------------------------------------------------------------- + +3. iscsiadm cannot update a setting of a running session. If you want +to change a timeout you have to run the iscsiadm logout command, +then update the record value, then login: + +iscsiadm -m node -T target -p ip -u +iscsidm -m node -T target -p ip -o update -n node.session.timeo.replacement_timeout -v 30 +iscsiadm -m node -T target -p ip -l + +iscsiadm should be modified to allow updating of a setting without having +to run the iscsiadm command. + +Note that for some settings like iSCSI ones (ImmediateData, FirstBurstLength, +etc) that must be negotiated with the target we will have to logout the +target then re-login, but we should not have to completely destroy the session +and scsi devices like is done when running the iscsiadm logout command. We +should be able to pass iscsid the new values and then have iscsid logout and +relogin. + +Other settings like the abort timeout will not need a logout/login. We can +just pass those to the kernel or iscsid to use. + + +*Being worked on by Tomoaki Nishimura + +--------------------------------------------------------------------------- + +4. iscsiadm will attempt to perform logins/logouts in parallel. Running +iscsiadm -m node -L, will cause iscsiadm to login to all portals with +the startup=automatic field set at the same time. + +To log into a target, iscsiadm opens a socket to iscsid, sends iscsid a +request to login to a target, iscsid performs the iSCSI login operation, +then iscsid sends iscsiadm a reply. + +To perform multiple logins iscsiadm will open a socket for each login +request, then wait for a reply. This is a problem because for 1000s of targets +we will have 1000s of sockets open. There is a rlimit to control how many +files a process can have open and iscsiadm currently runs setrlimit to +increase this. + +With users creating lots of virtual iscsi interfaces on the target and +initiator with each having multiple paths it beomes inefficient to open +a socket for each requests. + +At the very least we want to handle setrlimit RLIMIT_NOFILE limit better, +and it would be best to just stop openening a socket per login request. + +--------------------------------------------------------------------------- + +6. Implement broadcast/multicasts support, so the initiator can +find iSNS servers without the user having to set the iSNS server address. + +See +5.6.5.14. Name Service Heartbeat (Heartbeat) +in +http://tools.ietf.org/html//rfc4171 + +--------------------------------------------------------------------------- + +7. Open-iscsi uses the open-isns iSNS library. The library might be a little +too complicated and a little too heavy for what we need. Investigate +replacing it. + +Also explore merging the open-isns and linux-isns projects, so we do not have +to support multiple isns clients/servers in linux. + +--------------------------------------------------------------------------- + +8. Implement the DHCP iSNS option support, so we the initiator can +find the iSNS sever without the user having to set the iSNS server address. +See: +http://www.ietf.org/rfc/rfc4174.txt + +--------------------------------------------------------------------------- + +9. Some iscsiadm/iscsid operations that access the iscsi DB and sysfs can be +up to Big O(N^2). Some of the code was written when we thought 64 sessions +would be a lot and the norm would be 4 or 8. Due to virtualization, cloud use, +and targets like equallogic that do a target per logical unit (device) we can +see 1000s of sessions. + +- We should look into making the record DB more efficient. Maybe +time to use a real DB (something small simple and efficient since this +needs to run in places like the initramfs). + +- Rewrite code to look up a running session so we do not have loop +over every session in sysfs. + + +--------------------------------------------------------------------------- + +10. Look into using udev's libudev for our sysfs access in iscsiadm/iscsid/ +iscsistart. + +--------------------------------------------------------------------------- + +11. iSCSI lib. + +I am working on this one. Hopefully it should be done soon. + +--------------------------------------------------------------------------- + +12. Figure out how to stop using our own copy of iscsi_if.h, since +it gets out of sync with the kernel version, and that's not good. + +--------------------------------------------------------------------------- diff --git a/doc/iscsi-iname.8 b/doc/iscsi-iname.8 new file mode 100644 index 0000000..6a413f6 --- /dev/null +++ b/doc/iscsi-iname.8 @@ -0,0 +1,22 @@ +.TH ISCSI_INAME 8 "Jan 2010" "" "Linux Administrator's Manual" +.SH NAME +iscsi-iname \- iSCSI initiator name generation tool +.SH SYNOPSIS +.BI iscsi-iname +[OPTION] +.SH "DESCRIPTION" +.B iscsi-iname +generates a unique iSCSI node name on every invocation. + +.SH OPTIONS +.TP +.BI [-h|--help] +Display help +.TP +.BI [-p=]\fIprefix\fP +Use the prefix passed in instead of the default "iqn.2016-04.com.open-iscsi" + +.SH AUTHORS +Open-iSCSI project +.br +Mike Christie diff --git a/doc/iscsi_discovery.8 b/doc/iscsi_discovery.8 new file mode 100644 index 0000000..489783a --- /dev/null +++ b/doc/iscsi_discovery.8 @@ -0,0 +1,62 @@ +.de Vb \" Begin verbatim text +.ft CW +.nf +.ne \\$1 +.. + +.TH "iscsi_discovery" 8 +.SH NAME +iscsi_discovery \- discover iSCSI targets +.SH SYNOPSIS +.B iscsi_discovery +.I +.RB [ -p +.IR ] +.RB [ -d ] +.RB [\ -t +.IR +.RB [ -f ] +] +.RB [ -m ] +.RB [ -l ] + +.SH DESCRIPTION +Perform send-targets discovery to the specified IP. If a discovery record +is generated, try to login to the portal using the preferred transport +(\-t flag specifies the requested transport type, TCP is the default). +If login using a certain transport succeeds, mark the portal for automatic +login (unless \-m flag is used), and disconnect (unless \-l flag is used). + +For iSCSI discovery to work, open-iscsi services must be running. i.e. iscsid +should be up, and the iSCSI modules loaded. This is best accomplished by the +init.d startup script. + +.\" .SH OPTIONS +.TP +.BI [-p=]\fIport\-number\fP +set the port number (default is 3260). +.TP +.BI [-d] +print debugging information. +.TP +.BI [-t=]\fItransport\-type\fP +set transport (default is tcp). +.TP +.BI [-f] +force specific transport - +disable the fallback to tcp (default is fallback enabled). +force the transport specified by the argument of the \-t flag. + +.TP +.BI [-m] +manual startup - will set manual startup (default is automatic startup). +.TP +.BI [-l] +login - login to the new discovered nodes (default is false). + +.SH AUTHOR +Written by Dan Bar Dov +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co Voltaire Ltd. 2006. diff --git a/doc/iscsi_fw_login.8 b/doc/iscsi_fw_login.8 new file mode 100644 index 0000000..e9fccd2 --- /dev/null +++ b/doc/iscsi_fw_login.8 @@ -0,0 +1,16 @@ +.TH "iscsi_fw_login" 8 +.SH NAME +iscsi_fw_login \- Login to all iSCSI Firmware targets +.SH SYNOPSIS +.B iscsi_fw_login +.SH DESCRIPTION +This helper function logs into all known iSCSI firmware targets. +.P +It is meant as a helper function to be called by udev when new +firmware targets are detected asynchronously. +.SH AUTHOR +Written by Lee Duncan +.SH "REPORTING BUGS" +Report bugs to . +.SH COPYRIGHT +Copyright \(co 2015 Lee Duncan diff --git a/doc/iscsiadm.8 b/doc/iscsiadm.8 new file mode 100644 index 0000000..bf23dd2 --- /dev/null +++ b/doc/iscsiadm.8 @@ -0,0 +1,655 @@ +.TH ISCSIADM 8 "Sep 2006" "" "Linux Administrator's Manual" +.SH NAME +iscsiadm \- open-iscsi administration utility +.SH SYNOPSIS +.B iscsiadm +.B \-m discoverydb +.RB [ \-hV ] +.RB [ \-d +.IR debug_level ] +.RB [ \-P +.IR printlevel ] +[\ +.BI \-I\ iface\ \-t\ type\ \-p\ ip:port +.RB [ \-lD ] +] | [ +.RB [ \-p +.I ip:port +.B \-t +.IR type ] +.RB [ \-o +.IR operation ] +.RB [ \-n +.IR name ] +.RB [ \-v +.IR value ] +.RB [ \-lD ] +] +.PP +.B iscsiadm +.B \-m discovery +.RB [ \-hV ] +.RB [ \-d +.IR debug_level ] +.RB [ \-P +.IR printlevel ] +[\ +.BI \-I\ iface\ \-t\ type\ \-p\ ip:port +.RB [ \-l ] +] | [ +.RB [ \-p +.IR ip:port ] +.RB [ \-l | \-D ] +] +.PP +.B iscsiadm +.B \-m node +.RB [ \-hV ] +.RB [ \-d +.IR debug_level ] +.RB [ \-P +.IR printlevel ] +.RB [ \-L +.IR all,manual,automatic,onboot ] +.RB [ \-U +.IR all,manual,automatic,onboot ] +.RB [ \-S ] +[ +.RB [ \-T +.IB targetname\ \-p\ ip:port\ \-I\ iface +] +.RB [ \-l | \-u | \-R | \-s ] +] +[ +.RB [ \-o +.IR operation ] +.RB [ \-n +.IR name ] +.RB [ \-v +.IR value ] +.RB [ \-p +.IR ip:port ] +] +.PP +.B iscsiadm +.B \-m session +.RB [ \-hV ] +.RB [ \-d +.IR debug_level ] +.RB [ \-P +.IR printlevel ] +[ +.B \-r +.IR sessionid | sysfsdir +.RB [ \-R ] +.RB [ \-u | \-s | \-o +.IR new ] +] +.PP +.B iscsiadm +.B \-m iface +.RB [ \-hV ] +.RB [ \-d +.IR debug_level ] +.RB [ \-P +.IR printlevel ] +[ +.BI \-I\ ifacename +| +.BI \-H\ hostno|MAC +] +[ +.RB [ \-o +.IR operation ] +.RB [ \-n +.IR name ] +.RB [ \-v +.IR value ] +] +[ +.BI \-C\ ping +.RB [ \-a +.IR ip ] +.RB [ \-b +.IR packetsize ] +.RB [ \-c +.IR count ] +.RB [ \-i +.IR interval ] +] +.PP +.B iscsiadm +.B \-m fw +.RB [ \-d +.IR debug_level ] +.RB [ \-l ] +.PP +.B iscsiadm +.B \-m host +.RB [ \-P +.IR printlevel ] +.RB [ \-H +.IR hostno|MAC ] +[ +.RB [\ \-C +.IR chap +.RB [ \-x +.IR chap_tbl_idx ] +] | +.RB [\ \-C +.IR flashnode +.RB [ \-A +.IR portal_type ] +.RB [ \-x +.IR flashnode_idx ] +] | +.RB [\ \-C +.IR stats \ ] +] +[ +.RB [ \-o +.IR operation ] +.RB [ \-n +.IR name ] +.RB [ \-v +.IR value ] +] +.PP +.B iscsiadm +.B \-k priority +.SH "DESCRIPTION" +The iscsiadm utility is a command-line tool allowing discovery and login +to iSCSI targets, as well as access and management of the open-iscsi +database. +.PP +Open-iscsi does not use the term node as defined by the iSCSI RFC, +where a node is a single iSCSI initiator or target. Open-iscsi uses the +term node to refer to a portal on a target. +.PP +For session mode, a session id (sid) is used. The sid of a session can be +found by running iscsiadm \-m session \-P 1. The session id and sysfs +path are not currently persistent and is partially determined by when the +session is setup. +.PP +Note that many of the node and discovery operations require that the iSCSI +daemon (iscsid) be running. +.SH OPTIONS +.TP +\fB\-a\fR, \fB\-\-ip=\fIipaddr\fP +\fIipaddr\fR can be IPv4 or IPv6. +.IP +This option is only valid for ping submode. +.TP +\fB\-A\fR, \fB\-\-portal_type=\fI[ipv4|ipv6]\fR +Specify the portal type for the new flash node entry to be created. +.IP +This option is only valid for flashnode submode of host mode and only with \fInew\fR operation. +.TP +\fB\-b\fR, \fB\-\-packetsize=\fIpacketsize\fP +Specify the ping \fIpacketsize\fR. +.IP +This option is only valid for ping submode. +.TP +\fB\-c\fR, \fB\-\-count=\fIcount\fP +\fIcount\fR specify number of ping iterations. +.IP +This option is only valid for ping submode. +.TP +\fB\-C\fR, \fB\-\-submode=\fIop\fP +Specify the submode for mode. op must be name of submode. +.IP +Currently iscsiadm support ping as submode for iface. For example: +.IP +iscsiadm \-m iface \-I ifacename \-C ping \-a ipaddr \-b packetsize \-c count \-i interval +.IP +For host, it supports chap , flashnode and stats as submodes. For example: +.IP +iscsiadm \-m host \-H hostno \-C chap \-x chap_tbl_idx \-o operation +.IP +iscsiadm \-m host \-H hostno \-C flashnode \-x flashnode_idx \-o operation +.IP +iscsiadm \-m host \-H hostno \-C stats +.TP +\fB\-d\fR, \fB\-\-debug=\fIdebug_level\fP +print debugging information. Valid values for debug_level are 0 to 8. +.TP +\fB\-h\fR, \fB\-\-help\fR +display help text and exit +.TP +\fB\-H\fR, \fB\-\-host=\fI[hostno|MAC]\fR +The host argument specifies the SCSI host to use for the operation. It can be +the scsi host number assigned to the host by the kernel's scsi layer, or the +MAC address of a scsi host. +.TP +\fB\-i\fR, \fB\-\-interval=\fIinterval\fP +\fIinterval\fP specify delay between two ping iterations. +.IP +This option is only valid for ping submode. +.TP +\fB\-I\fR, \fB\-\-interface=\fI[iface]\fR +The interface argument specifies the iSCSI interface to use for the operation. +iSCSI interfaces (iface) are defined in /etc/iscsi/ifaces. For hardware +iSCSI (qla4xxx) the iface config must have the hardware address +(iface.hwaddress = port's MAC address) +and the driver/transport_name (iface.transport_name). The iface's name is +then the filename of the iface config. For software iSCSI, the iface config +must have either the hardware address (iface.hwaddress), or the network +layer's interface name (iface.net_ifacename), and it must have the +driver/transport_name +.IP +The available drivers/iscsi_transports are tcp (software iSCSI over TCP/IP), +iser (software iSCSI over InfiniBand), or qla4xxx (Qlogic 4XXXX HBAs). The +hwaddress is the MAC address or for software iSCSI it may be the special +value "default" which directs the initiator to not bind the session to a +specific hardware resource and instead allow the network or InfiniBand layer +to decide what to do. There is no need to create an iface config with the default +behavior. If you do not specify an iface, then the default behavior is used. +.IP +As mentioned above there is a special iface name default. There are three +others -- cxgb3i, bnx2i and iser, which does not bind the session to a +specific card, but will bind the session to the cxgb3i, bnx2i or iser transport. These +are experimental and the use is not supported as a stable interface yet. +.IP +In discovery mode multiple interfaces can be specified by passing in multiple +\-I/\-\-interface instances. For example: +.IP +"iscsiadm \-m discoverydb \-t st \-p ip:port \-I iface0 \-I iface2 \-\-discover" +.IP +Will direct iscsiadm to setup the node db to create records which will create +sessions through the two intefaces passed in. +.IP +In node mode, only a single interface is supported in each call to iscsiadm. +.IP +This option is valid for discovery, node and iface mode. +.TP +\fB\-k\fR, \fB\-\-killiscsid=\fI[priority]\fR +Currently priority must be zero. This will immediately stop all iscsid +operations and shutdown iscsid. It does not logout any sessions. Running +this command is the same as doing "killall iscsid". Neither should +normally be used, because if iscsid is doing error recovery or if there +is an error while iscsid is not running, the system may not be able to recover. +This command and iscsid's SIGTERM handling are experimental. +.TP +\fB\-D\fR, \fB\-\-discover\fR +Discover targets using the discovery record with the \fIrecid\fR matching +the the discovery type and portal passed in. If there is no matching record, +it will be created using the iscsid.conf discovery settings. +This must be passed in \fIdiscoverydb\fR mode to instruct iscsiadm to perform +discovery. +.IP +This option is only valid for SendTargets discovery mode. +.TP +\fB\-l\fR, \fB\-\-login\fR +For node and fw mode, login to a specified record. For discovery mode, login to +all discovered targets. +.IP +This option is only valid for discovery and node modes. +.TP +\fB\-L\fR, \fB\-\-loginall=\fI[all|manual|automatic|onboot]\fR +For node mode, login all sessions with the node or conn startup values passed +in or all running session, except ones marked onboot, if all is passed in. +.IP +This option is only valid for node mode (it is valid but not functional +for session mode). +.TP +\fB\-m, \-\-mode \fIop\fR +specify the mode. \fIop\fR +must be one of \fIdiscovery\fR, \fIdiscoverydb\fR, \fInode\fR, \fIfw\fR, +\fIhost\fR \fIiface\fR or \fIsession\fR. +.IP +If no other options are specified: for \fIdiscovery\fR, \fIdiscoverydb\fR and +\fInode\fR, all of their respective records are displayed; for \fIsession\fR, +all active sessions and connections are displayed; for \fIfw\fR, all boot +firmware values are displayed; for \fIhost\fR, all iSCSI hosts are displayed; +and for \fIiface\fR, all ifaces setup in /etc/iscsi/ifaces are displayed. +.TP +\fB\-n\fR, \fB\-\-name=\fIname\fR +In node mode, specify a field \fIname\fR in a record. In flashnode submode +of host mode, specify name of the flash node parameter. +.IP +For use with the \fIupdate\fR operator. +.TP +\fB\-o\fR, \fB\-\-op=\fIop\fR +Specifies a database operator \fIop\fR. \fIop\fR must be one of +\fInew\fR, \fIdelete\fR, \fIupdate\fR, \fIshow\fR or \fInonpersistent\fR. +.IP +For iface mode, \fIapply\fR and \fIapplyall\fR are also applicable. +.IP +For flashnode submode of host mode, \fIlogin\fR and \fIlogout\fR are also applicable. +.IP +This option is valid for all modes except fw. Delete should not be used +on a running session. If it is iscsiadm will stop the session and then delete the +record. +.IP +\fInew\fR creates a new database record for a given object. In node mode, the +\fIrecid\fR is the target name and portal (IP:port). In iface mode, the \fIrecid\fR +is the iface name. In discovery mode, the \fIrecid\fR is the portal and +discovery type. +.IP +In session mode, the \fInew\fR operation logs in a new session using +the same node database and iface information as the specified session. +.IP +In discovery mode, if the \fIrecid\fR and new operation is passed in, +but the \fI--discover\fR argument is not, then iscsiadm will only create a +discovery record (it will not perform discovery). If the \fI--discover\fR +argument is passed in with the portal and discovery type, then iscsiadm +will create the discovery record if needed, and it will create records +for portals returned by the target that do not yet have a node DB record. +.IP +\fIdelete\fR deletes a specified \fIrecid\fR. In discovery mode, if +iscsiadm is performing discovery it will delete records for portals that +are no longer returned. +.IP +\fIupdate\fR will update the \fIrecid\fR with \fIname\fR to the specified +\fIvalue\fR. In discovery mode, if iscsiadm is performing discovery the +\fIrecid\fR, \fIname\fR and \fIvalue\fR arguments are not needed. The +update operation will operate on the portals returned by the target, +and will update the node records with info from the config file and +command line. +.IP +\fIshow\fR is the default behaviour for node, discovery and iface mode. It is +also used when there are no commands passed into session mode and a running +sid is passed in. +\fIname\fR and \fIvalue\fR are currently ignored when used with \fIshow\fR. +.IP +\fInonpersistent\fR instructs iscsiadm to not manipulate the node DB. +.IP +\fIapply\fR will cause the network settings to take effect on the specified iface. +.IP +\fIapplyall\fR will cause the network settings to take effect on all the +ifaces whose MAC address or host number matches that of the specific host. +.IP +\fIlogin\fR will log into the specified flash node entry. +.IP +\fIlogout\fR does the logout from the given flash node entry. +.TP +\fB\-p\fR, \fB\-\-portal=\fIip[:port]\fR +Use target portal with ip-address \fIip\fR and \fIport\fR. If port is not passed +in the default \fIport\fR value is 3260. +.IP +IPv6 addresses can be specified as [ddd.ddd.ddd.ddd]:port or +ddd.ddd.ddd.ddd. +.IP +Hostnames can also be used for the ip argument. +.IP +This option is only valid for discovery, or for node operations with +the \fInew\fR operator. +.IP +This should be used along with \-\-target in node mode, to specify what +the open-iscsi docs refer to as a node or node record. Note: open-iscsi's +use of the word node, does not match the iSCSI RFC's iSCSI Node term. +.TP +\fB\-P\fR, \fB\-\-print=\fIprintlevel\fR +If in node mode print nodes in tree format. If in session mode print +sessions in tree format. If in discovery mode print the nodes in +tree format. +.TP +\fB\-T\fR, \fB\-\-targetname=\fItargetname\fR +Use target \fItargetname\fR. +.IP +This should be used along with \-\-portal in node mode, to specify what +the open-iscsi docs refer to as a node or node record. Note: open-iscsi's +use of the word node, does not match the iSCSI RFC's iSCSI Node term. +.TP +\fB\-r\fR, \fB\-\-sid=\fIsid | sysfsdir\fR +Use session ID \fIsid\fR. The sid of a session can be found from running +iscsiadm in session mode with the \-\-info argument. +.IP +Instead of sid, a sysfs path containing the session can be used. +For example using one of the following: +/sys/devices/platform/hostH/sessionS/targetH:B:I/H:B:I:L, +/sys/devices/platform/hostH/sessionS/targetH:B:I, or +/sys/devices/platform/hostH/sessionS, for the sysfsdir argument would +result in the session with sid S to be used. +.IP +\fIsid | sysfsdir\fR is only required for session mode. +.TP +\fB\-R\fR, \fB\-\-rescan\fR +In session mode, if sid is also passed in rescan the session. If no sid has +been passed in rescan all running sessions. +.IP +In node mode, rescan a session running through the target, portal, iface +tuple passed in. +.TP +\fB\-s\fR, \fB\-\-stats\fR +Display session statistics. +This option when used with host mode, displays host statistics. +.TP +\fB\-S\fR, \fB\-\-show\fR +When displaying records, do not hide masked values, such as the CHAP +secret (password). +.IP +This option is only valid for node and session mode. +.TP +\fB\-t\fR, \fB\-\-type=\fItype\fR +\fItype\fR must be \fIsendtargets\fR (or abbreviated as \fIst\fR), +\fIslp\fR, \fIisns\fR or \fIfw\fR. Currently only sendtargets, fw, and +iSNS is supported, see the DISCOVERY TYPES section. +.IP +This option is only valid for discovery mode. +.TP +\fB\-u\fR, \fB\-\-logout\fR +logout for a specified record. +.IP +This option is only valid for node and session mode. +.TP +\fB\-U\fR, \fB\-\-logoutall=\fI[all,manual,automatic|onboot]\fR +logout all sessions with the node or conn startup values passed in or all +running session, except ones marked onboot, if all is passed in. +.IP +This option is only valid for node mode (it is valid but not functional +for session mode). +.TP +\fB\-v\fR, \fB\-\-value=\fIvalue\fR +Specify a \fIvalue\fR for use with the \fIupdate\fR operator. +.IP +This option is only valid for node mode and flashnode submode of host mode. +.TP +\fB\-V\fR, \fB\-\-version\fR +display version and exit +.TP +\fB\-x\fR, \fB\-\-index=\fIindex\fR +Specify the \fIindex\fR of the entity to operate on. +.IP +This option is only valid for chap and flashnode submodes of host mode. +.SH DISCOVERY TYPES +iSCSI defines 3 discovery types: SendTargets, SLP, and iSNS. +.TP +.B +SendTargets +A native iSCSI protocol which allows each iSCSI +target to send a list of available targets to the initiator. +.TP +.B +SLP +Optionally an iSCSI target can use the Service Location Protocol (SLP) +to announce the available targets. The initiator can either implement +SLP queries directly or can use a separate tool to acquire the +information about available targets. +.TP +.B +iSNS +iSNS (Internet Storage Name Service) records information about storage +volumes within a larger network. To utilize iSNS, pass the address and +optionally the port of the iSNS server to do discovery to. +.TP +.B +fw +Several NICs and systems contain a mini iSCSI initiator which can be used +for boot. To get the values used for boot the fw option can be used. +Doing fw discovery, does not store persistent records in the node or +discovery DB, because the values are stored in the system's or NIC's +resource. +.IP +Performing fw discovery will print the portals, like with other discovery +methods. To see other settings like CHAP values and initiator settings, +like you would in node mode, run "iscsiadm \-m fw". +.IP +fw support in open-iscsi is experimental. The settings and iscsiadm +syntax and output format may change. +.P +iscsiadm supports the +.B +iSNS (isns) +or +.B +SendTargets (st) +discovery type. An SLP implementation is under development. +.SH EXIT STATUS +On success 0 is returned. On error one of the return codes below will +be returned. +.PP +Commands that operate on multiple objects (sessions, records, etc), +iscsiadm/iscsistart will return the first error that is encountered. +iscsiadm/iscsistart will attempt to execute the operation on the objects it +can. If no objects are found ISCSI_ERR_NO_OBJS_FOUND is returned. +.TP +.B +0 +ISCSI_SUCCESS - command executed successfully. +.TP +.B +1 +ISCSI_ERR - generic error code. +.TP +.B +2 +ISCSI_ERR_SESS_NOT_FOUND - session could not be found. +.TP +.B +3 +ISCSI_ERR_NOMEM - could not allocate resource for operation. +.TP +.B +4 +ISCSI_ERR_TRANS - connect problem caused operation to fail. +.TP +.B +5 +ISCSI_ERR_LOGIN - generic iSCSI login failure. +.TP +.B +6 +ISCSI_ERR_IDBM - error accessing/managing iSCSI DB. +.TP +.B +7 +ISCSI_ERR_INVAL - invalid argument. +.TP +.B +8 +ISCSI_ERR_TRANS_TIMEOUT - connection timer exired while trying to connect. +.TP +.B +9 +ISCSI_ERR_INTERNAL - generic internal iscsid/kernel failure. +.TP +.B +10 +ISCSI_ERR_LOGOUT - iSCSI logout failed. +.TP +.B +11 +ISCSI_ERR_PDU_TIMEOUT - iSCSI PDU timedout. +.TP +.B +12 +ISCSI_ERR_TRANS_NOT_FOUND - iSCSI transport module not loaded in kernel or iscsid. +.TP +.B +13 +ISCSI_ERR_ACCESS - did not have proper OS permissions to access iscsid or execute iscsiadm command. +.TP +.B +14 +ISCSI_ERR_TRANS_CAPS - transport module did not support operation. +.TP +.B +15 +ISCSI_ERR_SESS_EXISTS - session is logged in. +.TP +.B +16 +ISCSI_ERR_INVALID_MGMT_REQ - invalid IPC MGMT request. +.TP +.B +17 +ISCSI_ERR_ISNS_UNAVAILABLE - iSNS service is not supported. +.TP +.B +18 +ISCSI_ERR_ISCSID_COMM_ERR - a read/write to iscsid failed. +.TP +.B +19 +ISCSI_ERR_FATAL_LOGIN - fatal iSCSI login error. +.TP +.B +20 +ISCSI_ERR_ISCSID_NOTCONN - could not connect to iscsid. +.TP +.B +21 +ISCSI_ERR_NO_OBJS_FOUND - no records/targets/sessions/portals found to execute operation on. +.TP +.B +22 +ISCSI_ERR_SYSFS_LOOKUP - could not lookup object in sysfs. +.TP +.B +23 +ISCSI_ERR_HOST_NOT_FOUND - could not lookup host. +.TP +.B +24 +ISCSI_ERR_LOGIN_AUTH_FAILED - login failed due to authorization failure. +.TP +.B +25 +ISCSI_ERR_ISNS_QUERY - iSNS query failure. +.TP +.B +26 +ISCSI_ERR_ISNS_REG_FAILED - iSNS registration/deregistration failed. +.SH EXAMPLES +Discover targets at a given IP address: +.IP +iscsiadm \-\-mode discoverydb \-\-type sendtargets \-\-portal 192.168.1.10 \-\-discover +.PP +Login, must use a node record id found by the discovery: +.IP +iscsiadm \-\-mode node \-\-targetname iqn.2001-05.com.doe:test \-\-portal 192.168.1.1:3260 \-\-login +.PP +Logout: +.IP +iscsiadm \-\-mode node \-\-targetname iqn.2001-05.com.doe:test \-\-portal 192.168.1.1:3260 \-\-logout +.PP +List node records: +.IP +iscsiadm \-\-mode node +.PP +Display all data for a given node record: +.IP +iscsiadm \-\-mode node \-\-targetname iqn.2001-05.com.doe:test \-\-portal 192.168.1.1:3260 +.SH FILES +.TP +/etc/iscsi/iscsid.conf +The configuration file read by \fBiscsid\fR and \fBiscsiadm\fR on startup. +.TP +/etc/iscsi/initiatorname.iscsi +The file containing the iSCSI InitiatorName and InitiatorAlias read by +\fBiscsid\fR and \fBiscsiadm\fR on startup. +.TP +/etc/iscsi/nodes/ +This directory contains the nodes with their targets. +.TP +/etc/iscsi/send_targets +This directory contains the portals. +.SH "SEE ALSO" +.BR iscsid (8) +.SH AUTHORS +Open-iSCSI project +.br +Alex Aizman +.br +Dmitry Yusupov diff --git a/doc/iscsid.8 b/doc/iscsid.8 new file mode 100644 index 0000000..6f9218f --- /dev/null +++ b/doc/iscsid.8 @@ -0,0 +1,79 @@ +.TH ISCSID 8 "July 2005" "" "Linux Administrator's Manual" +.SH NAME +iscsid \- Open-iSCSI daemon +.SH SYNOPSIS +.BI iscsid +[OPTION] +.SH "DESCRIPTION" +The +.B iscsid +implements the control path of iSCSI protocol, plus some management +facilities. For example, the daemon could be configured to automatically +re-start discovery at startup, based on the contents of persistent +iSCSI database. +.SH OPTIONS +.TP +.BI [-c|--config=]\fIconfig\-file\fP +Read configuration from \fIconfig\-file\fR rather than the default +\fI/etc/iscsi/iscsid.conf\fR file. +.TP +.BI [-i|--initiatorname=]\fIiname\-file\fP +Read initiator name from \fIiname\-file\fR rather than the default +\fI/etc/iscsi/initiatorname.iscsi\fR file. +.TP +.BI [-f|--foreground] +run +.B iscsid +in the foreground. +.TP +.BI [-d|--debug=]\fIdebug_level\fP +print debugging information. Valid values for debug_level are 0 to 8. +.TP +.BI [-u|--uid=]\fIuid\fP +run under user ID \fIuid\fR (default is the current user ID) +.TP +.BI [-g|--gid=]\fIgid\fP +run under user group ID \fIgid\fR (default is the current user group ID). +.TP +.BI [-n|--no-pid-file]\fP +do not write a process ID file. +.TP +.BI [-p|--pid=]\fIpid\-file\fP +write process ID to \fIpid\-file\fR rather than the default +\fI/run/iscsid.pid\fR +.TP +.BI [-h|--help] +display this help and exit +.TP +.BI [-v|--version] +display version and exit. + +.SH FILES +.TP +/etc/iscsi/iscsid.conf +The configuration file read by +.B iscsid +and +.B iscsiadm +on startup. +.TP +/etc/iscsi/initiatorname.iscsi +The file containing the iSCSI initiatorname +and initiatoralias read by +.B iscsid +and +.B iscsiadm +on startup. +.TP +/etc/iscsi/nodes +Open-iSCSI persistent configuration database + +.SH "SEE ALSO" +.BR iscsiadm (8) + +.SH AUTHORS +Open-iSCSI project +.br +Alex Aizman +.br +Dmitry Yusupov diff --git a/doc/iscsistart.8 b/doc/iscsistart.8 new file mode 100644 index 0000000..5aa7dd4 --- /dev/null +++ b/doc/iscsistart.8 @@ -0,0 +1,71 @@ +.TH ISCSISTART 8 "Jan 2010" "" "Linux Administrator's Manual" +.SH NAME +iscsistart \- iSCSI boot tool +.SH SYNOPSIS +.BI iscsistart +[OPTION] +.SH "DESCRIPTION" +.B iscsistart +will start a session using the settings passed in, or +using the iBFT or Open Firmware [OF] boot information. This program should +not be run to manage sessions. Its primary use is to start +sessions used for iSCSI root boot. +.SH OPTIONS +.TP +.BI [-i|--initiatorname=]\fIname\fP +Set InitiatorName to name (Required if not using iBFT or OF) +.TP +.BI [-t|--targetname=]\fIname\fP +Set TargetName to name (Required if not using iBFT or OF) +.TP +.BI [-g|--tgpt=]\fIN\fP +Set target portal group tag to N (Required if not using iBFT or OF) +.TP +.BI [-a|--address=]\fIA.B.C.D\fP +Set IP address to A.B.C.D (Required if not using iBFT or OF) +.TP +.BI [-p|--port=]\fIN\fP +Set port to N (Optional. Default 3260) +.TP +.BI [-u|--username=]\fIN\fP +Set username to N (Optional) +.TP +.BI [-w|--password=]\fIN\fP +Set password to N (Optional) +.TP +.BI [-U|-username_in=]\fIN\fP +Set incoming username to N (Optional) +.TP +.BI [-W|--password_in=]\fIN\fP +Set incoming password to N (Optional) +.TP +.BI [-d|--debug=]\fIdebug_level\fP +Print debugging information +.TP +.BI [-b|--fwparam_connect] +Create a session to the target using the iBFT or OF info +.TP +.BI [-N|--fwparam_network] +Bring up the network as specified by iBFT or OF +.TP +.BI [-f|--fwparam_print] +Print the iBFT or OF info to STDOUT +.TP +.BI [-P|--param=]\fINAME=VALUE\fP +Set the parameter with the name NAME to VALUE. NAME is one of the settings +in the node record or iscsid.conf. Multiple params can be passed in. +.TP +.BI [-h|--help] +Display this help and exit +.TP +.BI [-v|--version] +Display version and exit + + +.SH "SEE ALSO" +.BR iscsiadm (8) + +.SH AUTHORS +Open-iSCSI project +.br +Mike Christie diff --git a/etc/iface.example b/etc/iface.example new file mode 100644 index 0000000..4b7f22c --- /dev/null +++ b/etc/iface.example @@ -0,0 +1,197 @@ +# +# Example iSCSI interface config +# +# There must be a separate iscsi interface config file for each NIC, network +# interface or port or iscsi HBA you want to bind sessions to. +# +# For hardware iscsi, this is created for you when you run iscsiadm. +# For software iscsi, you must define these files yourself. +# + +# REQUIRED: iface.transport_name +# +# Set the iscsi transport/driver to use for the iface by setting +# iface.transport_name +# example: +# iface.transport_name = tcp + +# This value is required and valid values for iface.transport_name are: +# - tcp (Software iSCSI over TCP/IP) +# - iser (Software iSCSI over infinniband +# - qla4xxx (Qlogic QLA4XXX HBAs) +# - bnx2i (Broadcom bnx iSCSI HBAs); +# - cxgb3i (Chelsio cxgb S3 iSCSI HBAs); +# +#OPTIONAL: iface.initiatorname +# To use an initiator name other than the one set in +# /etc/iscsi/initiatorname.iscsi for normal sessions set the +# iface.initiatorname. This is only used for normal sessions. +# For discovery sessions the /etc/iscsi/initiatorname.iscsi value +# is used. +# +# iface.initiatorname = iqn.2003-04.com.fedora:test +# +# +# REQUIRED to be able to bind a session to a network device: +# [iface.net_ifacename | iface.hwaddress] +# +# OPTIONAL if you are creating ifaces so you can create multiple sessions +# using the default behavior where the network layer selects the device. +# +# __One__ of the following values are required for binding a session +# to a specific nic/netdevice. +# +# To bind by network interface name (example: eth0, eth2:2, eth1.3) +# set iface.net_ifacename +# example: +# iface.net_ifacename = eth0 + +# To bind by hardware address set the NIC's MAC address to iface.hwaddress +# example: +# iface.hwaddress = 00:0F:1F:92:6B:BF + +# Note you can only bind using one value. If you set multiple values +# the bahavior is not defined. + +# For some transport (cxgb3i), a user could to set the private ip address for +# the iscsi traffic for an network interface: +# example: +# - set iscsi ip on eth0 to be 102.50.50.101, eth0 needs to be up and be on +# the same subnet. +# iface.net_ifacename = eth0 +# iface.ipaddress = 102.50.50.101 + +# OPTIONAL: iface.bootproto +# +# Valid values are: +# "dhcp" and "static" +# +# REQUIRED when IPv4 address need to be obtained dynamically using DHCP +# example: +# iface.bootproto = dhcp +# +# OPTIONAL when the IPv4 address is set statically +# example: +# iface.ipaddress = 102.50.50.101 +# iface.bootproto = static +# + +# OPTIONAL: iface.vlan_id +# Used to set the VLAN ID for the iSCSI interfae. +# example +# iface.vlan_id = 1022 + +# OPTIONAL: iface.vlan_priority +# Used to set the VLAN priority for the iSCSI interfae. +# example +# iface.vlan_priority = 1 + +# OPTIONAL: iface.vlan_state +# Used to enable or disable the VLAN on the iSCSI interface +# example +# iface.vlan_state = enable + +# OPTIONAL: iface.ipv6_linklocal +# Specify the IPV6 Link Local Address with the +# link local prefix of FE80::0/64 +# example: +# iface.ipv6_linklocal = fe80:0000:0000:0000:020e:1eff:1111:2221 + +# OPTIONAL: iface.ipv6_router +# Used to set a default IPV6 router +# example: +# iface.ipv6_router = fe80:0000:0000:0000:7ae7:d1ff:fe72:4048 + +# OPTIONAL: iface.ipv6_autocfg +# Used to set the discovery protocol to obtain IPV6 address +# For example qla4xxx support neighbor discovery +# example: +# iface.ipv6_autocfg = nd + +# OPTIONAL: iface.linklocal_autocfg +# For transport like qla4xxx this allows to auto configure the +# IPV6 link local address based on the MAC address of the iSCSI +# interface + +# OPTIONAL: iface.router_autocfg +# Required to set the IPv6 router discovery protocol +# To set the router discovery protocol to Neighbor Discovery specify "nd" +# example: +# iface.router_autocfg = nd + +# OPTIONAL: iface.state +# By default the iface is enabled +# iface.state = enable +# To disable the iface set the state to "disable" +# iface.state = disable + +# iface.iface_num +# REQUIRED: When there are more than 1 interface to be configured. +# For transports like qla4xxx, one can specify two IPV6 interfaces +# in such case the iface_num must be set correctly +# example: +# iface settings for first IPV6 interface +# iface.iscsi_ifacename = iface-qla4xxx-ipv6-1 +# iface.iface_num = 0 +# +# iface settings for second IPV6 interface +# iface.iscsi_ifacename = iface-qla4xxx-ipv6-2 +# iface.iface_num = 1 + +# Here are some example iface files +# IPV4 sample config file with static IP address: +# BEGIN RECORD 2.0-872 +# iface.iscsi_ifacename = qla4xxx-3 +# iface.ipaddress = 192.168.1.75 +# iface.hwaddress = 00:0e:1e:04:93:92 +# iface.transport_name = qla4xxx +# iface.bootproto = static +# iface.subnet_mask = 255.255.255.0 +# iface.gateway = 192.168.1.1 +# iface.state = enable +# iface.vlan = +# iface.iface_num = 0 +# END RECORD +# +# IPV6 sample config file with neighbor discovery: +# BEGIN RECORD 2.0-872 +# iface.iscsi_ifacename = qla4xxx-3-1 +# iface.ipaddress = +# iface.hwaddress = 00:0e:1e:04:93:92 +# iface.transport_name = qla4xxx +# iface.ipv6_autocfg = nd +# iface.linklocal_autocfg = auto +# iface.router_autocfg = nd +# iface.ipv6_linklocal = fe80:0000:0000:0000:020e:1eff:1111:2221 +# iface.ipv6_router = auto +# iface.state = enable +# iface.vlan = +# iface.iface_num = 0 +# END RECORD + +# Ipv4 sample config file (DHCP configuration): +# BEGIN RECORD 2.0-872 +# iface.iscsi_ifacename = qla4xxx-3 +# iface.hwaddress = 00:0e:1e:04:93:92 +# iface.transport_name = qla4xxx +# iface.bootproto = dhcp +# iface.state = enable +# iface.vlan = +# iface.iface_num = 0 +# END RECORD + +# Sample ipv6 config file(manual configured IPs): +# BEGIN RECORD 2.0-872 +# iface.iscsi_ifacename = iface-new-file +# iface.ipaddress = fec0:ce00:7014:0041:1111:2222:1e04:9392 +# iface.hwaddress = 00:0e:1e:04:93:92 +# iface.transport_name = qla4xxx +# iface.ipv6_autocfg = +# iface.linklocal_autocfg = +# iface.router_autocfg = +# iface.ipv6_linklocal = fe80:0000:0000:0000:0000:0000:1e04:9392 +# iface.ipv6_router = fe80:0000:0000:0000:7ae7:d1ff:fe72:4048 +# iface.state = enable +# iface.vlan = +# iface.iface_num = 0 +# END RECORD diff --git a/etc/initd/boot.suse b/etc/initd/boot.suse new file mode 100644 index 0000000..ac6abcc --- /dev/null +++ b/etc/initd/boot.suse @@ -0,0 +1,77 @@ +#!/bin/bash +# +# /etc/init.d/iscsi +# +### BEGIN INIT INFO +# Provides: iscsiboot +# Required-Start: +# Should-Start: boot.multipath +# Required-Stop: +# Should-Stop: +# Default-Start: B +# Default-Stop: +# Short-Description: iSCSI initiator daemon root-fs support +# Description: Starts the iSCSI initiator daemon if the +# root-filesystem is on an iSCSI device +# +### END INIT INFO + +ISCSIADM=/sbin/iscsiadm +CONFIG_FILE=/etc/iscsid.conf +DAEMON=/sbin/iscsid +ARGS="-c $CONFIG_FILE" + +# Source LSB init functions +. /etc/rc.status + +# +# This service is run right after booting. So all targets activated +# during mkinitrd run should not be removed when the open-iscsi +# service is stopped. +# +iscsi_mark_root_nodes() +{ + $ISCSIADM -m session 2> /dev/null | while read t num i target ; do + ip=${i%%:*} + STARTUP=`$ISCSIADM -m node -p $ip -T $target 2> /dev/null | grep "node.conn\[0\].startup" | cut -d' ' -f3` + if [ "$STARTUP" -a "$STARTUP" != "onboot" ] ; then + $ISCSIADM -m node -p $ip -T $target -o update -n node.conn[0].startup -v onboot + fi + done +} + +# Reset status of this service +rc_reset + +# We only need to start this for root on iSCSI +if ! grep -q iscsi_tcp /proc/modules ; then + rc_failed 6 + rc_exit +fi + +case "$1" in + start) + echo -n "Starting iSCSI initiator for the root device: " + startproc $DAEMON $ARGS + rc_status -v + iscsi_mark_root_nodes + ;; + stop|restart|reload) + rc_failed 0 + ;; + status) + echo -n "Checking for iSCSI initiator service: " + if checkproc $DAEMON ; then + rc_status -v + else + rc_failed 3 + rc_status -v + fi + ;; + *) + echo "Usage: $0 {start|stop|status|restart|reload}" + exit 1 + ;; +esac +rc_exit + diff --git a/etc/initd/initd.debian b/etc/initd/initd.debian new file mode 100644 index 0000000..b531be5 --- /dev/null +++ b/etc/initd/initd.debian @@ -0,0 +1,110 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: +# Required-Start: +# Required-Stop: +# Default-Start: +# Default-Stop: +# Short-Description: Starts and stops the iSCSI initiator services and logins to default targets +### END INIT INFO + +PATH=/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/sbin/iscsid +ADM=/sbin/iscsiadm +PIDFILE=/run/iscsid.pid + +[ -x "$DAEMON" ] || exit 0 + +. /lib/lsb/init-functions + +if [ ! -d /sys/class/ ]; then + log_failure_msg "iSCSI requires a mounted sysfs, not started." + exit 1 +fi + +nodestartup_re='s/^node\.conn\[0]\.startup[ ]*=[ ]*//p' + +RETVAL=0 + +start() { + log_daemon_msg "Starting iSCSI initiator service" "iscsid" + modprobe -q iscsi_tcp 2>/dev/null || : + modprobe -q ib_iser 2>/dev/null || : + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON + RETVAL=$? + log_end_msg $RETVAL + starttargets +} + +starttargets() { + log_daemon_msg "Setting up iSCSI targets" + $ADM -m node --loginall=automatic + log_end_msg 0 +} + +stoptargets() { + log_daemon_msg "Disconnecting iSCSI targets" + sync + $ADM -m node --logoutall=all + RETVAL=$? + log_end_msg $RETVAL +} + +stop() { + stoptargets + if [ $RETVAL -ne 0 ]; then + log_failure_msg "Could not stop all targets, try again later" + return $RETVAL + fi + + log_daemon_msg "Stopping iSCSI initiator service" + start-stop-daemon --stop --quiet --pidfile $PIDFILE --exec $DAEMON + rm -f $PIDFILE + status=0 + modprobe -r ib_iser 2>/dev/null + if [ "$?" -ne "0" -a "$?" -ne "1" ]; then + status=1 + fi + modprobe -r iscsi_tcp 2>/dev/null + if [ "$?" -ne "0" -a "$?" -ne "1" ]; then + status=1 + fi + log_end_msg $status +} + +restart() { + stop + if [ $RETVAL -ne 0 ]; then + log_failure_msg "Stopping iSCSI initiator service failed, not starting" + return $RETVAL + fi + start +} + +restarttargets() { + stoptargets + if [ $RETVAL -ne 0 ]; then + log_failure_msg "Could not stop all targets, try again later" + return $RETVAL + fi + starttargets +} + +status() { + #XXX FIXME: what to do here? + #status iscsid + # list active sessions + echo Current active iSCSI sessions: + $ADM -m session +} + +case "$1" in + start|starttargets|stop|stoptargets|restart|restarttargets|status) + $1 + ;; + *) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 + ;; +esac +exit $RETVAL diff --git a/etc/initd/initd.redhat b/etc/initd/initd.redhat new file mode 100644 index 0000000..568f0cf --- /dev/null +++ b/etc/initd/initd.redhat @@ -0,0 +1,104 @@ +#!/bin/sh +# +# chkconfig: 345 13 89 +# description: Starts and stops the iSCSI initiator +# +# processname: iscsid +# pidfile: /run/iscsid.pid +# config: /etc/iscsi/iscsid.conf + +# Source function library. +. /etc/init.d/functions + +PATH=/sbin:/bin:/usr/sbin:/usr/bin + +RETVAL=0 + +start() +{ + echo -n $"Starting iSCSI initiator service: " + daemon iscsid + RETVAL=$? + success + echo + [ $RETVAL -eq 0 ] || return + + touch /run/lock/subsys/open-iscsi + + echo -n $"Setting up iSCSI targets: " + iscsiadm -m node --loginall=automatic + success + echo + +} + +stop() +{ + echo -n $"Stopping iSCSI initiator service: " + sync + iscsiadm -m node --logoutall=all + RETVAL=$? + if [ $RETVAL -ne 0 ]; then + echo "Could not logout from all nodes, try again later" + return $RETVAL + fi + iscsiadm -k 0 + rm -f /run/iscsid.pid + [ $RETVAL -eq 0 ] && rm -f /run/lock/subsys/open-iscsi + status=0 + modprobe -r iscsi_tcp 2>/dev/null + if [ "$?" -ne "0" -a "$?" -ne "1" ]; then + status=1 + fi + modprobe -r ib_iser 2>/dev/null + if [ "$?" -ne "0" -a "$?" -ne "1" ]; then + status=1 + fi + if [ "$status" -eq "0" ]; then + success + else + failure + fi + echo + +} + +restart() +{ + stop + if [ $RETVAL -ne 0 ]; then + echo "Stopping iSCSI initiator service failed, not starting" + return $RETVAL + fi + start + +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + stop + if [ $RETVAL -ne 0 ]; then + echo "Stopping iSCSI initiator service failed, not starting" + exit $RETVAL + fi + start + ;; + status) + status iscsid + RETVAL=$? + ;; + condrestart) + [ -f /run/lock/subsys/open-iscsi ] && restart + ;; + *) + echo $"Usage: $0 {start|stop|restart|status|condrestart}" + exit 1 +esac + +exit $RETVAL diff --git a/etc/initd/initd.suse b/etc/initd/initd.suse new file mode 100644 index 0000000..54d0d84 --- /dev/null +++ b/etc/initd/initd.suse @@ -0,0 +1,465 @@ +#!/bin/bash +# +# /etc/init.d/iscsi +# +### BEGIN INIT INFO +# Provides: iscsi +# Required-Start: $network +# Should-Start: iscsitarget multipathd +# Required-Stop: $network +# Should-Stop: multipathd +# Default-Start: 3 5 +# Default-Stop: +# Short-Description: iSCSI initiator daemon +# Description: The iSCSI initator is used to create and +# manage iSCSI connections to an iSCSI Target. +# +### END INIT INFO + +CONFIG_FILE=/etc/iscsi/iscsid.conf +DAEMON=/sbin/iscsid +ISCSIADM=/sbin/iscsiadm +BRCM_ISCSIUIO=/sbin/brcm_iscsiuio +ARGS="-c $CONFIG_FILE -n" + +# Source LSB init functions +. /etc/rc.status + +# Reset status of this service +rc_reset + +DM_MAJOR=$(sed -n 's/\(.*\) device-mapper/\1/p' /proc/devices) + +iscsi_login_all_nodes() +{ + echo -n "Setting up iSCSI targets: " + $ISCSIADM -m node --loginall=automatic 2> /dev/null + if [ $? == 21 ] ; then + rc_failed 6 + fi + rc_status -v +} + +# +# Try to load all required modules prior to startup +# +iscsi_load_transport_modules() +{ + loaded=$(sed -n "/^iscsi_tcp/p" /proc/modules) + if [ -z "$loaded" ] ; then + modprobe iscsi_tcp + if [ $? = 0 ] ; then + echo -n " tcp" + fi + fi + + for iface in /etc/iscsi/ifaces/*; do + [ -f "$iface" ] || continue + [ "$iface" = "iface.example" ] && continue + # Check if the iface has been configured + result=$(sed '/#.*/D;/iface.iscsi_ifacename/D;/iface.hwaddress/D;/iface.transport_name/D' $iface) + if [ "$result" ] ; then + mod=$(sed -n 's/iface.transport_name *= *\(.*\)/\1/p' $iface) + loaded=$(sed -n "/^$mod/p" /proc/modules) + if [ -z "$loaded" ] ; then + modprobe $mod + if [ $? = 0 ] ; then + echo -n " $mod" + fi + fi + fi + done +} + +# +# Set a temporary startmode for ifdown +# +iscsi_modify_if_startmode() +{ + local ifname=$1 + local tmp_ifcfg=/dev/.sysconfig/network/if-$ifname + + if [ -e "$tmp_ifcfg" ] ; then + . $tmp_ifcfg + if [ "$startmode" ] ; then + return + fi + fi + : disabling shutdown on $ifname + echo "startmode=nfsroot" >> $tmp_ifcfg +} + +iscsi_get_ifacename_from_session() +{ + local session=$1 + local ifacename + + ifacename=$(iscsiadm -m session -r ${session##.*/session} 2> /dev/null | \ + sed -n 's/iface.iscsi_ifacename = \(.*\)/\1/p') + if [ -z "$ifacename" ] ; then + # Check for iBFT + ifacename=$(iscsiadm -m fw 2> /dev/null) + if [ -n "$ifacename" ] ; then + ifacename="fw" + fi + fi + echo $ifacename +} + +iscsi_get_hwaddress_from_iface() +{ + local iface=$1 + local hwaddress + + hwaddress=$(iscsiadm -m iface -I "$iface" 2> /dev/null | sed -n 's/iface.hwaddress = \(.*\)/\1/p') + [ "$hwaddress" = "" ] && hwaddress= + + echo $hwaddress +} + +iscsi_get_ifname_from_iface() +{ + local iface=$1 + local ifname + + ifname=$(iscsiadm -m iface -I "$iface" 2> /dev/null | sed -n 's/iface.net_ifacename = \(.*\)/\1/p') + [ "$ifname" = "" ] && ifname= + + echo $ifname +} + +iscsi_get_ipaddr_from_iface() +{ + local iface=$1 + local ipaddr + + ipaddr=$(iscsiadm -m iface -I "$iface" 2> /dev/null | sed -n 's/iface.ipaddress = \(.*\)/\1/p') + [ "$ipaddr" = "" ] && ipaddr= + + echo $ipaddr +} + +iscsi_get_ifname_from_firmware() +{ + local hwaddress + + hwaddress=$(iscsiadm -m fw 2> /dev/null | sed -n 's/iface.net_ifacename = \(.*\)/\1/p') + + echo $hwaddress +} + +# +# cxgb3i is using the HWAddress to select +# the correct interface +# +iscsi_get_ifname_from_hwaddress() +{ + local hwaddress=$1 + + for if in /sys/class/net/*; do + [ -e "$if" ] || continue + read mac < $if/address + [ "$mac" = "$hwaddress" ] || continue + echo ${if##*/} + break + done +} + +iscsi_get_ifname_from_ipaddr() +{ + local ipaddr=$1 + local ifname + + ifname=$(ip addr show to $ipaddr | sed -n 's/[0-9]*: \([^ :]*\): .*/\1/p') + return $ifname +} + +# +# Handle 'default' interface: +# It is basically impossible to determine via which +# interface the iSCSI traffic will flow, so we take +# the easy option and ignore _all_ active interfaces +# during shutdown +# +iscsi_modify_all_interfaces() +{ + ip link show up | sed -n '/.*LOOPBACK.*/d;s/[0-9]*: \(.*\): .*/\1/p' | while read ifname; do + iscsi_modify_if_startmode $ifname + done +} + +# +# Check iface setting and disable +# affected network interfaces +# +iscsi_check_interface() +{ + local session=$1 + local i h n + + i=$(iscsi_get_ifacename_from_session $session) + [ -z "$i" ] && continue + if [ "$i" = "default" ] ; then + iscsi_modify_all_interfaces + elif [ "$i" = "fw" ] ; then + n=$(iscsi_get_ifname_from_firmware) + else + n=$(iscsi_get_ifname_from_iface $i) + if [ -z "$n" ] ; then + h=$(iscsi_get_hwaddress_from_iface $i) + if [ -n "$h" ] ; then + n=$(iscsi_get_ifname_from_hwaddress $h) + fi + fi + if [ -z "$n" ] ; then + h=$(iscsi_get_ipaddr_from_iface $i) + if [ -n "$h" ] ; then + n=$(iscsi_get_ifname_from_ipaddr $h) + fi + fi + fi + if [ "$n" ] ; then + iscsi_modify_if_startmode $n + fi +} + +# +# Check if device 'dev' is mounted +# Returns the mount point on success +# +iscsi_check_if_mounted() +{ + local dev=$1 + local d m t o x p + + cat /proc/mounts | sed -ne '/^\/dev\/.*/p' | while read d m t o x; do + if [ -L "$d" ] ; then + d=$(readlink -f $d) + fi + [ -b "$d" ] || continue + + b=$(ls -l $d | sed -n 's/.* \([0-9]*\), \([0-9]*\) .*/\1:\2/p') + p=$(cd -P /sys/dev/block/$b ; echo $PWD) + + if [ -z "$p" ] ; then + d=${d##/dev} + p="/sys/block${d%%[0-9]*}" + fi + + [ ! -d ${p} ] && continue + + if [ -e $p/partition ] ; then + p=$(cd -P $p/../; echo $PWD) + fi + if [ "$dev" = "${p##*/}" ] ; then + echo $m + fi + done +} + +# +# Unwind block device stack +# +# Stops unwinding if either no more 'holders' +# are found or if a device is mounted +# +# Unmounts top-level device and deconfigures +# all devices down the stack +# +# Root fs is not unmounted +# +iscsi_unwind_stack() +{ + local p=$1 + local d=${p##*/} + local u + local m + + if [ ! -d ${p} ] ; then + return; + fi + + m=$(iscsi_check_if_mounted $d) + if [ -z "$m" ] ; then + for s in $p/holders/* ; do + [ -e $s ] || continue + p=$(cd -P $s; echo $PWD) + u=$(iscsi_unwind_stack $p) + if [ "$u" ] ; then + echo -n "$u " + fi + done + else + if [ "$m" = "/" ] ; then + echo -n "$d " + return 1 + fi + if ! umount $m ; then + echo -n "$d " + return 1 + fi + fi + + if [ "${d#dm-}" != "$d" ] ; then + if ! dmsetup remove -j $DM_MAJOR -m ${d#dm-} 2> /dev/null ; then + echo -n "$d " + return 1 + fi + fi + + if [ "${d#md}" != "$d" ] ; then + if ! mdadm --manage /dev/$d --stop 2> /dev/null ; then + echo -n "$d " + return 1 + fi + fi + return 0 +} + +# +# Return all targets for a given session +# +iscsi_get_target() +{ + local session=$1 + local d + + for d in $session/device/target* ; do + [ -e "$d" ] || continue + echo "$d" + done +} + +# +# Checks all devices presented by a target +# and tries to umount them. +# Skip unmounting for the root fs. +# Stops on the first device which could not be unmounted +# and returns the mount device of that device. +# +iscsi_check_target() +{ + local t=$1 + local d b m + + for d in $t/* ; do + [ -d $d/block ] || continue + for b in $d/block/sd* ; do + [ -d "$b" ] || continue + m=$(iscsi_unwind_stack $b) + if [ -n "$m" ] ; then + echo $m + return 1 + fi + done + done +} + +# +# Check all sessions for mounted devices +# and shutdown the session if the affected +# devices could be umounted cleanly. +# If umount fails disable shutdown on all +# affected network interfaces +# +iscsi_stop_sessions() +{ + local t m s i + + i=0 + for session in /sys/class/iscsi_session/session* ; do + [ -e "$session" ] || continue; + [ -e $session/device ] || continue + t=$(iscsi_get_target $session) + m=$(iscsi_check_target $t) + s=${session##*/session} + if [ -z "$m" ] ; then + iscsiadm -m session -r ${s} -u + i=$(( $i + 1 )) + else + iscsi_check_interface $s + fi + done + echo $i +} + +iscsi_list_all_nodes() +{ + # Check for active sessions + if $ISCSIADM -m session > /dev/null; then + return 0 + fi + echo "Active connections:" + $ISCSIADM -m session | while read proto num PORTAL TARGET ; do + PORTAL=${PORTAL%,*} + echo -e "\t$TARGET at $PORTAL" + done +} + +case "$1" in + start) + if checkproc $DAEMON ; then + RETVAL=0 + else + echo -n "Starting iSCSI initiator service: " + iscsi_load_transport_modules + if grep -q bnx2i /proc/modules && [ -x $BRCM_ISCSIUIO ] ; then + startproc $BRCM_ISCSIUIO + fi + startproc $DAEMON $ARGS + RETVAL=$? + rc_status -v + fi + if [ "$RETVAL" == "0" ]; then + iscsi_login_all_nodes + fi + ;; + stop) + n=$(iscsi_stop_sessions) + echo -n "Stopping iSCSI initiator service: " + if [ "$n" ] && [ "$n" != "0" ] ; then + m=$(iscsiadm -m session 2> /dev/null) + if [ -z "$m" ] ; then + killproc -KILL $DAEMON + RETVAL=$? + if grep -q bnx2i /proc/modules && [ -x $BRCM_ISCSIUIO ]; then + killproc -KILL $BRCM_ISCSIUIO + fi + RETVAL=$? + else + RETVAL=1 + fi + rc_failed $RETVAL + rc_status -v + else + # umounting failed, leave initiator running + rc_status -s + fi + ;; + status) + echo -n "Checking for iSCSI initiator service: " + if checkproc $DAEMON ; then + rc_status -v + iscsi_list_all_nodes + else + rc_failed 3 + rc_status -v + fi + ;; + restart|reload) + $0 stop + RETVAL=$? + if [ "$RETVAL" != "0" ]; then + echo "Stopping iSCSI initiator service failed, not starting" + exit $RETVAL + fi + sleep 1 + $0 start + ;; + *) + echo "Usage: $0 {start|stop|status|restart|reload}" + exit 1 + ;; +esac +rc_exit + diff --git a/etc/iscsid.conf b/etc/iscsid.conf new file mode 100644 index 0000000..70985af --- /dev/null +++ b/etc/iscsid.conf @@ -0,0 +1,328 @@ +# +# Open-iSCSI default configuration. +# Could be located at /etc/iscsi/iscsid.conf or ~/.iscsid.conf +# +# Note: To set any of these values for a specific node/session run +# the iscsiadm --mode node --op command for the value. See the README +# and man page for iscsiadm for details on the --op command. +# + +###################### +# iscsid daemon config +###################### +# +# If you want iscsid to start the first time an iscsi tool +# needs to access it, instead of starting it when the init +# scripts run, set the iscsid startup command here. This +# should normally only need to be done by distro package +# maintainers. If you leave the iscsid daemon running all +# the time then leave this attribute commented out. +# +# Default for Fedora and RHEL. (uncomment to activate). +# iscsid.startup = /bin/systemctl start iscsid.socket iscsiuio.soccket +# +# Default if you are not using systemd (uncomment to activate) +# iscsid.startup = /usr/bin/service start iscsid + +# Check for active mounts on devices reachable through a session +# and refuse to logout if there are any. Defaults to "No". +# iscsid.safe_logout = Yes + +############################# +# NIC/HBA and driver settings +############################# +# open-iscsi can create a session and bind it to a NIC/HBA. +# To set this up see the example iface config file. + +#***************** +# Startup settings +#***************** + +# To request that the iscsi initd scripts startup a session set to "automatic". +# node.startup = automatic +# +# To manually startup the session set to "manual". The default is manual. +node.startup = manual + +# For "automatic" startup nodes, setting this to "Yes" will try logins on each +# available iface until one succeeds, and then stop. The default "No" will try +# logins on all available ifaces simultaneously. +node.leading_login = No + +# ************* +# CHAP Settings +# ************* + +# To enable CHAP authentication set node.session.auth.authmethod +# to CHAP. The default is None. +#node.session.auth.authmethod = CHAP + +# To set a CHAP username and password for initiator +# authentication by the target(s), uncomment the following lines: +#node.session.auth.username = username +#node.session.auth.password = password + +# To set a CHAP username and password for target(s) +# authentication by the initiator, uncomment the following lines: +#node.session.auth.username_in = username_in +#node.session.auth.password_in = password_in + +# To enable CHAP authentication for a discovery session to the target +# set discovery.sendtargets.auth.authmethod to CHAP. The default is None. +#discovery.sendtargets.auth.authmethod = CHAP + +# To set a discovery session CHAP username and password for the initiator +# authentication by the target(s), uncomment the following lines: +#discovery.sendtargets.auth.username = username +#discovery.sendtargets.auth.password = password + +# To set a discovery session CHAP username and password for target(s) +# authentication by the initiator, uncomment the following lines: +#discovery.sendtargets.auth.username_in = username_in +#discovery.sendtargets.auth.password_in = password_in + +# ******** +# Timeouts +# ******** +# +# See the iSCSI README's Advanced Configuration section for tips +# on setting timeouts when using multipath or doing root over iSCSI. +# +# To specify the length of time to wait for session re-establishment +# before failing SCSI commands back to the application when running +# the Linux SCSI Layer error handler, edit the line. +# The value is in seconds and the default is 120 seconds. +# Special values: +# - If the value is 0, IO will be failed immediately. +# - If the value is less than 0, IO will remain queued until the session +# is logged back in, or until the user runs the logout command. +node.session.timeo.replacement_timeout = 120 + +# To specify the time to wait for login to complete, edit the line. +# The value is in seconds and the default is 15 seconds. +node.conn[0].timeo.login_timeout = 15 + +# To specify the time to wait for logout to complete, edit the line. +# The value is in seconds and the default is 15 seconds. +node.conn[0].timeo.logout_timeout = 15 + +# Time interval to wait for on connection before sending a ping. +node.conn[0].timeo.noop_out_interval = 5 + +# To specify the time to wait for a Nop-out response before failing +# the connection, edit this line. Failing the connection will +# cause IO to be failed back to the SCSI layer. If using dm-multipath +# this will cause the IO to be failed to the multipath layer. +node.conn[0].timeo.noop_out_timeout = 5 + +# To specify the time to wait for abort response before +# failing the operation and trying a logical unit reset edit the line. +# The value is in seconds and the default is 15 seconds. +node.session.err_timeo.abort_timeout = 15 + +# To specify the time to wait for a logical unit response +# before failing the operation and trying session re-establishment +# edit the line. +# The value is in seconds and the default is 30 seconds. +node.session.err_timeo.lu_reset_timeout = 30 + +# To specify the time to wait for a target response +# before failing the operation and trying session re-establishment +# edit the line. +# The value is in seconds and the default is 30 seconds. +node.session.err_timeo.tgt_reset_timeout = 30 + + +#****** +# Retry +#****** + +# To specify the number of times iscsid should retry a login +# if the login attempt fails due to the node.conn[0].timeo.login_timeout +# expiring modify the following line. Note that if the login fails +# quickly (before node.conn[0].timeo.login_timeout fires) because the network +# layer or the target returns an error, iscsid may retry the login more than +# node.session.initial_login_retry_max times. +# +# This retry count along with node.conn[0].timeo.login_timeout +# determines the maximum amount of time iscsid will try to +# establish the initial login. node.session.initial_login_retry_max is +# multiplied by the node.conn[0].timeo.login_timeout to determine the +# maximum amount. +# +# The default node.session.initial_login_retry_max is 8 and +# node.conn[0].timeo.login_timeout is 15 so we have: +# +# node.conn[0].timeo.login_timeout * node.session.initial_login_retry_max = +# 120 seconds +# +# Valid values are any integer value. This only +# affects the initial login. Setting it to a high value can slow +# down the iscsi service startup. Setting it to a low value can +# cause a session to not get logged into, if there are distuptions +# during startup or if the network is not ready at that time. +node.session.initial_login_retry_max = 8 + +################################ +# session and device queue depth +################################ + +# To control how many commands the session will queue set +# node.session.cmds_max to an integer between 2 and 2048 that is also +# a power of 2. The default is 128. +node.session.cmds_max = 128 + +# To control the device's queue depth set node.session.queue_depth +# to a value between 1 and 1024. The default is 32. +node.session.queue_depth = 32 + +################################## +# MISC SYSTEM PERFORMANCE SETTINGS +################################## + +# For software iscsi (iscsi_tcp) and iser (ib_iser) each session +# has a thread used to transmit or queue data to the hardware. For +# cxgb3i you will get a thread per host. +# +# Setting the thread's priority to a lower value can lead to higher throughput +# and lower latencies. The lowest value is -20. Setting the priority to +# a higher value, can lead to reduced IO performance, but if you are seeing +# the iscsi or scsi threads dominate the use of the CPU then you may want +# to set this value higher. +# +# Note: For cxgb3i you must set all sessions to the same value, or the +# behavior is not defined. +# +# The default value is -20. The setting must be between -20 and 20. +node.session.xmit_thread_priority = -20 + + +#*************** +# iSCSI settings +#*************** + +# To enable R2T flow control (i.e., the initiator must wait for an R2T +# command before sending any data), uncomment the following line: +# +#node.session.iscsi.InitialR2T = Yes +# +# To disable R2T flow control (i.e., the initiator has an implied +# initial R2T of "FirstBurstLength" at offset 0), uncomment the following line: +# +# The defaults is No. +node.session.iscsi.InitialR2T = No + +# +# To disable immediate data (i.e., the initiator does not send +# unsolicited data with the iSCSI command PDU), uncomment the following line: +# +#node.session.iscsi.ImmediateData = No +# +# To enable immediate data (i.e., the initiator sends unsolicited data +# with the iSCSI command packet), uncomment the following line: +# +# The default is Yes +node.session.iscsi.ImmediateData = Yes + +# To specify the maximum number of unsolicited data bytes the initiator +# can send in an iSCSI PDU to a target, edit the following line. +# +# The value is the number of bytes in the range of 512 to (2^24-1) and +# the default is 262144 +node.session.iscsi.FirstBurstLength = 262144 + +# To specify the maximum SCSI payload that the initiator will negotiate +# with the target for, edit the following line. +# +# The value is the number of bytes in the range of 512 to (2^24-1) and +# the defauls it 16776192 +node.session.iscsi.MaxBurstLength = 16776192 + +# To specify the maximum number of data bytes the initiator can receive +# in an iSCSI PDU from a target, edit the following line. +# +# The value is the number of bytes in the range of 512 to (2^24-1) and +# the default is 262144 +node.conn[0].iscsi.MaxRecvDataSegmentLength = 262144 + +# To specify the maximum number of data bytes the initiator will send +# in an iSCSI PDU to the target, edit the following line. +# +# The value is the number of bytes in the range of 512 to (2^24-1). +# Zero is a special case. If set to zero, the initiator will use +# the target's MaxRecvDataSegmentLength for the MaxXmitDataSegmentLength. +# The default is 0. +node.conn[0].iscsi.MaxXmitDataSegmentLength = 0 + +# To specify the maximum number of data bytes the initiator can receive +# in an iSCSI PDU from a target during a discovery session, edit the +# following line. +# +# The value is the number of bytes in the range of 512 to (2^24-1) and +# the default is 32768 +# +discovery.sendtargets.iscsi.MaxRecvDataSegmentLength = 32768 + +# To allow the targets to control the setting of the digest checking, +# with the initiator requesting a preference of enabling the checking, uncomment# one or both of the following lines: +#node.conn[0].iscsi.HeaderDigest = CRC32C,None +#node.conn[0].iscsi.DataDigest = CRC32C,None +# +# To allow the targets to control the setting of the digest checking, +# with the initiator requesting a preference of disabling the checking, +# uncomment one or both of the following lines: +#node.conn[0].iscsi.HeaderDigest = None,CRC32C +#node.conn[0].iscsi.DataDigest = None,CRC32C +# +# To enable CRC32C digest checking for the header and/or data part of +# iSCSI PDUs, uncomment one or both of the following lines: +#node.conn[0].iscsi.HeaderDigest = CRC32C +#node.conn[0].iscsi.DataDigest = CRC32C +# +# To disable digest checking for the header and/or data part of +# iSCSI PDUs, uncomment one or both of the following lines: +#node.conn[0].iscsi.HeaderDigest = None +#node.conn[0].iscsi.DataDigest = None +# +# The default is to never use DataDigests or HeaderDigests. +# + +# For multipath configurations, you may want more than one session to be +# created on each iface record. If node.session.nr_sessions is greater +# than 1, performing a 'login' for that node will ensure that the +# appropriate number of sessions is created. +node.session.nr_sessions = 1 + +# When iscsid starts up it recovers existing sessions, if possible. +# If the target for a session has gone away when this occurs, the +# iscsid daemon normally tries to reestablish each session, +# in succession, in the background, by trying again every two +# seconds, until all sessions are restored. This configuration +# variable can limits the number of retries for each session. +# For example, setting reopen_max=150 would mean that each session +# recovery was limited to about five minutes. +# +node.session.reopen_max = 0 + +#************ +# Workarounds +#************ + +# Some targets like IET prefer after an initiator has sent a task +# management function like an ABORT TASK or LOGICAL UNIT RESET, that +# it does not respond to PDUs like R2Ts. To enable this behavior uncomment +# the following line (The default behavior is Yes): +node.session.iscsi.FastAbort = Yes + +# Some targets like Equalogic prefer that after an initiator has sent +# a task management function like an ABORT TASK or LOGICAL UNIT RESET, that +# it continue to respond to R2Ts. To enable this uncomment this line +# node.session.iscsi.FastAbort = No + +# To prevent doing automatic scans that would add unwanted luns to the system +# we can disable them and have sessions only do manually requested scans. +# Automatic scans are performed on startup, on login, and on AEN/AER reception +# on devices supporting it. For HW drivers all sessions will use the value +# defined in the configuration file. This configuration option is independent +# of scsi_mod scan parameter. (The default behavior is auto): +node.session.scan = auto diff --git a/etc/systemd/iscsi.service b/etc/systemd/iscsi.service new file mode 100644 index 0000000..e475888 --- /dev/null +++ b/etc/systemd/iscsi.service @@ -0,0 +1,18 @@ +[Unit] +Description=Login and scanning of iSCSI devices +Documentation=man:iscsiadm(8) man:iscsid(8) +Before=remote-fs.target +After=network.target network-online.target iscsid.service +Requires=iscsid.service +ConditionPathExists=/etc/iscsi/initiatorname.iscsi + +[Service] +Type=oneshot +ExecStart=/sbin/iscsiadm -m node --loginall=automatic +ExecStop=/sbin/iscsiadm -m node --logoutall=automatic +ExecStop=/sbin/iscsiadm -m node --logoutall=manual +SuccessExitStatus=21 +RemainAfterExit=true + +[Install] +WantedBy=remote-fs.target diff --git a/etc/systemd/iscsid.service b/etc/systemd/iscsid.service new file mode 100644 index 0000000..4fef168 --- /dev/null +++ b/etc/systemd/iscsid.service @@ -0,0 +1,17 @@ +[Unit] +Description=Open-iSCSI +Documentation=man:iscsid(8) man:iscsiuio(8) man:iscsiadm(8) +DefaultDependencies=no +After=network.target iscsiuio.service +Before=remote-fs-pre.target + +[Service] +Type=notify +NotifyAccess=main +ExecStart=/sbin/iscsid -f +KillMode=mixed +Restart=on-failure + +[Install] +WantedBy=multi-user.target +Also=iscsid.socket diff --git a/etc/systemd/iscsid.socket b/etc/systemd/iscsid.socket new file mode 100644 index 0000000..58a8d12 --- /dev/null +++ b/etc/systemd/iscsid.socket @@ -0,0 +1,9 @@ +[Unit] +Description=Open-iSCSI iscsid Socket +Documentation=man:iscsid(8) man:iscsiadm(8) + +[Socket] +ListenStream=@ISCSIADM_ABSTRACT_NAMESPACE + +[Install] +WantedBy=sockets.target diff --git a/etc/systemd/iscsiuio.service b/etc/systemd/iscsiuio.service new file mode 100644 index 0000000..e4d9fd0 --- /dev/null +++ b/etc/systemd/iscsiuio.service @@ -0,0 +1,19 @@ +[Unit] +Description=iSCSI UserSpace I/O driver +Documentation=man:iscsiuio(8) +DefaultDependencies=no +Conflicts=shutdown.target +Requires=iscsid.service +BindTo=iscsid.service +After=network.target +Before=remote-fs-pre.target iscsid.service + +[Service] +Type=notify +NotifyAccess=main +ExecStart=/sbin/iscsiuio -f +KillMode=mixed +Restart=on-failure + +[Install] +WantedBy=multi-user.target diff --git a/etc/systemd/iscsiuio.socket b/etc/systemd/iscsiuio.socket new file mode 100644 index 0000000..d42cedc --- /dev/null +++ b/etc/systemd/iscsiuio.socket @@ -0,0 +1,9 @@ +[Unit] +Description=Open-iSCSI iscsiuio Socket +Documentation=man:iscsiuio(8) + +[Socket] +ListenStream=@ISCSID_UIP_ABSTRACT_NAMESPACE + +[Install] +WantedBy=sockets.target diff --git a/include/fw_context.h b/include/fw_context.h new file mode 100644 index 0000000..88c6a02 --- /dev/null +++ b/include/fw_context.h @@ -0,0 +1,84 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright (C) IBM Corporation. 2007 + * Author: Doug Maxey + * "Prasanna Mumbai" + * + */ +#ifndef FWPARAM_CONTEXT_H_ +#define FWPARAM_CONTEXT_H_ + +#include +#include + +#include "iscsi_proto.h" +#include "list.h" +#include "auth.h" + +enum ibft_ip_prefix_origin { + IBFT_IP_PREFIX_ORIGIN_OTHER = 0, + IBFT_IP_PREFIX_ORIGIN_MANUAL, + IBFT_IP_PREFIX_ORIGIN_WELL_KNOWN, + IBFT_IP_PREFIX_ORIGIN_DHCP, + IBFT_IP_PREFIX_ORIGIN_ROUTER_ADVERTISEMENT, + IBFT_IP_PREFIX_ORIGIN_UNCHANGED = 16 +}; + +struct boot_context { + struct list_head list; + char boot_root[BOOT_NAME_MAXLEN]; + char boot_nic[BOOT_NAME_MAXLEN]; + char boot_target[BOOT_NAME_MAXLEN]; + + /* target settings */ + int target_flags; + int target_port; + char targetname[TARGET_NAME_MAXLEN + 1]; + char target_ipaddr[NI_MAXHOST]; + char chap_name[AUTH_STR_MAX_LEN]; + char chap_password[AUTH_STR_MAX_LEN]; + char chap_name_in[AUTH_STR_MAX_LEN]; + char chap_password_in[AUTH_STR_MAX_LEN]; + + /* initiator settings */ + char isid[10]; + char initiatorname[TARGET_NAME_MAXLEN + 1]; + + /* network settings */ + int nic_flags; + enum ibft_ip_prefix_origin origin; + int prefix; + char dhcp[NI_MAXHOST]; + char iface[IF_NAMESIZE]; + char mac[18]; + char ipaddr[NI_MAXHOST]; + char gateway[NI_MAXHOST]; + char primary_dns[NI_MAXHOST]; + char secondary_dns[NI_MAXHOST]; + char mask[NI_MAXHOST]; + char lun[17]; + char vlan[15]; + + char scsi_host_name[64]; +}; + +extern int fw_get_entry(struct boot_context *context); +extern void fw_print_entry(struct boot_context *context); +extern int fw_get_targets(struct list_head *list); +extern void fw_free_targets(struct list_head *list); +extern int fw_setup_nics(void); + +#endif /* FWPARAM_CONTEXT_H_ */ diff --git a/include/iscsi_err.h b/include/iscsi_err.h new file mode 100644 index 0000000..ed000dd --- /dev/null +++ b/include/iscsi_err.h @@ -0,0 +1,81 @@ +/* + * Return codes used by iSCSI tools. + */ +#ifndef _ISCSI_ERR_ +#define _ISCSI_ERR_ + +enum { + ISCSI_SUCCESS = 0, + /* Generic error */ + ISCSI_ERR = 1, + /* session could not be found */ + ISCSI_ERR_SESS_NOT_FOUND = 2, + /* Could not allocate resource for operation */ + ISCSI_ERR_NOMEM = 3, + /* Transport error caused operation to fail */ + ISCSI_ERR_TRANS = 4, + /* Generic login failure */ + ISCSI_ERR_LOGIN = 5, + /* Error accessing/managing iSCSI DB */ + ISCSI_ERR_IDBM = 6, + /* Invalid argument */ + ISCSI_ERR_INVAL = 7, + /* Connection timer exired while trying to connect */ + ISCSI_ERR_TRANS_TIMEOUT = 8, + /* Generic internal iscsid failure */ + ISCSI_ERR_INTERNAL = 9, + /* Logout failed */ + ISCSI_ERR_LOGOUT = 10, + /* iSCSI PDU timedout */ + ISCSI_ERR_PDU_TIMEOUT = 11, + /* iSCSI transport module not loaded in kernel or iscsid */ + ISCSI_ERR_TRANS_NOT_FOUND = 12, + /* Permission denied */ + ISCSI_ERR_ACCESS = 13, + /* Transport module did not support operation */ + ISCSI_ERR_TRANS_CAPS = 14, + /* Session is logged in */ + ISCSI_ERR_SESS_EXISTS = 15, + /* Invalid IPC MGMT request */ + ISCSI_ERR_INVALID_MGMT_REQ = 16, + /* iSNS service is not supported */ + ISCSI_ERR_ISNS_UNAVAILABLE = 17, + /* A read/write to iscsid failed */ + ISCSI_ERR_ISCSID_COMM_ERR = 18, + /* Fatal login error */ + ISCSI_ERR_FATAL_LOGIN = 19, + /* Could ont connect to iscsid */ + ISCSI_ERR_ISCSID_NOTCONN = 20, + /* No records/targets/sessions/portals found to execute operation on */ + ISCSI_ERR_NO_OBJS_FOUND = 21, + /* Could not lookup object in sysfs */ + ISCSI_ERR_SYSFS_LOOKUP = 22, + /* Could not lookup host */ + ISCSI_ERR_HOST_NOT_FOUND = 23, + /* Login failed due to authorization failure */ + ISCSI_ERR_LOGIN_AUTH_FAILED = 24, + /* iSNS query failure */ + ISCSI_ERR_ISNS_QUERY = 25, + /* iSNS registration/deregistration failed */ + ISCSI_ERR_ISNS_REG_FAILED = 26, + /* operation not supported */ + ISCSI_ERR_OP_NOT_SUPP = 27, + /* device or resource in use */ + ISCSI_ERR_BUSY = 28, + /* Operation failed, but retrying layer may succeed */ + ISCSI_ERR_AGAIN = 29, + /* unknown discovery type */ + ISCSI_ERR_UNKNOWN_DISCOVERY_TYPE = 30, + /* child process terminated */ + ISCSI_ERR_CHILD_TERMINATED = 31, + /* session likely not connected */ + ISCSI_ERR_SESSION_NOT_CONNECTED = 32, + + /* Always last. Indicates end of error code space */ + ISCSI_MAX_ERR_VAL, +} iscsi_err; + +extern void iscsi_err_print_msg(int err); +extern char *iscsi_err_to_str(int err); + +#endif diff --git a/include/iscsi_if.h b/include/iscsi_if.h new file mode 100644 index 0000000..2d46214 --- /dev/null +++ b/include/iscsi_if.h @@ -0,0 +1,979 @@ +/* + * iSCSI User/Kernel Shares (Defines, Constants, Protocol definitions, etc) + * + * Copyright (C) 2005 Dmitry Yusupov + * Copyright (C) 2005 Alex Aizman + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#ifndef ISCSI_IF_H +#define ISCSI_IF_H + +#ifdef __KERNEL__ +#include +#include +#else +#include +#endif + +#include "iscsi_proto.h" + +/* + * NOTE: This file should be kept in sync with the kernel include file + * of the same name. In particular, iscsi_param and iscsi_err need + * to be in sync. + */ + +#define ISCSI_NL_GRP_ISCSID 1 +#define ISCSI_NL_GRP_UIP 2 + +#define UEVENT_BASE 10 +#define KEVENT_BASE 100 +#define ISCSI_ERR_BASE 1000 + +enum iscsi_uevent_e { + ISCSI_UEVENT_UNKNOWN = 0, + + /* down events */ + ISCSI_UEVENT_CREATE_SESSION = UEVENT_BASE + 1, + ISCSI_UEVENT_DESTROY_SESSION = UEVENT_BASE + 2, + ISCSI_UEVENT_CREATE_CONN = UEVENT_BASE + 3, + ISCSI_UEVENT_DESTROY_CONN = UEVENT_BASE + 4, + ISCSI_UEVENT_BIND_CONN = UEVENT_BASE + 5, + ISCSI_UEVENT_SET_PARAM = UEVENT_BASE + 6, + ISCSI_UEVENT_START_CONN = UEVENT_BASE + 7, + ISCSI_UEVENT_STOP_CONN = UEVENT_BASE + 8, + ISCSI_UEVENT_SEND_PDU = UEVENT_BASE + 9, + ISCSI_UEVENT_GET_STATS = UEVENT_BASE + 10, + ISCSI_UEVENT_GET_PARAM = UEVENT_BASE + 11, + + ISCSI_UEVENT_TRANSPORT_EP_CONNECT = UEVENT_BASE + 12, + ISCSI_UEVENT_TRANSPORT_EP_POLL = UEVENT_BASE + 13, + ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT = UEVENT_BASE + 14, + + ISCSI_UEVENT_TGT_DSCVR = UEVENT_BASE + 15, + ISCSI_UEVENT_SET_HOST_PARAM = UEVENT_BASE + 16, + ISCSI_UEVENT_UNBIND_SESSION = UEVENT_BASE + 17, + ISCSI_UEVENT_CREATE_BOUND_SESSION = UEVENT_BASE + 18, + ISCSI_UEVENT_TRANSPORT_EP_CONNECT_THROUGH_HOST = UEVENT_BASE + 19, + + ISCSI_UEVENT_PATH_UPDATE = UEVENT_BASE + 20, + ISCSI_UEVENT_SET_IFACE_PARAMS = UEVENT_BASE + 21, + ISCSI_UEVENT_PING = UEVENT_BASE + 22, + ISCSI_UEVENT_GET_CHAP = UEVENT_BASE + 23, + ISCSI_UEVENT_DELETE_CHAP = UEVENT_BASE + 24, + ISCSI_UEVENT_SET_FLASHNODE_PARAMS = UEVENT_BASE + 25, + ISCSI_UEVENT_NEW_FLASHNODE = UEVENT_BASE + 26, + ISCSI_UEVENT_DEL_FLASHNODE = UEVENT_BASE + 27, + ISCSI_UEVENT_LOGIN_FLASHNODE = UEVENT_BASE + 28, + ISCSI_UEVENT_LOGOUT_FLASHNODE = UEVENT_BASE + 29, + ISCSI_UEVENT_LOGOUT_FLASHNODE_SID = UEVENT_BASE + 30, + ISCSI_UEVENT_SET_CHAP = UEVENT_BASE + 31, + ISCSI_UEVENT_GET_HOST_STATS = UEVENT_BASE + 32, + ISCSI_UEVENT_MAX = ISCSI_UEVENT_GET_HOST_STATS, + + /* up events */ + ISCSI_KEVENT_RECV_PDU = KEVENT_BASE + 1, + ISCSI_KEVENT_CONN_ERROR = KEVENT_BASE + 2, + ISCSI_KEVENT_IF_ERROR = KEVENT_BASE + 3, + ISCSI_KEVENT_DESTROY_SESSION = KEVENT_BASE + 4, + ISCSI_KEVENT_UNBIND_SESSION = KEVENT_BASE + 5, + ISCSI_KEVENT_CREATE_SESSION = KEVENT_BASE + 6, + + ISCSI_KEVENT_PATH_REQ = KEVENT_BASE + 7, + ISCSI_KEVENT_IF_DOWN = KEVENT_BASE + 8, + ISCSI_KEVENT_CONN_LOGIN_STATE = KEVENT_BASE + 9, + ISCSI_KEVENT_HOST_EVENT = KEVENT_BASE + 10, + ISCSI_KEVENT_PING_COMP = KEVENT_BASE + 11, + + ISCSI_KEVENT_MAX = ISCSI_KEVENT_PING_COMP, +}; + +enum iscsi_tgt_dscvr { + ISCSI_TGT_DSCVR_SEND_TARGETS = 1, + ISCSI_TGT_DSCVR_ISNS = 2, + ISCSI_TGT_DSCVR_SLP = 3, +}; + +enum iscsi_host_event_code { + ISCSI_EVENT_LINKUP = 1, + ISCSI_EVENT_LINKDOWN, + /* must always be last */ + ISCSI_EVENT_MAX, +}; + +struct iscsi_uevent { + uint32_t type; /* k/u events type */ + uint32_t iferror; /* carries interface or resource errors */ + uint64_t transport_handle; + + union { + /* messages u -> k */ + struct msg_create_session { + uint32_t initial_cmdsn; + uint16_t cmds_max; + uint16_t queue_depth; + } c_session; + struct msg_create_bound_session { + uint64_t ep_handle; + uint32_t initial_cmdsn; + uint16_t cmds_max; + uint16_t queue_depth; + } c_bound_session; + struct msg_destroy_session { + uint32_t sid; + } d_session; + struct msg_create_conn { + uint32_t sid; + uint32_t cid; + } c_conn; + struct msg_bind_conn { + uint32_t sid; + uint32_t cid; + uint64_t transport_eph; + uint32_t is_leading; + } b_conn; + struct msg_destroy_conn { + uint32_t sid; + uint32_t cid; + } d_conn; + struct msg_send_pdu { + uint32_t sid; + uint32_t cid; + uint32_t hdr_size; + uint32_t data_size; + } send_pdu; + struct msg_set_param { + uint32_t sid; + uint32_t cid; + uint32_t param; /* enum iscsi_param */ + uint32_t len; + } set_param; + struct msg_start_conn { + uint32_t sid; + uint32_t cid; + } start_conn; + struct msg_stop_conn { + uint32_t sid; + uint32_t cid; + uint64_t conn_handle; + uint32_t flag; + } stop_conn; + struct msg_get_stats { + uint32_t sid; + uint32_t cid; + } get_stats; + struct msg_transport_connect { + uint32_t non_blocking; + } ep_connect; + struct msg_transport_connect_through_host { + uint32_t host_no; + uint32_t non_blocking; + } ep_connect_through_host; + struct msg_transport_poll { + uint64_t ep_handle; + uint32_t timeout_ms; + } ep_poll; + struct msg_transport_disconnect { + uint64_t ep_handle; + } ep_disconnect; + struct msg_tgt_dscvr { + enum iscsi_tgt_dscvr type; + uint32_t host_no; + /* + * enable = 1 to establish a new connection + * with the server. enable = 0 to disconnect + * from the server. Used primarily to switch + * from one iSNS server to another. + */ + uint32_t enable; + } tgt_dscvr; + struct msg_set_host_param { + uint32_t host_no; + uint32_t param; /* enum iscsi_host_param */ + uint32_t len; + } set_host_param; + struct msg_set_path { + uint32_t host_no; + } set_path; + struct msg_set_iface_params { + uint32_t host_no; + uint32_t count; + } set_iface_params; + struct msg_iscsi_ping { + uint32_t host_no; + uint32_t iface_num; + uint32_t iface_type; + uint32_t payload_size; + uint32_t pid; /* unique ping id associated + with each ping request */ + } iscsi_ping; + struct msg_get_chap { + uint32_t host_no; + uint32_t num_entries; /* number of CHAP entries + * on request, number of + * valid CHAP entries on + * response */ + uint16_t chap_tbl_idx; + } get_chap; + struct msg_delete_chap { + uint32_t host_no; + uint16_t chap_tbl_idx; + } delete_chap; + struct msg_set_flashnode_param { + uint32_t host_no; + uint32_t flashnode_idx; + uint32_t count; + } set_flashnode; + struct msg_new_flashnode { + uint32_t host_no; + uint32_t len; + } new_flashnode; + struct msg_del_flashnode { + uint32_t host_no; + uint32_t flashnode_idx; + } del_flashnode; + struct msg_login_flashnode { + uint32_t host_no; + uint32_t flashnode_idx; + } login_flashnode; + struct msg_logout_flashnode { + uint32_t host_no; + uint32_t flashnode_idx; + } logout_flashnode; + struct msg_logout_flashnode_sid { + uint32_t host_no; + uint32_t sid; + } logout_flashnode_sid; + struct msg_get_host_stats { + uint32_t host_no; + } get_host_stats; + + } u; + union { + /* messages k -> u */ + int retcode; + struct msg_create_session_ret { + uint32_t sid; + uint32_t host_no; + } c_session_ret; + struct msg_create_conn_ret { + uint32_t sid; + uint32_t cid; + } c_conn_ret; + struct msg_unbind_session { + uint32_t sid; + uint32_t host_no; + } unbind_session; + struct msg_recv_req { + uint32_t sid; + uint32_t cid; + uint64_t recv_handle; + } recv_req; + struct msg_conn_login { + uint32_t sid; + uint32_t cid; + uint32_t state; /* enum iscsi_conn_state */ + } conn_login; + struct msg_conn_error { + uint32_t sid; + uint32_t cid; + uint32_t error; /* enum iscsi_err */ + } connerror; + struct msg_session_destroyed { + uint32_t host_no; + uint32_t sid; + } d_session; + struct msg_transport_connect_ret { + uint64_t handle; + } ep_connect_ret; + struct msg_req_path { + uint32_t host_no; + } req_path; + struct msg_notify_if_down { + uint32_t host_no; + } notify_if_down; + struct msg_host_event { + uint32_t host_no; + uint32_t data_size; + enum iscsi_host_event_code code; + } host_event; + struct msg_ping_comp { + uint32_t host_no; + uint32_t status; /* enum + * iscsi_ping_status_code */ + uint32_t pid; /* unique ping id associated + with each ping request */ + uint32_t data_size; + } ping_comp; + struct msg_new_flashnode_ret { + uint32_t flashnode_idx; + } new_flashnode_ret; + } r; +} __attribute__ ((aligned (sizeof(uint64_t)))); + +enum iscsi_param_type { + ISCSI_PARAM, /* iscsi_param (session, conn, target, LU) */ + ISCSI_HOST_PARAM, /* iscsi_host_param */ + ISCSI_NET_PARAM, /* iscsi_net_param */ + ISCSI_FLASHNODE_PARAM, /* iscsi_flashnode_param */ + ISCSI_CHAP_PARAM, /* iscsi_chap_param */ + ISCSI_IFACE_PARAM, /* iscsi_iface_param */ +}; + +/* structure for minimalist usecase */ +struct iscsi_param_info { + uint32_t len; /* Actual length of the param value */ + uint16_t param; /* iscsi param */ + uint8_t value[0]; /* length sized value follows */ +} __attribute__((__packed__)); + +struct iscsi_iface_param_info { + uint32_t iface_num; /* iface number, 0 - n */ + uint32_t len; /* Actual length of the param */ + uint16_t param; /* iscsi param value */ + uint8_t iface_type; /* IPv4 or IPv6 */ + uint8_t param_type; /* iscsi_param_type */ + uint8_t value[0]; /* length sized value follows */ +} __attribute__((__packed__)); + +/* + * To keep the struct iscsi_uevent size the same for userspace code + * compatibility, the main structure for ISCSI_UEVENT_PATH_UPDATE and + * ISCSI_KEVENT_PATH_REQ is defined separately and comes after the + * struct iscsi_uevent in the NETLINK_ISCSI message. + */ +struct iscsi_path { + uint64_t handle; + uint8_t mac_addr[6]; + uint8_t mac_addr_old[6]; + uint32_t ip_addr_len; /* 4 or 16 */ + union { + struct in_addr v4_addr; + struct in6_addr v6_addr; + } src; + union { + struct in_addr v4_addr; + struct in6_addr v6_addr; + } dst; + uint16_t vlan_id; + uint16_t pmtu; +} __attribute__ ((aligned (sizeof(uint64_t)))); + +/* iscsi iface enabled/disabled setting */ +#define ISCSI_IFACE_DISABLE 0x01 +#define ISCSI_IFACE_ENABLE 0x02 + +/* ipv4 bootproto */ +#define ISCSI_BOOTPROTO_STATIC 0x01 +#define ISCSI_BOOTPROTO_DHCP 0x02 + +/* ipv6 addr autoconfig type */ +#define ISCSI_IPV6_AUTOCFG_DISABLE 0x01 +#define ISCSI_IPV6_AUTOCFG_ND_ENABLE 0x02 +#define ISCSI_IPV6_AUTOCFG_DHCPV6_ENABLE 0x03 + +/* ipv6 link local addr type */ +#define ISCSI_IPV6_LINKLOCAL_AUTOCFG_ENABLE 0x01 +#define ISCSI_IPV6_LINKLOCAL_AUTOCFG_DISABLE 0x02 + +/* ipv6 router addr type */ +#define ISCSI_IPV6_ROUTER_AUTOCFG_ENABLE 0x01 +#define ISCSI_IPV6_ROUTER_AUTOCFG_DISABLE 0x02 + +#define ISCSI_IFACE_TYPE_IPV4 0x01 +#define ISCSI_IFACE_TYPE_IPV6 0x02 + +#define ISCSI_MAX_VLAN_ID 4095 +#define ISCSI_MAX_VLAN_PRIORITY 7 + +/* iscsi vlan enable/disabled setting */ +#define ISCSI_VLAN_DISABLE 0x01 +#define ISCSI_VLAN_ENABLE 0x02 + +/* iscsi generic enable/disabled setting for various features */ +#define ISCSI_NET_PARAM_DISABLE 0x01 +#define ISCSI_NET_PARAM_ENABLE 0x02 + +/* iSCSI network params */ +enum iscsi_net_param { + ISCSI_NET_PARAM_IPV4_ADDR = 1, + ISCSI_NET_PARAM_IPV4_SUBNET, + ISCSI_NET_PARAM_IPV4_GW, + ISCSI_NET_PARAM_IPV4_BOOTPROTO, + ISCSI_NET_PARAM_MAC, + ISCSI_NET_PARAM_IPV6_LINKLOCAL, + ISCSI_NET_PARAM_IPV6_ADDR, + ISCSI_NET_PARAM_IPV6_ROUTER, + ISCSI_NET_PARAM_IPV6_ADDR_AUTOCFG, + ISCSI_NET_PARAM_IPV6_LINKLOCAL_AUTOCFG, + ISCSI_NET_PARAM_IPV6_ROUTER_AUTOCFG, + ISCSI_NET_PARAM_IFACE_ENABLE, + ISCSI_NET_PARAM_VLAN_ID, + ISCSI_NET_PARAM_VLAN_PRIORITY, + ISCSI_NET_PARAM_VLAN_ENABLED, + ISCSI_NET_PARAM_VLAN_TAG, + ISCSI_NET_PARAM_IFACE_TYPE, + ISCSI_NET_PARAM_IFACE_NAME, + ISCSI_NET_PARAM_MTU, + ISCSI_NET_PARAM_PORT, + ISCSI_NET_PARAM_IPADDR_STATE, + ISCSI_NET_PARAM_IPV6_LINKLOCAL_STATE, + ISCSI_NET_PARAM_IPV6_ROUTER_STATE, + ISCSI_NET_PARAM_DELAYED_ACK_EN, + ISCSI_NET_PARAM_TCP_NAGLE_DISABLE, + ISCSI_NET_PARAM_TCP_WSF_DISABLE, + ISCSI_NET_PARAM_TCP_WSF, + ISCSI_NET_PARAM_TCP_TIMER_SCALE, + ISCSI_NET_PARAM_TCP_TIMESTAMP_EN, + ISCSI_NET_PARAM_CACHE_ID, + ISCSI_NET_PARAM_IPV4_DHCP_DNS_ADDR_EN, + ISCSI_NET_PARAM_IPV4_DHCP_SLP_DA_EN, + ISCSI_NET_PARAM_IPV4_TOS_EN, + ISCSI_NET_PARAM_IPV4_TOS, + ISCSI_NET_PARAM_IPV4_GRAT_ARP_EN, + ISCSI_NET_PARAM_IPV4_DHCP_ALT_CLIENT_ID_EN, + ISCSI_NET_PARAM_IPV4_DHCP_ALT_CLIENT_ID, + ISCSI_NET_PARAM_IPV4_DHCP_REQ_VENDOR_ID_EN, + ISCSI_NET_PARAM_IPV4_DHCP_USE_VENDOR_ID_EN, + ISCSI_NET_PARAM_IPV4_DHCP_VENDOR_ID, + ISCSI_NET_PARAM_IPV4_DHCP_LEARN_IQN_EN, + ISCSI_NET_PARAM_IPV4_FRAGMENT_DISABLE, + ISCSI_NET_PARAM_IPV4_IN_FORWARD_EN, + ISCSI_NET_PARAM_IPV4_TTL, + ISCSI_NET_PARAM_IPV6_GRAT_NEIGHBOR_ADV_EN, + ISCSI_NET_PARAM_IPV6_MLD_EN, + ISCSI_NET_PARAM_IPV6_FLOW_LABEL, + ISCSI_NET_PARAM_IPV6_TRAFFIC_CLASS, + ISCSI_NET_PARAM_IPV6_HOP_LIMIT, + ISCSI_NET_PARAM_IPV6_ND_REACHABLE_TMO, + ISCSI_NET_PARAM_IPV6_ND_REXMIT_TIME, + ISCSI_NET_PARAM_IPV6_ND_STALE_TMO, + ISCSI_NET_PARAM_IPV6_DUP_ADDR_DETECT_CNT, + ISCSI_NET_PARAM_IPV6_RTR_ADV_LINK_MTU, + ISCSI_NET_PARAM_REDIRECT_EN, +}; + +enum iscsi_ipaddress_state { + ISCSI_IPDDRESS_STATE_UNCONFIGURED, + ISCSI_IPDDRESS_STATE_ACQUIRING, + ISCSI_IPDDRESS_STATE_TENTATIVE, + ISCSI_IPDDRESS_STATE_VALID, + ISCSI_IPDDRESS_STATE_DISABLING, + ISCSI_IPDDRESS_STATE_INVALID, + ISCSI_IPDDRESS_STATE_DEPRECATED, +}; + +enum iscsi_router_state { + ISCSI_ROUTER_STATE_UNKNOWN, + ISCSI_ROUTER_STATE_ADVERTISED, + ISCSI_ROUTER_STATE_MANUAL, + ISCSI_ROUTER_STATE_STALE, +}; + +/* iSCSI specific settings params for iface */ +enum iscsi_iface_param { + ISCSI_IFACE_PARAM_DEF_TASKMGMT_TMO, + ISCSI_IFACE_PARAM_HDRDGST_EN, + ISCSI_IFACE_PARAM_DATADGST_EN, + ISCSI_IFACE_PARAM_IMM_DATA_EN, + ISCSI_IFACE_PARAM_INITIAL_R2T_EN, + ISCSI_IFACE_PARAM_DATASEQ_INORDER_EN, + ISCSI_IFACE_PARAM_PDU_INORDER_EN, + ISCSI_IFACE_PARAM_ERL, + ISCSI_IFACE_PARAM_MAX_RECV_DLENGTH, + ISCSI_IFACE_PARAM_FIRST_BURST, + ISCSI_IFACE_PARAM_MAX_R2T, + ISCSI_IFACE_PARAM_MAX_BURST, + ISCSI_IFACE_PARAM_CHAP_AUTH_EN, + ISCSI_IFACE_PARAM_BIDI_CHAP_EN, + ISCSI_IFACE_PARAM_DISCOVERY_AUTH_OPTIONAL, + ISCSI_IFACE_PARAM_DISCOVERY_LOGOUT_EN, + ISCSI_IFACE_PARAM_STRICT_LOGIN_COMP_EN, + ISCSI_IFACE_PARAM_INITIATOR_NAME, +}; + +enum iscsi_conn_state { + ISCSI_CONN_STATE_FREE, + ISCSI_CONN_STATE_XPT_WAIT, + ISCSI_CONN_STATE_IN_LOGIN, + ISCSI_CONN_STATE_LOGGED_IN, + ISCSI_CONN_STATE_IN_LOGOUT, + ISCSI_CONN_STATE_LOGOUT_REQUESTED, + ISCSI_CONN_STATE_CLEANUP_WAIT, +}; + +/* + * Common error codes + */ +enum iscsi_err { + ISCSI_OK = 0, + + ISCSI_ERR_DATASN = ISCSI_ERR_BASE + 1, + ISCSI_ERR_DATA_OFFSET = ISCSI_ERR_BASE + 2, + ISCSI_ERR_MAX_CMDSN = ISCSI_ERR_BASE + 3, + ISCSI_ERR_EXP_CMDSN = ISCSI_ERR_BASE + 4, + ISCSI_ERR_BAD_OPCODE = ISCSI_ERR_BASE + 5, + ISCSI_ERR_DATALEN = ISCSI_ERR_BASE + 6, + ISCSI_ERR_AHSLEN = ISCSI_ERR_BASE + 7, + ISCSI_ERR_PROTO = ISCSI_ERR_BASE + 8, + ISCSI_ERR_LUN = ISCSI_ERR_BASE + 9, + ISCSI_ERR_BAD_ITT = ISCSI_ERR_BASE + 10, + ISCSI_ERR_CONN_FAILED = ISCSI_ERR_BASE + 11, + ISCSI_ERR_R2TSN = ISCSI_ERR_BASE + 12, + ISCSI_ERR_SESSION_FAILED = ISCSI_ERR_BASE + 13, + ISCSI_ERR_HDR_DGST = ISCSI_ERR_BASE + 14, + ISCSI_ERR_DATA_DGST = ISCSI_ERR_BASE + 15, + ISCSI_ERR_PARAM_NOT_FOUND = ISCSI_ERR_BASE + 16, + ISCSI_ERR_NO_SCSI_CMD = ISCSI_ERR_BASE + 17, + ISCSI_ERR_INVALID_HOST = ISCSI_ERR_BASE + 18, + ISCSI_ERR_XMIT_FAILED = ISCSI_ERR_BASE + 19, + ISCSI_ERR_TCP_CONN_CLOSE = ISCSI_ERR_BASE + 20, + ISCSI_ERR_SCSI_EH_SESSION_RST = ISCSI_ERR_BASE + 21, + ISCSI_ERR_NOP_TIMEDOUT = ISCSI_ERR_BASE + 22, +}; + +/* + * iSCSI Parameters (RFC3720) + */ +enum iscsi_param { + /* passed in using netlink set param */ + ISCSI_PARAM_MAX_RECV_DLENGTH, + ISCSI_PARAM_MAX_XMIT_DLENGTH, + ISCSI_PARAM_HDRDGST_EN, + ISCSI_PARAM_DATADGST_EN, + ISCSI_PARAM_INITIAL_R2T_EN, + ISCSI_PARAM_MAX_R2T, + ISCSI_PARAM_IMM_DATA_EN, + ISCSI_PARAM_FIRST_BURST, + ISCSI_PARAM_MAX_BURST, + ISCSI_PARAM_PDU_INORDER_EN, + ISCSI_PARAM_DATASEQ_INORDER_EN, + ISCSI_PARAM_ERL, + ISCSI_PARAM_IFMARKER_EN, + ISCSI_PARAM_OFMARKER_EN, + ISCSI_PARAM_EXP_STATSN, + ISCSI_PARAM_TARGET_NAME, + ISCSI_PARAM_TPGT, + ISCSI_PARAM_PERSISTENT_ADDRESS, + ISCSI_PARAM_PERSISTENT_PORT, + ISCSI_PARAM_SESS_RECOVERY_TMO, + + /* passed in through bind conn using transport_fd */ + ISCSI_PARAM_CONN_PORT, + ISCSI_PARAM_CONN_ADDRESS, + + ISCSI_PARAM_USERNAME, + ISCSI_PARAM_USERNAME_IN, + ISCSI_PARAM_PASSWORD, + ISCSI_PARAM_PASSWORD_IN, + + ISCSI_PARAM_FAST_ABORT, + ISCSI_PARAM_ABORT_TMO, + ISCSI_PARAM_LU_RESET_TMO, + ISCSI_PARAM_HOST_RESET_TMO, + + ISCSI_PARAM_PING_TMO, + ISCSI_PARAM_RECV_TMO, + + ISCSI_PARAM_IFACE_NAME, + ISCSI_PARAM_ISID, + ISCSI_PARAM_INITIATOR_NAME, + + ISCSI_PARAM_TGT_RESET_TMO, + ISCSI_PARAM_TARGET_ALIAS, + + ISCSI_PARAM_CHAP_IN_IDX, + ISCSI_PARAM_CHAP_OUT_IDX, + + ISCSI_PARAM_BOOT_ROOT, + ISCSI_PARAM_BOOT_NIC, + ISCSI_PARAM_BOOT_TARGET, + + ISCSI_PARAM_AUTO_SND_TGT_DISABLE, + ISCSI_PARAM_DISCOVERY_SESS, + ISCSI_PARAM_PORTAL_TYPE, + ISCSI_PARAM_CHAP_AUTH_EN, + ISCSI_PARAM_DISCOVERY_LOGOUT_EN, + ISCSI_PARAM_BIDI_CHAP_EN, + ISCSI_PARAM_DISCOVERY_AUTH_OPTIONAL, + + ISCSI_PARAM_DEF_TIME2WAIT, + ISCSI_PARAM_DEF_TIME2RETAIN, + ISCSI_PARAM_MAX_SEGMENT_SIZE, + ISCSI_PARAM_STATSN, + ISCSI_PARAM_KEEPALIVE_TMO, + ISCSI_PARAM_LOCAL_PORT, + ISCSI_PARAM_TSID, + ISCSI_PARAM_DEF_TASKMGMT_TMO, + + ISCSI_PARAM_TCP_TIMESTAMP_STAT, + ISCSI_PARAM_TCP_WSF_DISABLE, + ISCSI_PARAM_TCP_NAGLE_DISABLE, + ISCSI_PARAM_TCP_TIMER_SCALE, + ISCSI_PARAM_TCP_TIMESTAMP_EN, + ISCSI_PARAM_TCP_XMIT_WSF, + ISCSI_PARAM_TCP_RECV_WSF, + ISCSI_PARAM_IP_FRAGMENT_DISABLE, + ISCSI_PARAM_IPV4_TOS, + ISCSI_PARAM_IPV6_TC, + ISCSI_PARAM_IPV6_FLOW_LABEL, + ISCSI_PARAM_IS_FW_ASSIGNED_IPV6, + + ISCSI_PARAM_DISCOVERY_PARENT_IDX, + ISCSI_PARAM_DISCOVERY_PARENT_TYPE, + ISCSI_PARAM_LOCAL_IPADDR, + /* must always be last */ + ISCSI_PARAM_MAX, +}; + +/* iSCSI HBA params */ +enum iscsi_host_param { + ISCSI_HOST_PARAM_HWADDRESS, + ISCSI_HOST_PARAM_INITIATOR_NAME, + ISCSI_HOST_PARAM_NETDEV_NAME, + ISCSI_HOST_PARAM_IPADDRESS, + ISCSI_HOST_PARAM_PORT_STATE, + ISCSI_HOST_PARAM_PORT_SPEED, + ISCSI_HOST_PARAM_MAX, +}; + +/* portal type */ +#define PORTAL_TYPE_IPV4 "ipv4" +#define PORTAL_TYPE_IPV6 "ipv6" + +/* iSCSI Flash Target params */ +enum iscsi_flashnode_param { + ISCSI_FLASHNODE_IS_FW_ASSIGNED_IPV6, + ISCSI_FLASHNODE_PORTAL_TYPE, + ISCSI_FLASHNODE_AUTO_SND_TGT_DISABLE, + ISCSI_FLASHNODE_DISCOVERY_SESS, + ISCSI_FLASHNODE_ENTRY_EN, + ISCSI_FLASHNODE_HDR_DGST_EN, + ISCSI_FLASHNODE_DATA_DGST_EN, + ISCSI_FLASHNODE_IMM_DATA_EN, + ISCSI_FLASHNODE_INITIAL_R2T_EN, + ISCSI_FLASHNODE_DATASEQ_INORDER, + ISCSI_FLASHNODE_PDU_INORDER, + ISCSI_FLASHNODE_CHAP_AUTH_EN, + ISCSI_FLASHNODE_SNACK_REQ_EN, + ISCSI_FLASHNODE_DISCOVERY_LOGOUT_EN, + ISCSI_FLASHNODE_BIDI_CHAP_EN, + /* make authentication for discovery sessions optional */ + ISCSI_FLASHNODE_DISCOVERY_AUTH_OPTIONAL, + ISCSI_FLASHNODE_ERL, + ISCSI_FLASHNODE_TCP_TIMESTAMP_STAT, + ISCSI_FLASHNODE_TCP_NAGLE_DISABLE, + ISCSI_FLASHNODE_TCP_WSF_DISABLE, + ISCSI_FLASHNODE_TCP_TIMER_SCALE, + ISCSI_FLASHNODE_TCP_TIMESTAMP_EN, + ISCSI_FLASHNODE_IP_FRAG_DISABLE, + ISCSI_FLASHNODE_MAX_RECV_DLENGTH, + ISCSI_FLASHNODE_MAX_XMIT_DLENGTH, + ISCSI_FLASHNODE_FIRST_BURST, + ISCSI_FLASHNODE_DEF_TIME2WAIT, + ISCSI_FLASHNODE_DEF_TIME2RETAIN, + ISCSI_FLASHNODE_MAX_R2T, + ISCSI_FLASHNODE_KEEPALIVE_TMO, + ISCSI_FLASHNODE_ISID, + ISCSI_FLASHNODE_TSID, + ISCSI_FLASHNODE_PORT, + ISCSI_FLASHNODE_MAX_BURST, + ISCSI_FLASHNODE_DEF_TASKMGMT_TMO, + ISCSI_FLASHNODE_IPADDR, + ISCSI_FLASHNODE_ALIAS, + ISCSI_FLASHNODE_REDIRECT_IPADDR, + ISCSI_FLASHNODE_MAX_SEGMENT_SIZE, + ISCSI_FLASHNODE_LOCAL_PORT, + ISCSI_FLASHNODE_IPV4_TOS, + ISCSI_FLASHNODE_IPV6_TC, + ISCSI_FLASHNODE_IPV6_FLOW_LABEL, + ISCSI_FLASHNODE_NAME, + ISCSI_FLASHNODE_TPGT, + ISCSI_FLASHNODE_LINK_LOCAL_IPV6, + ISCSI_FLASHNODE_DISCOVERY_PARENT_IDX, + ISCSI_FLASHNODE_DISCOVERY_PARENT_TYPE, + ISCSI_FLASHNODE_TCP_XMIT_WSF, + ISCSI_FLASHNODE_TCP_RECV_WSF, + ISCSI_FLASHNODE_CHAP_IN_IDX, + ISCSI_FLASHNODE_CHAP_OUT_IDX, + ISCSI_FLASHNODE_USERNAME, + ISCSI_FLASHNODE_USERNAME_IN, + ISCSI_FLASHNODE_PASSWORD, + ISCSI_FLASHNODE_PASSWORD_IN, + ISCSI_FLASHNODE_STATSN, + ISCSI_FLASHNODE_EXP_STATSN, + ISCSI_FLASHNODE_IS_BOOT_TGT, + + ISCSI_FLASHNODE_MAX, +}; + +struct iscsi_flashnode_param_info { + uint32_t len; /* Actual length of the param */ + uint16_t param; /* iscsi param value */ + uint8_t value[0]; /* length sized value follows */ +} __attribute__((__packed__)); + +enum iscsi_discovery_parent_type { + ISCSI_DISC_PARENT_UNKNOWN = 0x1, + ISCSI_DISC_PARENT_SENDTGT = 0x2, + ISCSI_DISC_PARENT_ISNS = 0x3, +}; + +/* iSCSI port Speed */ +enum iscsi_port_speed { + ISCSI_PORT_SPEED_UNKNOWN = 0x1, + ISCSI_PORT_SPEED_10MBPS = 0x2, + ISCSI_PORT_SPEED_100MBPS = 0x4, + ISCSI_PORT_SPEED_1GBPS = 0x8, + ISCSI_PORT_SPEED_10GBPS = 0x10, + ISCSI_PORT_SPEED_25GBPS = 0x20, + ISCSI_PORT_SPEED_40GBPS = 0x40, +}; + +/* iSCSI port state */ +enum iscsi_port_state { + ISCSI_PORT_STATE_DOWN = 0x1, + ISCSI_PORT_STATE_UP = 0x2, +}; + +/* iSCSI PING status/error code */ +enum iscsi_ping_status_code { + ISCSI_PING_SUCCESS = 0, + ISCSI_PING_FW_DISABLED = 0x1, + ISCSI_PING_IPADDR_INVALID = 0x2, + ISCSI_PING_LINKLOCAL_IPV6_ADDR_INVALID = 0x3, + ISCSI_PING_TIMEOUT = 0x4, + ISCSI_PING_INVALID_DEST_ADDR = 0x5, + ISCSI_PING_OVERSIZE_PACKET = 0x6, + ISCSI_PING_ICMP_ERROR = 0x7, + ISCSI_PING_MAX_REQ_EXCEEDED = 0x8, + ISCSI_PING_NO_ARP_RECEIVED = 0x9, +}; + +#define iscsi_ptr(_handle) ((void*)(unsigned long)_handle) +#define iscsi_handle(_ptr) ((uint64_t)(unsigned long)_ptr) + +/* + * These flags presents iSCSI Data-Path capabilities. + */ +#define CAP_RECOVERY_L0 0x1 +#define CAP_RECOVERY_L1 0x2 +#define CAP_RECOVERY_L2 0x4 +#define CAP_MULTI_R2T 0x8 +#define CAP_HDRDGST 0x10 +#define CAP_DATADGST 0x20 +#define CAP_MULTI_CONN 0x40 +#define CAP_TEXT_NEGO 0x80 +#define CAP_MARKERS 0x100 +#define CAP_FW_DB 0x200 +#define CAP_SENDTARGETS_OFFLOAD 0x400 /* offload discovery process */ +#define CAP_DATA_PATH_OFFLOAD 0x800 /* offload entire IO path */ +#define CAP_DIGEST_OFFLOAD 0x1000 /* offload hdr and data digests */ +#define CAP_PADDING_OFFLOAD 0x2000 /* offload padding insertion, removal, + and verification */ +#define CAP_LOGIN_OFFLOAD 0x4000 /* offload session login */ + +/* + * These flags describes reason of stop_conn() call + */ +#define STOP_CONN_TERM 0x1 +#define STOP_CONN_SUSPEND 0x2 +#define STOP_CONN_RECOVER 0x3 + +#define ISCSI_STATS_CUSTOM_MAX 32 +#define ISCSI_STATS_CUSTOM_DESC_MAX 64 +struct iscsi_stats_custom { + char desc[ISCSI_STATS_CUSTOM_DESC_MAX]; + uint64_t value; +}; + +/* + * struct iscsi_stats - iSCSI Statistics (iSCSI MIB) + * + * Note: this structure contains counters collected on per-connection basis. + */ +struct iscsi_stats { + /* octets */ + uint64_t txdata_octets; + uint64_t rxdata_octets; + + /* xmit pdus */ + uint32_t noptx_pdus; + uint32_t scsicmd_pdus; + uint32_t tmfcmd_pdus; + uint32_t login_pdus; + uint32_t text_pdus; + uint32_t dataout_pdus; + uint32_t logout_pdus; + uint32_t snack_pdus; + + /* recv pdus */ + uint32_t noprx_pdus; + uint32_t scsirsp_pdus; + uint32_t tmfrsp_pdus; + uint32_t textrsp_pdus; + uint32_t datain_pdus; + uint32_t logoutrsp_pdus; + uint32_t r2t_pdus; + uint32_t async_pdus; + uint32_t rjt_pdus; + + /* errors */ + uint32_t digest_err; + uint32_t timeout_err; + + /* + * iSCSI Custom Statistics support, i.e. Transport could + * extend existing MIB statistics with its own specific statistics + * up to ISCSI_STATS_CUSTOM_MAX + */ + uint32_t custom_length; + struct iscsi_stats_custom custom[0] + __attribute__ ((aligned (sizeof(uint64_t)))); +}; + +enum chap_type_e { + CHAP_TYPE_OUT, + CHAP_TYPE_IN, +}; + +enum iscsi_chap_param { + ISCSI_CHAP_PARAM_INDEX, + ISCSI_CHAP_PARAM_CHAP_TYPE, + ISCSI_CHAP_PARAM_USERNAME, + ISCSI_CHAP_PARAM_PASSWORD, + ISCSI_CHAP_PARAM_PASSWORD_LEN +}; + +#define ISCSI_CHAP_AUTH_NAME_MAX_LEN 256 +#define ISCSI_CHAP_AUTH_SECRET_MAX_LEN 256 +struct iscsi_chap_rec { + uint16_t chap_tbl_idx; + enum chap_type_e chap_type; + char username[ISCSI_CHAP_AUTH_NAME_MAX_LEN]; + uint8_t password[ISCSI_CHAP_AUTH_SECRET_MAX_LEN]; + uint8_t password_length; +}; + +#define ISCSI_HOST_STATS_CUSTOM_MAX 32 +#define ISCSI_HOST_STATS_CUSTOM_DESC_MAX 64 +struct iscsi_host_stats_custom { + char desc[ISCSI_HOST_STATS_CUSTOM_DESC_MAX]; + uint64_t value; +}; + +/* struct iscsi_offload_host_stats: Host statistics, + * Include statistics for MAC, IP, TCP & iSCSI. + */ +struct iscsi_offload_host_stats { + /* MAC */ + uint64_t mactx_frames; + uint64_t mactx_bytes; + uint64_t mactx_multicast_frames; + uint64_t mactx_broadcast_frames; + uint64_t mactx_pause_frames; + uint64_t mactx_control_frames; + uint64_t mactx_deferral; + uint64_t mactx_excess_deferral; + uint64_t mactx_late_collision; + uint64_t mactx_abort; + uint64_t mactx_single_collision; + uint64_t mactx_multiple_collision; + uint64_t mactx_collision; + uint64_t mactx_frames_dropped; + uint64_t mactx_jumbo_frames; + uint64_t macrx_frames; + uint64_t macrx_bytes; + uint64_t macrx_unknown_control_frames; + uint64_t macrx_pause_frames; + uint64_t macrx_control_frames; + uint64_t macrx_dribble; + uint64_t macrx_frame_length_error; + uint64_t macrx_jabber; + uint64_t macrx_carrier_sense_error; + uint64_t macrx_frame_discarded; + uint64_t macrx_frames_dropped; + uint64_t mac_crc_error; + uint64_t mac_encoding_error; + uint64_t macrx_length_error_large; + uint64_t macrx_length_error_small; + uint64_t macrx_multicast_frames; + uint64_t macrx_broadcast_frames; + /* IP */ + uint64_t iptx_packets; + uint64_t iptx_bytes; + uint64_t iptx_fragments; + uint64_t iprx_packets; + uint64_t iprx_bytes; + uint64_t iprx_fragments; + uint64_t ip_datagram_reassembly; + uint64_t ip_invalid_address_error; + uint64_t ip_error_packets; + uint64_t ip_fragrx_overlap; + uint64_t ip_fragrx_outoforder; + uint64_t ip_datagram_reassembly_timeout; + uint64_t ipv6tx_packets; + uint64_t ipv6tx_bytes; + uint64_t ipv6tx_fragments; + uint64_t ipv6rx_packets; + uint64_t ipv6rx_bytes; + uint64_t ipv6rx_fragments; + uint64_t ipv6_datagram_reassembly; + uint64_t ipv6_invalid_address_error; + uint64_t ipv6_error_packets; + uint64_t ipv6_fragrx_overlap; + uint64_t ipv6_fragrx_outoforder; + uint64_t ipv6_datagram_reassembly_timeout; + /* TCP */ + uint64_t tcptx_segments; + uint64_t tcptx_bytes; + uint64_t tcprx_segments; + uint64_t tcprx_byte; + uint64_t tcp_duplicate_ack_retx; + uint64_t tcp_retx_timer_expired; + uint64_t tcprx_duplicate_ack; + uint64_t tcprx_pure_ackr; + uint64_t tcptx_delayed_ack; + uint64_t tcptx_pure_ack; + uint64_t tcprx_segment_error; + uint64_t tcprx_segment_outoforder; + uint64_t tcprx_window_probe; + uint64_t tcprx_window_update; + uint64_t tcptx_window_probe_persist; + /* ECC */ + uint64_t ecc_error_correction; + /* iSCSI */ + uint64_t iscsi_pdu_tx; + uint64_t iscsi_data_bytes_tx; + uint64_t iscsi_pdu_rx; + uint64_t iscsi_data_bytes_rx; + uint64_t iscsi_io_completed; + uint64_t iscsi_unexpected_io_rx; + uint64_t iscsi_format_error; + uint64_t iscsi_hdr_digest_error; + uint64_t iscsi_data_digest_error; + uint64_t iscsi_sequence_error; + /* + * iSCSI Custom Host Statistics support, i.e. Transport could + * extend existing host statistics with its own specific statistics + * up to ISCSI_HOST_STATS_CUSTOM_MAX + */ + uint32_t custom_length; + struct iscsi_host_stats_custom custom[0] + __attribute__ ((aligned (sizeof(uint64_t)))); +}; + +#endif diff --git a/include/iscsi_net_util.h b/include/iscsi_net_util.h new file mode 100644 index 0000000..16d48e3 --- /dev/null +++ b/include/iscsi_net_util.h @@ -0,0 +1,14 @@ +#ifndef __ISCSI_NET_UTIL_h__ +#define __ISCSI_NET_UTIL_h__ + +#define ISCSI_HWADDRESS_BUF_SIZE 18 +#define ISCSIUIO_PATH "/sbin/iscsiuio" + +extern int net_get_transport_name_from_netdev(char *netdev, char *transport); +extern int net_get_netdev_from_hwaddress(char *hwaddress, char *netdev); +extern int net_setup_netdev(char *netdev, char *local_ip, char *mask, + char *gateway, char *vlan, char *remote_ip, + int needs_bringup); +extern int net_ifup_netdev(char *netdev); + +#endif diff --git a/include/iscsi_proto.h b/include/iscsi_proto.h new file mode 100644 index 0000000..1d14b89 --- /dev/null +++ b/include/iscsi_proto.h @@ -0,0 +1,642 @@ +/* + * RFC 3720 (iSCSI) protocol data types + * + * Copyright (C) 2005 Dmitry Yusupov + * Copyright (C) 2005 Alex Aizman + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#ifndef ISCSI_PROTO_H +#define ISCSI_PROTO_H + +#ifndef __KERNEL__ +#include +#endif +#include + +#define ISCSI_DRAFT20_VERSION 0x00 + +/* default iSCSI listen port for incoming connections */ +#define ISCSI_LISTEN_PORT 3260 + +/* Padding word length */ +#define ISCSI_PAD_LEN 4 + +/* + * useful common(control and data pathes) macro + */ +#define ntoh24(p) (((p)[0] << 16) | ((p)[1] << 8) | ((p)[2])) +#define hton24(p, v) { \ + p[0] = (((v) >> 16) & 0xFF); \ + p[1] = (((v) >> 8) & 0xFF); \ + p[2] = ((v) & 0xFF); \ +} +#define zero_data(p) {p[0]=0;p[1]=0;p[2]=0;} + +#if !defined(__bitwise) +/* + * If running svn modules we may need to define these. + * This should not go upstream since this is already properly defined there + */ +#ifdef __CHECKER__ +#define __bitwise__ __attribute__((bitwise)) +#else +#define __bitwise__ +#endif +#ifdef __CHECK_ENDIAN__ +#define __bitwise __bitwise__ +#else +#define __bitwise +#endif +#endif + +/* initiator tags; opaque for target */ +typedef uint32_t __bitwise__ itt_t; +/* below makes sense only for initiator that created this tag */ +#define build_itt(itt, age) ((__force itt_t)\ + ((itt) | ((age) << ISCSI_AGE_SHIFT))) +#define get_itt(itt) ((__force uint32_t)(itt_t)(itt) & ISCSI_ITT_MASK) +#define RESERVED_ITT ((__force itt_t)0xffffffff) + +/* + * iSCSI Template Message Header + */ +struct iscsi_hdr { + uint8_t opcode; + uint8_t flags; /* Final bit */ + uint8_t rsvd2[2]; + uint8_t hlength; /* AHSs total length */ + uint8_t dlength[3]; /* Data length */ + uint8_t lun[8]; + itt_t itt; /* Initiator Task Tag, opaque for target */ + __be32 ttt; /* Target Task Tag */ + __be32 statsn; + __be32 exp_statsn; + __be32 max_statsn; + uint8_t other[12]; +}; + +/************************* RFC 3720 Begin *****************************/ + +#define ISCSI_RESERVED_TAG 0xffffffff + +/* Opcode encoding bits */ +#define ISCSI_OP_RETRY 0x80 +#define ISCSI_OP_IMMEDIATE 0x40 +#define ISCSI_OPCODE_MASK 0x3F + +/* Initiator Opcode values */ +#define ISCSI_OP_NOOP_OUT 0x00 +#define ISCSI_OP_SCSI_CMD 0x01 +#define ISCSI_OP_SCSI_TMFUNC 0x02 +#define ISCSI_OP_LOGIN 0x03 +#define ISCSI_OP_TEXT 0x04 +#define ISCSI_OP_SCSI_DATA_OUT 0x05 +#define ISCSI_OP_LOGOUT 0x06 +#define ISCSI_OP_SNACK 0x10 + +#define ISCSI_OP_VENDOR1_CMD 0x1c +#define ISCSI_OP_VENDOR2_CMD 0x1d +#define ISCSI_OP_VENDOR3_CMD 0x1e +#define ISCSI_OP_VENDOR4_CMD 0x1f + +/* Target Opcode values */ +#define ISCSI_OP_NOOP_IN 0x20 +#define ISCSI_OP_SCSI_CMD_RSP 0x21 +#define ISCSI_OP_SCSI_TMFUNC_RSP 0x22 +#define ISCSI_OP_LOGIN_RSP 0x23 +#define ISCSI_OP_TEXT_RSP 0x24 +#define ISCSI_OP_SCSI_DATA_IN 0x25 +#define ISCSI_OP_LOGOUT_RSP 0x26 +#define ISCSI_OP_R2T 0x31 +#define ISCSI_OP_ASYNC_EVENT 0x32 +#define ISCSI_OP_REJECT 0x3f + +struct iscsi_ahs_hdr { + __be16 ahslength; + uint8_t ahstype; + uint8_t ahspec[5]; +}; + +#define ISCSI_AHSTYPE_CDB 1 +#define ISCSI_AHSTYPE_RLENGTH 2 +#define ISCSI_CDB_SIZE 16 + +/* iSCSI PDU Header */ +struct iscsi_cmd { + uint8_t opcode; + uint8_t flags; + __be16 rsvd2; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t lun[8]; + itt_t itt; /* Initiator Task Tag */ + __be32 data_length; + __be32 cmdsn; + __be32 exp_statsn; + uint8_t cdb[ISCSI_CDB_SIZE]; /* SCSI Command Block */ + /* Additional Data (Command Dependent) */ +}; + +/* Command PDU flags */ +#define ISCSI_FLAG_CMD_FINAL 0x80 +#define ISCSI_FLAG_CMD_READ 0x40 +#define ISCSI_FLAG_CMD_WRITE 0x20 +#define ISCSI_FLAG_CMD_ATTR_MASK 0x07 /* 3 bits */ + +/* SCSI Command Attribute values */ +#define ISCSI_ATTR_UNTAGGED 0 +#define ISCSI_ATTR_SIMPLE 1 +#define ISCSI_ATTR_ORDERED 2 +#define ISCSI_ATTR_HEAD_OF_QUEUE 3 +#define ISCSI_ATTR_ACA 4 + +struct iscsi_rlength_ahdr { + __be16 ahslength; + uint8_t ahstype; + uint8_t reserved; + __be32 read_length; +}; + +/* Extended CDB AHS */ +struct iscsi_ecdb_ahdr { + __be16 ahslength; /* CDB length - 15, including reserved byte */ + uint8_t ahstype; + uint8_t reserved; + /* 4-byte aligned extended CDB spillover */ + uint8_t ecdb[260 - ISCSI_CDB_SIZE]; +}; + +/* SCSI Response Header */ +struct iscsi_cmd_rsp { + uint8_t opcode; + uint8_t flags; + uint8_t response; + uint8_t cmd_status; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t rsvd[8]; + itt_t itt; /* Initiator Task Tag */ + __be32 rsvd1; + __be32 statsn; + __be32 exp_cmdsn; + __be32 max_cmdsn; + __be32 exp_datasn; + __be32 bi_residual_count; + __be32 residual_count; + /* Response or Sense Data (optional) */ +}; + +/* Command Response PDU flags */ +#define ISCSI_FLAG_CMD_BIDI_OVERFLOW 0x10 +#define ISCSI_FLAG_CMD_BIDI_UNDERFLOW 0x08 +#define ISCSI_FLAG_CMD_OVERFLOW 0x04 +#define ISCSI_FLAG_CMD_UNDERFLOW 0x02 + +/* iSCSI Status values. Valid if Rsp Selector bit is not set */ +#define ISCSI_STATUS_CMD_COMPLETED 0 +#define ISCSI_STATUS_TARGET_FAILURE 1 +#define ISCSI_STATUS_SUBSYS_FAILURE 2 + +/* Asynchronous Event Header */ +struct iscsi_async { + uint8_t opcode; + uint8_t flags; + uint8_t rsvd2[2]; + uint8_t rsvd3; + uint8_t dlength[3]; + uint8_t lun[8]; + uint8_t rsvd4[8]; + __be32 statsn; + __be32 exp_cmdsn; + __be32 max_cmdsn; + uint8_t async_event; + uint8_t async_vcode; + __be16 param1; + __be16 param2; + __be16 param3; + uint8_t rsvd5[4]; +}; + +/* iSCSI Event Codes */ +#define ISCSI_ASYNC_MSG_SCSI_EVENT 0 +#define ISCSI_ASYNC_MSG_REQUEST_LOGOUT 1 +#define ISCSI_ASYNC_MSG_DROPPING_CONNECTION 2 +#define ISCSI_ASYNC_MSG_DROPPING_ALL_CONNECTIONS 3 +#define ISCSI_ASYNC_MSG_PARAM_NEGOTIATION 4 +#define ISCSI_ASYNC_MSG_VENDOR_SPECIFIC 255 + +/* NOP-Out Message */ +struct iscsi_nopout { + uint8_t opcode; + uint8_t flags; + __be16 rsvd2; + uint8_t rsvd3; + uint8_t dlength[3]; + uint8_t lun[8]; + itt_t itt; /* Initiator Task Tag */ + __be32 ttt; /* Target Transfer Tag */ + __be32 cmdsn; + __be32 exp_statsn; + uint8_t rsvd4[16]; +}; + +/* NOP-In Message */ +struct iscsi_nopin { + uint8_t opcode; + uint8_t flags; + __be16 rsvd2; + uint8_t rsvd3; + uint8_t dlength[3]; + uint8_t lun[8]; + itt_t itt; /* Initiator Task Tag */ + __be32 ttt; /* Target Transfer Tag */ + __be32 statsn; + __be32 exp_cmdsn; + __be32 max_cmdsn; + uint8_t rsvd4[12]; +}; + +/* SCSI Task Management Message Header */ +struct iscsi_tm { + uint8_t opcode; + uint8_t flags; + uint8_t rsvd1[2]; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t lun[8]; + itt_t itt; /* Initiator Task Tag */ + itt_t rtt; /* Reference Task Tag */ + __be32 cmdsn; + __be32 exp_statsn; + __be32 refcmdsn; + __be32 exp_datasn; + uint8_t rsvd2[8]; +}; + +#define ISCSI_FLAG_TM_FUNC_MASK 0x7F + +/* Function values */ +#define ISCSI_TM_FUNC_ABORT_TASK 1 +#define ISCSI_TM_FUNC_ABORT_TASK_SET 2 +#define ISCSI_TM_FUNC_CLEAR_ACA 3 +#define ISCSI_TM_FUNC_CLEAR_TASK_SET 4 +#define ISCSI_TM_FUNC_LOGICAL_UNIT_RESET 5 +#define ISCSI_TM_FUNC_TARGET_WARM_RESET 6 +#define ISCSI_TM_FUNC_TARGET_COLD_RESET 7 +#define ISCSI_TM_FUNC_TASK_REASSIGN 8 + +#define ISCSI_TM_FUNC_VALUE(hdr) ((hdr)->flags & ISCSI_FLAG_TM_FUNC_MASK) + +/* SCSI Task Management Response Header */ +struct iscsi_tm_rsp { + uint8_t opcode; + uint8_t flags; + uint8_t response; /* see Response values below */ + uint8_t qualifier; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t rsvd2[8]; + itt_t itt; /* Initiator Task Tag */ + itt_t rtt; /* Reference Task Tag */ + __be32 statsn; + __be32 exp_cmdsn; + __be32 max_cmdsn; + uint8_t rsvd3[12]; +}; + +/* Response values */ +#define ISCSI_TMF_RSP_COMPLETE 0x00 +#define ISCSI_TMF_RSP_NO_TASK 0x01 +#define ISCSI_TMF_RSP_NO_LUN 0x02 +#define ISCSI_TMF_RSP_TASK_ALLEGIANT 0x03 +#define ISCSI_TMF_RSP_NO_FAILOVER 0x04 +#define ISCSI_TMF_RSP_NOT_SUPPORTED 0x05 +#define ISCSI_TMF_RSP_AUTH_FAILED 0x06 +#define ISCSI_TMF_RSP_REJECTED 0xff + +/* Ready To Transfer Header */ +struct iscsi_r2t_rsp { + uint8_t opcode; + uint8_t flags; + uint8_t rsvd2[2]; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t lun[8]; + itt_t itt; /* Initiator Task Tag */ + __be32 ttt; /* Target Transfer Tag */ + __be32 statsn; + __be32 exp_cmdsn; + __be32 max_cmdsn; + __be32 r2tsn; + __be32 data_offset; + __be32 data_length; +}; + +/* SCSI Data Hdr */ +struct iscsi_data { + uint8_t opcode; + uint8_t flags; + uint8_t rsvd2[2]; + uint8_t rsvd3; + uint8_t dlength[3]; + uint8_t lun[8]; + itt_t itt; + __be32 ttt; + __be32 rsvd4; + __be32 exp_statsn; + __be32 rsvd5; + __be32 datasn; + __be32 offset; + __be32 rsvd6; + /* Payload */ +}; + +/* SCSI Data Response Hdr */ +struct iscsi_data_rsp { + uint8_t opcode; + uint8_t flags; + uint8_t rsvd2; + uint8_t cmd_status; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t lun[8]; + itt_t itt; + __be32 ttt; + __be32 statsn; + __be32 exp_cmdsn; + __be32 max_cmdsn; + __be32 datasn; + __be32 offset; + __be32 residual_count; +}; + +/* Data Response PDU flags */ +#define ISCSI_FLAG_DATA_ACK 0x40 +#define ISCSI_FLAG_DATA_OVERFLOW 0x04 +#define ISCSI_FLAG_DATA_UNDERFLOW 0x02 +#define ISCSI_FLAG_DATA_STATUS 0x01 + +/* Text Header */ +struct iscsi_text { + uint8_t opcode; + uint8_t flags; + uint8_t rsvd2[2]; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t rsvd4[8]; + itt_t itt; + __be32 ttt; + __be32 cmdsn; + __be32 exp_statsn; + uint8_t rsvd5[16]; + /* Text - key=value pairs */ +}; + +#define ISCSI_FLAG_TEXT_CONTINUE 0x40 + +/* Text Response Header */ +struct iscsi_text_rsp { + uint8_t opcode; + uint8_t flags; + uint8_t rsvd2[2]; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t rsvd4[8]; + itt_t itt; + __be32 ttt; + __be32 statsn; + __be32 exp_cmdsn; + __be32 max_cmdsn; + uint8_t rsvd5[12]; + /* Text Response - key:value pairs */ +}; + +/* Login Header */ +struct iscsi_login { + uint8_t opcode; + uint8_t flags; + uint8_t max_version; /* Max. version supported */ + uint8_t min_version; /* Min. version supported */ + uint8_t hlength; + uint8_t dlength[3]; + uint8_t isid[6]; /* Initiator Session ID */ + __be16 tsih; /* Target Session Handle */ + itt_t itt; /* Initiator Task Tag */ + __be16 cid; + __be16 rsvd3; + __be32 cmdsn; + __be32 exp_statsn; + uint8_t rsvd5[16]; +}; + +/* Login PDU flags */ +#define ISCSI_FLAG_LOGIN_TRANSIT 0x80 +#define ISCSI_FLAG_LOGIN_CONTINUE 0x40 +#define ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK 0x0C /* 2 bits */ +#define ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK 0x03 /* 2 bits */ + +#define ISCSI_LOGIN_CURRENT_STAGE(flags) \ + ((flags & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) >> 2) +#define ISCSI_LOGIN_NEXT_STAGE(flags) \ + (flags & ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK) + +/* Login Response Header */ +struct iscsi_login_rsp { + uint8_t opcode; + uint8_t flags; + uint8_t max_version; /* Max. version supported */ + uint8_t active_version; /* Active version */ + uint8_t hlength; + uint8_t dlength[3]; + uint8_t isid[6]; /* Initiator Session ID */ + __be16 tsih; /* Target Session Handle */ + itt_t itt; /* Initiator Task Tag */ + __be32 rsvd3; + __be32 statsn; + __be32 exp_cmdsn; + __be32 max_cmdsn; + uint8_t status_class; /* see Login RSP ststus classes below */ + uint8_t status_detail; /* see Login RSP Status details below */ + uint8_t rsvd4[10]; +}; + +/* Login stage (phase) codes for CSG, NSG */ +#define ISCSI_INITIAL_LOGIN_STAGE -1 +#define ISCSI_SECURITY_NEGOTIATION_STAGE 0 +#define ISCSI_OP_PARMS_NEGOTIATION_STAGE 1 +#define ISCSI_FULL_FEATURE_PHASE 3 + +/* Login Status response classes */ +#define ISCSI_STATUS_CLS_SUCCESS 0x00 +#define ISCSI_STATUS_CLS_REDIRECT 0x01 +#define ISCSI_STATUS_CLS_INITIATOR_ERR 0x02 +#define ISCSI_STATUS_CLS_TARGET_ERR 0x03 + +/* Login Status response detail codes */ +/* Class-0 (Success) */ +#define ISCSI_LOGIN_STATUS_ACCEPT 0x00 + +/* Class-1 (Redirection) */ +#define ISCSI_LOGIN_STATUS_TGT_MOVED_TEMP 0x01 +#define ISCSI_LOGIN_STATUS_TGT_MOVED_PERM 0x02 + +/* Class-2 (Initiator Error) */ +#define ISCSI_LOGIN_STATUS_INIT_ERR 0x00 +#define ISCSI_LOGIN_STATUS_AUTH_FAILED 0x01 +#define ISCSI_LOGIN_STATUS_TGT_FORBIDDEN 0x02 +#define ISCSI_LOGIN_STATUS_TGT_NOT_FOUND 0x03 +#define ISCSI_LOGIN_STATUS_TGT_REMOVED 0x04 +#define ISCSI_LOGIN_STATUS_NO_VERSION 0x05 +#define ISCSI_LOGIN_STATUS_ISID_ERROR 0x06 +#define ISCSI_LOGIN_STATUS_MISSING_FIELDS 0x07 +#define ISCSI_LOGIN_STATUS_CONN_ADD_FAILED 0x08 +#define ISCSI_LOGIN_STATUS_NO_SESSION_TYPE 0x09 +#define ISCSI_LOGIN_STATUS_NO_SESSION 0x0a +#define ISCSI_LOGIN_STATUS_INVALID_REQUEST 0x0b + +/* Class-3 (Target Error) */ +#define ISCSI_LOGIN_STATUS_TARGET_ERROR 0x00 +#define ISCSI_LOGIN_STATUS_SVC_UNAVAILABLE 0x01 +#define ISCSI_LOGIN_STATUS_NO_RESOURCES 0x02 + +/* Logout Header */ +struct iscsi_logout { + uint8_t opcode; + uint8_t flags; + uint8_t rsvd1[2]; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t rsvd2[8]; + itt_t itt; /* Initiator Task Tag */ + __be16 cid; + uint8_t rsvd3[2]; + __be32 cmdsn; + __be32 exp_statsn; + uint8_t rsvd4[16]; +}; + +/* Logout PDU flags */ +#define ISCSI_FLAG_LOGOUT_REASON_MASK 0x7F + +/* logout reason_code values */ + +#define ISCSI_LOGOUT_REASON_CLOSE_SESSION 0 +#define ISCSI_LOGOUT_REASON_CLOSE_CONNECTION 1 +#define ISCSI_LOGOUT_REASON_RECOVERY 2 +#define ISCSI_LOGOUT_REASON_AEN_REQUEST 3 + +/* Logout Response Header */ +struct iscsi_logout_rsp { + uint8_t opcode; + uint8_t flags; + uint8_t response; /* see Logout response values below */ + uint8_t rsvd2; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t rsvd3[8]; + itt_t itt; /* Initiator Task Tag */ + __be32 rsvd4; + __be32 statsn; + __be32 exp_cmdsn; + __be32 max_cmdsn; + __be32 rsvd5; + __be16 t2wait; + __be16 t2retain; + __be32 rsvd6; +}; + +/* logout response status values */ + +#define ISCSI_LOGOUT_SUCCESS 0 +#define ISCSI_LOGOUT_CID_NOT_FOUND 1 +#define ISCSI_LOGOUT_RECOVERY_UNSUPPORTED 2 +#define ISCSI_LOGOUT_CLEANUP_FAILED 3 + +/* SNACK Header */ +struct iscsi_snack { + uint8_t opcode; + uint8_t flags; + uint8_t rsvd2[14]; + itt_t itt; + __be32 begrun; + __be32 runlength; + __be32 exp_statsn; + __be32 rsvd3; + __be32 exp_datasn; + uint8_t rsvd6[8]; +}; + +/* SNACK PDU flags */ +#define ISCSI_FLAG_SNACK_TYPE_MASK 0x0F /* 4 bits */ + +/* Reject Message Header */ +struct iscsi_reject { + uint8_t opcode; + uint8_t flags; + uint8_t reason; + uint8_t rsvd2; + uint8_t hlength; + uint8_t dlength[3]; + uint8_t rsvd3[8]; + __be32 ffffffff; + uint8_t rsvd4[4]; + __be32 statsn; + __be32 exp_cmdsn; + __be32 max_cmdsn; + __be32 datasn; + uint8_t rsvd5[8]; + /* Text - Rejected hdr */ +}; + +/* Reason for Reject */ +#define ISCSI_REASON_CMD_BEFORE_LOGIN 1 +#define ISCSI_REASON_DATA_DIGEST_ERROR 2 +#define ISCSI_REASON_DATA_SNACK_REJECT 3 +#define ISCSI_REASON_PROTOCOL_ERROR 4 +#define ISCSI_REASON_CMD_NOT_SUPPORTED 5 +#define ISCSI_REASON_IMM_CMD_REJECT 6 +#define ISCSI_REASON_TASK_IN_PROGRESS 7 +#define ISCSI_REASON_INVALID_SNACK 8 +#define ISCSI_REASON_BOOKMARK_INVALID 9 +#define ISCSI_REASON_BOOKMARK_NO_RESOURCES 10 +#define ISCSI_REASON_NEGOTIATION_RESET 11 + +/* Max. number of Key=Value pairs in a text message */ +#define MAX_KEY_VALUE_PAIRS 8192 + +/* maximum length for text keys/values */ +#define KEY_MAXLEN 64 +#define VALUE_MAXLEN 255 +#define TARGET_NAME_MAXLEN VALUE_MAXLEN +#define BOOT_NAME_MAXLEN 256 + +#define ISCSI_DEF_MAX_RECV_SEG_LEN 8192 +#define ISCSI_MIN_MAX_RECV_SEG_LEN 512 +#define ISCSI_MAX_MAX_RECV_SEG_LEN 16777215 + +#define ISCSI_DEF_FIRST_BURST_LEN 65536 +#define ISCSI_MIN_FIRST_BURST_LEN 512 +#define ISCSI_MAX_FIRST_BURST_LEN 16777215 + +#define ISCSI_DEF_MAX_BURST_LEN 262144 +#define ISCSI_MIN_MAX_BURST_LEN 512 +#define ISCSI_MAX_MAX_BURST_LEN 16777215 + +#define ISCSI_DEF_TIME2WAIT 2 + +/************************* RFC 3720 End *****************************/ + +#endif /* ISCSI_PROTO_H */ diff --git a/include/list.h b/include/list.h new file mode 100644 index 0000000..94ad99b --- /dev/null +++ b/include/list.h @@ -0,0 +1,111 @@ +#ifndef __LIST_H__ +#define __LIST_H__ + +#include +/* taken from linux kernel */ + +#undef offsetof +#ifdef __compiler_offsetof +#define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER) +#else +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) +#endif + +#define list_container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +static inline void INIT_LIST_HEAD(struct list_head *list) +{ + list->next = list; + list->prev = list; +} + +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +#define list_entry(ptr, type, member) \ + list_container_of(ptr, type, member) + +#define list_first_entry(ptr, type, member) \ + list_entry((ptr)->next, type, member) + +#define list_first_entry_or_null(ptr, type, member) \ + (!list_empty(ptr) ? list_first_entry(ptr, type, member) : NULL) + +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +static inline void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = entry->prev = NULL; +} + +static inline void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/** + * list_move_tail - delete from one list and add as another's tail + * @list: the entry to move + * @head: the head that will follow our entry + */ +static inline void list_move_tail(struct list_head *list, + struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add_tail(list, head); +} + +#endif diff --git a/include/sysdeps.h b/include/sysdeps.h new file mode 100644 index 0000000..2f18e3b --- /dev/null +++ b/include/sysdeps.h @@ -0,0 +1,27 @@ +/* + * wrapping of libc features and kernel interfaces + * + * Copyright (C) 2005-2006 Kay Sievers + * + * 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 version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef _SYSDEPS_H_ +#define _SYSDEPS_H_ + +extern size_t strlcpy(char *dst, const char *src, size_t size); +extern size_t strlcat(char *dst, const char *src, size_t size); + +#endif diff --git a/iscsiuio/.gitignore b/iscsiuio/.gitignore new file mode 100644 index 0000000..a27452a --- /dev/null +++ b/iscsiuio/.gitignore @@ -0,0 +1,25 @@ +# Autogenerated files +stamp-h1 +Makefile.in +Makefile +configure +config.h.in +config.h +config.guess +config.log +config.status +config.sub +COPYING + +.deps +autom4te.cache + +# autotools +aclocal.m4 +compile +depcomp +install-sh +libtool +ltmain.sh +missing + diff --git a/iscsiuio/AUTHORS b/iscsiuio/AUTHORS new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/iscsiuio/AUTHORS diff --git a/iscsiuio/ChangeLog b/iscsiuio/ChangeLog new file mode 100644 index 0000000..a91b4d5 --- /dev/null +++ b/iscsiuio/ChangeLog @@ -0,0 +1,7 @@ +Version 0.4.1 (July 20, 2009) + * Fix from Mike Christie to determine page size from getpagesize() + rather then the constant PAGE_SIZE. PAGE_SIZE is not defined om + ia64 and ppc. + * Update documentation to indicate IPv6 is not supported + * Fix code to catch the message from the CNIC that the network + interface is going down. diff --git a/iscsiuio/INSTALL b/iscsiuio/INSTALL new file mode 100644 index 0000000..c9fd2c0 --- /dev/null +++ b/iscsiuio/INSTALL @@ -0,0 +1,290 @@ +Installation Instructions +************************* + +Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, +2006, 2007, 2008 Free Software Foundation, Inc. + + This file is free documentation; the Free Software Foundation gives +unlimited permission to copy, distribute and modify it. + +Basic Installation +================== + + Briefly, the shell commands `./configure; make; make install' should +configure, build, and install this package. The following +more-detailed instructions are generic; see the `README' file for +instructions specific to this package. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. Caching is +disabled by default to prevent problems with accidental use of stale +cache files. + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You need `configure.ac' if +you want to change it or regenerate `configure' using a newer version +of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. + + Running `configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + + 6. Often, you can also type `make uninstall' to remove the installed + files again. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + With a non-GNU `make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use `make distclean' before +reconfiguring for another architecture. + + On MacOS X 10.5 and later systems, you can create libraries and +executables that work on multiple system types--known as "fat" or +"universal" binaries--by specifying multiple `-arch' options to the +compiler but only a single `-arch' option to the preprocessor. Like +this: + + ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CPP="gcc -E" CXXCPP="g++ -E" + + This is not guaranteed to produce working output in all cases, you +may have to build one architecture at a time and combine the results +using the `lipo' tool if you have problems. + +Installation Names +================== + + By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Particular systems +================== + + On HP-UX, the default C compiler is not ANSI C compatible. If GNU +CC is not installed, it is recommended to use the following options in +order to use an ANSI C compiler: + + ./configure CC="cc -Ae" + +and if that doesn't work, install pre-built binaries of GCC for HP-UX. + + On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot +parse its `' header file. The option `-nodtk' can be used as +a workaround. If GNU CC is not installed, it is therefore recommended +to try + + ./configure CC="cc" + +and if that doesn't work, try + + ./configure CC="cc -nodtk" + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for `CONFIG_SHELL' due to +an Autoconf bug. Until the bug is fixed you can use this workaround: + + CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of all of the options to `configure', and exit. + +`--help=short' +`--help=recursive' + Print a summary of the options unique to this package's + `configure', and exit. The `short' variant lists options used + only in the top level, while the `recursive' variant lists options + also present in any nested packages. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--prefix=DIR' + Use DIR as the installation prefix. *Note Installation Names:: + for more details, including other options available for fine-tuning + the installation locations. + +`--no-create' +`-n' + Run the configure checks, but stop before creating any output + files. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. diff --git a/iscsiuio/Makefile.am b/iscsiuio/Makefile.am new file mode 100644 index 0000000..97f478f --- /dev/null +++ b/iscsiuio/Makefile.am @@ -0,0 +1,33 @@ +SUBDIRS= src + +EXTRA_DIST = build_date + +build_date: + if [ -n "$$SOURCE_DATE_EPOCH" ] ; then \ + echo 'char *build_date = "'`LC_ALL=C.UTF-8 date --date=@$$SOURCE_DATE_EPOCH -u`'";' > build_date.c ; \ + else \ + echo 'char *build_date = "'`date`'";' > build_date.c ; \ + fi + echo 'char *build_date;'> build_date.h + +manprefix = /usr/share +mandir = ${manprefix}/man +logdir = /etc/logrotate.d + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am install-man install-log install-brcm + +install-man: + cat docs/iscsiuio.8 | GZIP=$(GZIP_ENV) gzip -c > iscsiuio.8.gz + $(INSTALL) -d $(DESTDIR)$(mandir)/man8/ + $(INSTALL_DATA) iscsiuio.8.gz $(DESTDIR)$(mandir)/man8/ + +install-log: + $(INSTALL) -d $(DESTDIR)$(logdir)/ + $(INSTALL_DATA) iscsiuiolog $(DESTDIR)$(logdir)/ + +install-brcm: + $(RM) $(DESTDIR)$(sbindir)/brcm_iscsiuio + (cd $(DESTDIR)/$(sbindir); \ + $(RM) brcm_iscsiuio; \ + $(LN_S) iscsiuio brcm_iscsiuio) diff --git a/iscsiuio/NEWS b/iscsiuio/NEWS new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/iscsiuio/NEWS diff --git a/iscsiuio/README b/iscsiuio/README new file mode 100644 index 0000000..53b700c --- /dev/null +++ b/iscsiuio/README @@ -0,0 +1,224 @@ +Iscsiuio Userspace Tool +Version 0.7.8.6 +Jun 27, 2019 +------------------------------------------------------ + +This tool is to be used in conjunction with the QLogic NetXtreme II Linux +driver (Kernel module name: 'bnx2' and 'bnx2x'), QLogic CNIC driver, +and the QLogic iSCSI driver (Kernel module name: 'bnx2i'). +This user space tool is used in conjunction with the following +QLogic Network Controllers: + bnx2: BCM5706, BCM5708, BCM5709 devices + bnx2x: BCM57710, BCM57711, BCM57711E, BCM57712, BCM57712E, + BCM57800, BCM57810, BCM57840 devices + +This utility will provide the ARP and DHCP functionality for the iSCSI offload. +The communication to the driver is done via Userspace I/O (Kernel module name +'uio'). + +There is one component to this application: + +1. 'iscsiuio' - This is the daemon which aids in creating iSCSI offloaded + connections. + +Dependencies: +======================================= + +Linux Kernel Dependencies: +1. QLogic CNIC driver (cnic) +1. QLogic iSCSI offload driver (bnx2i) +2. Userspace I/O driver (uio) + +Directory Structure of this Package: +======================================= + + + | + +-doc (documentation directory: man pages) + | + +-src + | + +- uip - the uIP stack + | + +- unix - iscsiuio source + + + +Compiling / Installing +======================================= + +1. Please untar the tarball. +2. Run the configure script. This will create the Makefiles and proper + header files needed for the build. +3. Run 'make'. This will create the binary, 'iscsiuio' +4. Run 'make install' to place the binaries in their installed location. + (The default location is '/sbin') + +iscsid IFACE Configuration File: +======================================= +The network interface configuration files are driven by the iscsid iface +files. The configuration data is parsed by iscsid and passed to the uIP +stack when the connection is established. + +One can use the following iscsiadm commands to create/set the configuration +using the iface files: + +1. Create the iface file: + + iscsiadm -m iface -I --op=new + +2. Discover the targets associated with the new iface + + iscsiadm -m discovery -t st -p -I + +3. Update the iface file: + + To use a static IPv4 address: + iscsiadm -m iface -I --op=update --name=iface.ipaddress --value= + + To use a DHCP address: + iscsiadm -m iface -I --op=update --name=iface.ipaddress --value=0.0.0.0 + + The following values are required. + + To specify the bnx2i as the transport: + iscsiadm -m iface -I --op=update --name=iface.transport_name --value=bnx2i + + To specify the network interface to offload with: + + a. Specify the physical network interface name + iscsiadm -m iface -I --op=update --name=iface.net_ifacename --value= + + b. Specify the iSCSI MAC address of the iSCSI HBA + iscsiadm -m iface -I --op=update --name=iface.hwaddress --value= + +4. Now all the settings should be set so that one could connect to their + desired iSCSI target. + + iscsiadm -m node -p -T -I --login + +bnx2 Limitations: +======================================= +* RX iSCSI ring: + * default ring size is 3 entries + * default buffer size is 0x400 bytes +* TX iSCSI ring: + * default ring size of 1 entry + * default buffer size is 0x400 bytes + +bnx2x Limitations: +======================================= +* RX iSCSI ring: + * default ring size is 15 entries + * default buffer size is 0x400 bytes +* TX iSCSI ring: + * default ring size of 1 entry + * default buffer size is 0x400 bytes + +Other Limiations: + +Any packets larger then the buffer size will not be sent/received by the +hardware and will be dropped. + +IPv6 support: + +IPv6 NDP (neighbor discovery protocol), DHCPv6 and Static IPv6 are now +supported. The IPv6 address used for the connection will be matched against +the DHCPv6/static IPv6 address, the RA (router advertise) address, and the +assigned link local address. + +VLAN support: + +VLAN support is only supported when using static IP addresses. +Also, currently only 1 VLAN is supported per physical network interface. +Either non-VLAN offloaded traffic is allowed or VLAN offloaded traffic +is allowed. The current implementation does not support both at the +same time. + +Currently there is no explicit VLAN attributes in the iface file. +To configure the VLAN offload, the iface.hwaddress attribute or +physical net_ifacename (without the VLAN identifier) must be used +to specify the HBA device. For the proper CNIC routing, the +corresponding L2 interface which has the associated VLAN interface must +have an IP address on the same subnet. + +The following attributes need to be filled when offloading via the +VLAN interface: + + iface.iscsi_ifacename = + iface.hwaddress = XX:XX:XX:XX:XX:XX + iface.ipaddress = XX.XX.XX.XX + iface.transport_name = bnx2i + +Setting IP address: + +On RHEL5.4, RHEL5.5+, RHEL6.0+, and SLES11SP1 distributions, +discovery login is done over the Linux TCP/IP stack and L2 network +interface. The ethx interface corresponding to the HBA must +therefore be in the same IP subnet in order to reach the iSCSI +target during discovery. However, the HBA's IP address should not +be the same as the L2 ethx's IP address. + +Starting with RHEL6.1 and all other newer distributions, discovery +using SendTargets is done over the HBA interface, so there is no +need for the HBA and L2 network to be on the same subnet. However, +if VLAN is used on the HBA, they still have to be on the same subnet +as described above. + + +Setting Netmask and Gateway addresses: + +With the current limitations of the iface file, there are no entries +to allow the user to enter a netmask or gateway IP address. + +The only way to explicitly configure these options is to use DHCP +addressing. Then the netmask/gateway are set on the DHCP server. +These settings are then sent to uIP via the DHCPOFFERs. + +If the netmask is not defined then the netmask are automatically +generated depending on the destination IP address. + +Debugging: +======================================= + +By default, the iscsiuio daemon does not output any messages to the log file, +'/var/log/iscsiuio.log'. Message logging is only enabled when the daemon is +run under debug mode. + +To run the daemon in debug mode please pass the parameter '-d ' + +where the following debug levels are defined: + +DEBUG 4 - Print all messages +INFO 3 - Print messages needed to follow the uIP code (default) +WARN 2 - Print warning messages +ERROR 1 - Only print critical errors + +A sample banner message: + +INFO [Mon Jun 20 11:23:14 2011]Started iSCSI uio stack: Ver 0.7.0.6 +INFO [Mon Jun 20 11:23:14 2011]Build date: Mon Jun 20 11:22:05 PDT 2011 +INFO [Mon Jun 20 11:23:14 2011]Debug mode enabled + +These messages can be used to help debug any issues. + +When debugging issues like the iscsid, the iscsiuio daemon can be run +in the foreground and the maximum debugging level should be used. + +To place the daemon in foreground mode please pass the parameter '-f' + +Note: The messages to the log file are not flushed unless debugging is enabled. + +Note: If the daemon iscsiuio is running, one will not be able to + trample over the existing binary. One might see the following message: + + 'cannot create regular file `/sbin/iscsiuio': Text file busy' + + The solve this, please stop the iscsid service and then install. + +Warning: If full debug is enabled, this may quickly fill the partition +containing the iscsiuio logs. This is because full debugging will log +packet activity which on a busy network will quickly fill the logs. + +Note: If the bnx2i and cnic drivers are unloaded, then iscsiuio will also +need to be restarted so that it can determine the iscsid version. diff --git a/iscsiuio/RELEASE.TXT b/iscsiuio/RELEASE.TXT new file mode 100644 index 0000000..28e681b --- /dev/null +++ b/iscsiuio/RELEASE.TXT @@ -0,0 +1,2100 @@ + Release Notes + QLogic uIP Linux Driver + Version 0.7.8.6 + 06/27/2019 + + QLogic Corporation + 26650 Aliso Viejo Pkwy, + Aliso Viejo, CA 92656 + + Copyright (c) 2004 - 2013 Broadcom Corporation + Copyright (c) 2014, QLogic Corporation + All rights reserved + +uIP v0.7.8.6 (Jun 27, 2019) +======================================================= + Fixes: + ------- + 1. Problem: OS fails to boot after one path is + disconnected from iSCSI MPIO config. + Change: In the event of DHCP failure, killing of enable_nic_thread did + not process any iscsid requests leading to error, + iscsistart: Could not broadcast to uIP after 5 tries + and login failure of active path. + Added fix to not kill enable_nic_thread and allow further + processing of iscsid requests and performing login + to next active path. + Impact: All + +uIP v0.7.8.5 (Nov 20, 2018) +======================================================= + Fixes: + ------- + 1. Problem: CQ102578: observing ISCSI initiator IP ping drop + Change: 1. Do not flush tx queue on each uio interrupt + 2. Use UIO BD index instead on buffer index. + 3. Set buf_size in case of ICMP and ARP packet + Impact: QL41xxx adapters + + 2. Problem: CQ103034 - Unable to boot iSCSI BFS in IPv6 DHCP config + Change: Limit retries of performing dhcpv6 before declaring dhcp failure + Impact: All + + 3. Problem: CQ102438: I/O fails to resume on multipath LUN during port toggle. + Change: lib/cnic, lib/qedi, Release xmit_mutex in error code path and + during clear tx queue. + Impact: QL84xx adapters + + 4. Problem: Netlink buffer corruption when more than one host + try to xmit packet at the same time + Change: Add inter-host mutex while doing xmit + Impact: All + +uIP v0.7.8.4 (Feb 22, 2018) +======================================================= + Fixes: + ------- + 1. Problem: CQ95605: iSCSI BFS in DHCP config intermittently fails to boot + into the OS when source and destination addresses are in + different networks. + Change: Allow ARP for non-matching source and destination addresses. + For source and destination IP addresses in different networks, + continue with the ARP retries and further login process + instead of assuming abrupt failure. iSCSI offload adapters + may not rely on netmask information for successful iSCSI + target login. + Impact: All + +uIP v0.7.8.3 (May 18, 2017) +======================================================= + Fixes: + ------- + 1. Problem: CQ93985: iscsiuio seg faults if discovery done to not + reachable target + Change: Serialize xmit_mutex lock to prevent iscsiuio seg fault. + Impact: All + + 2. Problem: CQ91497 - Initiator fails to acquire IPv6 DHCP address + from the DHCP server + Change: Initialize the transaction-id within the dhcpv6 packet with + correct byte order, to fix the trans-id mismatch error. + Impact: All + + 3. Problem: Missing qedi ping transport hook + Change: Add qedi ping transport hook + Impact: 10/25/40/50GGbE Controller (iSCSI) + +uIP v0.7.8.3 (Sept 28, 2016) +======================================================= + Enhancements + ------------ + 1. Change: Add support for the new qedi transport + Impact: 10/25/40/50GGbE Controller (iSCSI) + +uIP v0.7.8.2 (Dec 10, 2013) +======================================================= + Fixes + ----- + 1. Problem: Cont00072053 - Some hardware iSCSI paths fail during test + Cause: The test exercised a corner case where the ARP cache flush + mechanism didn't work properly + Change: Fixed the ARP cache flush mechanism + Impact: All + + Enhancements + ------------ + 1. Change: Added a new tx doorbell field in the uio path to work with + the new bnx2x/cnic drivers that supports VF_RSS + Impact: 10G only + + 2. Change: Fixed the iface.subnet_mask decoding for IPv6 + Impact: IPv6 + + +uIP v0.7.8.1b (May 01, 2013) +======================================================= + Enhancements + ------------ + 1. Change: Performance optimization by caching the page size + Impact: All + + 2. Change: Fixed a bug in the tx completion interrupt handler + Impact: 10G only + + +uIP v0.7.6.1g (Jan 14, 2013) +======================================================= + Fixes + ----- + 1. Problem: Cont00067316 - IPv6 address prefix length < 32 + bits fails to connect + Cause: CIDR notation has an order bug in the IPv6 section + whenever the prefix length specified is < 32 + Change: Fixed the network order bug + Impact: IPv6 only + + +uIP v0.7.6.1f (Nov 14, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00065768 - RHEL5.X iscsiuio segfault possible + if there is a specific 1024 byte size broadcast + packet + Cause: This is another corner case where the packet size + is also exactly 1024 bytes + padding that exceeded + the DMA rx buffer. The previous fix was not + sufficient + Change: Ensure that the packet size + padding do not + exceed this limit. + Impact: 10G only. 1G already has the guard against it. + + +uIP v0.7.6.1e (Nov 07, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00066397 - Unable to connect to iSCSI target + with NPAR enabled on 57840 + Cause: The PCI device ID for 57840_MF has been changed from + 0x16ab to 0x16a4 + Change: Updated the PCI id table to match exactly what the + bnx2x 1.76 indicates + Impact: 57840 MF + + +uIP v0.7.6.1d (Oct 31, 2012) +======================================================= + Enhancements + ------------ + 1. Change: Added support for open-iscsi-2.0.873 + Impact: All + + +uIP v0.7.6.1c (Oct 15, 2012) +======================================================= + Enhancements + ------------ + 1. Change: Added support for 10G 57840 4x10 and 2x20 + Impact: 10G 57840 + + +uIP v0.7.6.1b (Oct 09, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00065690 - Vconfig method of connecting over + tagged vlan with IPv6 failed + Cause: The new net param support changes has prevented + the old vconfig method from execising the IPv6 + acquisition engine properly + Change: Ensure that this old vconfig method to run the IPv6 + acquisition engine properly and to its entirety + Impact: IPv6 + VLAN using the network VLAN configuration + method + + 2. Problem: Cont00065768 - RHEL5.X iscsiuio segfault possible + if there is a specific 1024 byte size broadcast + packet + Cause: This is a corner case where the packet size is + exactly 1024 bytes + padding that exceeded the + DMA rx buffer. This has been there since day 1. + Change: Ensure that the packet size + padding do not + exceed this limit. + Impact: 10G only. 1G already has the guard against it. + + + Enhancements + ------------ + 1. Change: Source optimization - backported source code fixes + as reported from the upstream submission patch + Impact: ALL + + +uIP v0.7.4.2k (Aug 10, 2012) +======================================================= + Enhancements + ------------ + 1. Change: Enable HP SD mode + Impact: 577XX/578XX + + +uIP v0.7.4.2j (Jul 18, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00064665 - Linux iSCSI connects via gateway address + on the wrong subnet + Cause: The gateway address used was not checked against the + subnet mask specified before the ARP requests. Since + this behavior deters from how L2 operates, therefore, + a change was made to correct this. + Change: Added check of the gateway specified against the subnet + specified. + Impact: Static IPv4 operation + + 2. Problem: Cont00064722 - Linux iSCSI unable to force IPv6 LL + override (advanced iface parameters) + Cause: The override LL address was not being populated to the + IPv6 address database correctly + Change: Added this correctly to the IPv6 initialization + Impact: Static/DHCP IPv6 LL address override only + + +uIP v0.7.4.2i (Jul 11, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00064604 - Fails to connect to routed IPv6 target + via RA + Cause: The default router IPv6 address was not being retrieved + correctly. + Change: Fixed the default router IPv6 address read + Impact: All + + +uIP v0.7.4.2h (Jun 15, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00063863 - can't boot into offload image + when VLAN is enabled + Cause: During the iSCSI login exchange, certain iSCSI targets + will send an ARP request even though the TCP connection + has been made. The bug was in this ARP reply where + the local MAC was corrupted when VLAN is enabled. + Change: Fixed the ARP reply packet + Impact: All + + +uIP v0.7.4.2g (Jun 08, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00063816 - The initiator is not able to connect + to the iSCSI targets over VLAN + Cause: The process packet routine did not consider the PCP + of the VLAN tag to be non-zero. This created a + mismatch when this VLAN tag was compared against the + nic_iface->vlan_id which doesn't include the PCP. + Change: Added the consideration of non-zero PCP + Impact: All + + +uIP v0.7.4.2f (Jun 04, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00063626 - Static IPv6 does not connect when + the prefix len is not set explicitly + Cause: The IPv6 prefix length was not set correctly + for Static IPv6 operation when CIDR notation is + not specified + Change: Fixed the default prefix length + Impact: Static IPv6 + + 2. Problem: Cont00063651 - Cannot connect to iSCSI targets + HP PTM/SF + Cause: Switch-Dependent mode + invalid Outer VLAN was + not supported + Change: Allow SD+invalid OV to fallback to SF operation mode + Impact: 5771X/578XX + + +uIP v0.7.4.2e (May 30, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00063443 - Compilation error on SLES11sp1 + Cause: The iface_num field was not defined + Change: Fixed all references to iface_num + Impact: SLES11sp1 + + 2. Problem: Cont00063518 - HBA fails to connect across router + using iface.gateway address + Cause: The gateway override code did not populate the + address into the lower level engine + Change: Fixed the gateway override code + Impact: IPv4 Static IP operation + + 3. Problem: Cont00063567 - IPv6 LL and RA override does not work + Cause: The IPv6 LL/RA override addresses were overwritten + by the NDP engine + Change: Fixed the LL/RA override code + Impact: IPv6 operation + + Enhancements + ------------ + 1. Added support for jumbo MTU (independent from the L2 MTU) + + +uIP v0.7.4.2d (May 21, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00063421 - Static IPv6 cannot connect via RA/LL + Cause: The router advertise and the linklocal address + were corrupted due to the override capabilities + added for the newer open-iscsi util + Change: Fixed the address override code + Impact: Static IPv6 + + Enhancements + ------------ + 1. Allow VLAN tag = 1 (router management) to connect offload + + +uIP v0.7.4.2c (May 09, 2012) +======================================================= + Fixes + ----- + 1. Problem: RHEL BZ 734010/804580 - issues found by the Coverity + scan + Cause: 10 code issues were flagged for revision + Change: Fixed all area of concern + Impact: All + + 2. Problem: Cont00063177 - IPv4 DHCP with VLAN specification in + iface file gets wrong address + Cause: The DHCPv4 handler was not discriminating the VLAN tag + associated with the DHCP offers from multiple DHCP + servers + Change: Changed the DHCPv4 handler to drop DHCP offer packets + that doesn't match the VLAN tag of the intended DHCP + discovery packet + Impact: DHCPv4 operation + + +uIP v0.7.4.2b (May 01, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00062993 - IPv6 DHCP with VLAN specification in + iface file gets wrong address + Cause: The DHCPv6 request was using the same DUID as always + so the non-VLAN DHCP server responded to our broadcast + instead + Change: Changed the DHCPv6 request DUID to link address + time + instead of link address alone + Impact: DHCPv6 operation + + +uIP v0.7.4.1j (Apr 24, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00062805 - Cannot login to iSCSI targets on RHEL6.3 + Cause: The problem was caused by a change made to the iface_rec + structure in the RHEL6.3 inbox open-iscsi util + Change: The new changes is now incorporated + Impact: All + + +uIP v0.7.4.1i (Apr 16, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00062660 - Unable to login with VLAN iscsiuio + on RHEL6.2 + Cause: The open-iscsi util in RHEL6.2 has a bug which + does not pass the correct iface_num to iscsiuio + Change: Added workaround to fall back to do the legacy + VLAN support if iface_num and vlan_id = 0 + Impact: RHEL6.2 + + +uIP v0.7.4.1h (Apr 13, 2012) +======================================================= + Enhancements + ------------ + 1. Added support for the new iface_num field in the iscsi_uevent + path + + 2. Fixed bug in the nic_iface search engine based on iface_num + + +uIP v0.7.4.1g (Mar 22, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00061869 - Unable to setup an offload iSCSI + connection with FLR/NPAR under ESX5.0:PDA + Cause: The physical function ID was previously extracted + from the sysfs of the VM which might not be consistent + to the actual physical setup due to the function + remapping in the hypervisor + Change: Read the physical function ID directly from the BAR0 + ME register + Impact: All + + 2. Problem: Cont00062170 - IPv6 login/logout stress fails + Cause: The packet interrupt was lost after running the test + for a much longer period of time. A bug in the + packet processing routine was found to exit prematurely + Change: Fixed the packet processing routine to process all + packets before exiting + Impact: All + + +uIP v0.7.4.1f (Mar 19, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00062170 - IPv6 login/logout stress fails + Cause: The packet buffer routine for IPv6 did not take + network order <-> host order into consideration + Change: Added a htons call to compensate for the ntohs pair + Impact: All + + +uIP v0.7.4.1e (Mar 08, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00061978 - Load/unload stress test fails + Cause: The bnx2x open request was failing due to the module + request procedure. However, the open failure was + not being handled correctly. + Change: Fixed the device open error handling + Impact: 5771X/578XX + + +uIP v0.7.4.1d (Mar 02, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00061708 - Unable to log into target after running + driver load/unload + Cause: A bug was introduced in the previous bug fix (CQ61459) + where a pthread_cond_broadcast call was erroneously + enabled + Change: Restored this back + Impact: All + + +uIP v0.7.4.1c (Feb 16, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00061529 - Unable to connect to target after an + initial failed login attempt until iscsi service is + restarted + Cause: Upon a failed DHCPv4 acquisition due to the wrong VLAN + tag in the initial iface setup, any iscsid connect request + from the same NIC will get dropped due to a bug. + Change: Fixed the bug which prevented new iscsid connect requests + from getting honored + Impact: All + + Enhancements + ------------ + 1. Updated README + + +uIP v0.7.4.1b (Feb 08, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00061513 - Unable to connect to target over VLAN + interface + Cause: The VLAN id was not properly passed back to the CNIC + driver for the offload request + Change: Fixed the VLAN id being passed back to the CNIC driver + Impact: All + + +uIP v0.7.4.1a (Feb 01, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00049383 - No mechanism in iface file to support + gateway/routing + Change: Added support for the additional network parameters + as passed from the newer iscsi-util. + These parameters include: + IPv4: subnet_mask, gateway + IPv6: ipv6_linklocal, ipv6_router, + ipv6_autocfg, linklocal_autocfg, router_autocfg + VLAN: vlan_id, vlan_priority, vlan_state + Other: mtu, port + Impact: All + + 2. Problem: Cont00060806 - Unable to connect target using DHCP over + tagged VLAN + Change: DHCP+VLAN is a new feature enhancement that was added + alongside all other new iface parameters. + Impact: All + + + Enhancements + ------------ + 1. Lock iscsid's connect request with path_req so connect requests + with DHCP/Static will no longer override each other + + 2. Fixed the if_down handler from global to nic specific + + 3. Fixed various synchronization issues + + +uIP v0.7.2.1e (Jan 05, 2012) +======================================================= + Fixes + ----- + 1. Problem: Cont00060734 - ifupdown-mtu change stress with active + session causes iscsiuio to fail + Change: Fixed a race condition between the nic enable thread + and when DHCP fails + Impact: All + + +uIP v0.7.2.1d (Dec 28, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00060368 - segfault observed after failing both + mpio paths + Change: Various memory leaks were identified and resolved in + the nic cleanup path + Impact: All + + +uIP v0.7.2.1c (Dec 16, 2011) +======================================================= + Enhancements + ------------ + 1. Change: Disable HP SD mode + + +uIP v0.7.2.1b (Dec 14, 2011) +======================================================= + Enhancements + ------------ + 1. Change: Default iscsiuio logging to off. Use the '-d' + option to enable + + +uIP v0.7.0.14g (Oct 25, 2011) +======================================================= + Enhancements + ------------ + 1. Change: Fixed the compilation under RHEL6.2 + 2. Change: Added oom_adjust call to prevent OOM Killer from killing + iscsiuio when memory is low + 3. Change: Added mlockall setting to prevent page swap + + +uIP v0.7.0.14f (Oct 20, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00058994 - DOS vulnerability in uip during UDP flood + Cause: The warning messages from the UDP handler was logging + at a rate faster than the log file logrotate rate + Therefore, the system's OOM eventually got kicked in to + start terminating running processes which includes iscsiuio + Change: Moved several UDP warning messages from the default log + level to the debug log level + Impact: All (minor) + + 2. Problem: Cont00059288 - Show segfault w/ SLES11 SP1 Xen kernel + Cause: The bnx2x chip_id was not read correctly from the PCIe BAR1 + under the Xen kernel. The error was in the mmap area. + Change: Corrected the mmapping of the PCI MMIO space. + Impact: Xen kernels + + Enhancements + ------------ + 1. Change: Changed the log file open error to a warning and let + the daemon progress. This was only observed under iSCSI boot + + +uIP v0.7.0.14e (Sep 19, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00058678 - Can not iboot target from ipv6 path + using VLAN + Cause: A bug was found in the path request path where the vlan + iface's protocol family was not used correctly in the + iface search + Change: This has been corrected + + +uIP v0.7.0.14d (Sep 16, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00058602 - Can't iboot using IPv6 offload path + Cause: The bug was exposed by a fix in 0.7.0.14c where the + IPv6 router solicitation timeout exceeded the nic + enable thread timeout. + Change: The IPv6 router solicitation timeout has been adjusted + + +uIP v0.7.0.14c (Sep 01, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00058256 - Sessions fail after loginstress to via + simultaneous ipv4 and ipv6 dhcp + Cause: Switching between DHCPv4/v6 coupled with VLAN exposed + a drawback in our nic_iface architecture design where + VLAN is not specified by iscsid. + Change: The code was optimized and improved the performance when + switching between DHCPv4/v6+VLAN. However, the ultimate + fix is to make use of the net config parameters introduced + in the newer open-iscsi util which will identify the + specific VLAN nic_iface to use. + + Enhancements + ------------ + 1. Change: Added support for bnx2x-1.71.00 + + +uIP v0.7.0.14b (Aug 23, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00057840 - RHEL6.2 inbox: Unable to connect to + targets with 5709 + Cause: For cases when the bnx2/bnx2x driver gets removed, the + uio database that was built by cnic would have the device + ->net reference removed. This has caused an unnecessary + timeout of 5s for each stale uio entry in the database. + Change: Adjusted the routine which seeks the device->net entry + to include more logic instead of hard waiting for 5s. + + Enhancements + ------------ + 1. Change: Added support for RHEL6.2 for out-of-box release + 2. Change: Updated the man page with -h and -p info + 3. Change: Updated the -h info + + +uIP v0.7.0.13 (Aug 10, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00057768 - iscsiuio logrotate causes daemon failure + Cause: The logrotate script will send a SIGUSR1 signal to notify + the iscsiuio daemon of such action. However, the daemon + wasn't programmed to catch this signal. + Change: Restored the catching of this signal + + +uIP v0.7.0.12 (Aug 04, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00050634 - brcm_iscsiuio Tainted: running IoZone, + Iometer and receiving a UDP flood on 3260 + Cause: Upon iscsiuio termination, because of the UDP flood, + the nic thread will be busy servicing those UDP packets + while the signal handling thread will free up all nic + resources. The two threads were not in sync. + Change: Added a nic_remove_all routine to destroy all nic threads + before the nic resources get freed. + + Enhancements + ------------ + 1. Change: Fixed all warnings as reported by RHELS' Coverity testing. + + +uIP v0.7.0.11 (Aug 02, 2011) +======================================================= + Fixes + ----- + 1. Problem: Erroneous VLAN tag was being passed by iscsid for connect + request + Cause: The iscsid's iface_rec_t ipc message does not contain this + vlan field. This field was added in uIP for future vlan + support. Since the buffer allocated to receive such message + in uIP didn't get initialized, therefore, garbled up VLAN + tag was getting used. + Change: Added the initialization of this buffer. + + +uIP v0.7.0.10 (Jul 26, 2011) +======================================================= + Fixes + ----- + 1. Problem: Can't offload when switching from Static to DHCP then back to + Static IPv4 when connecting through a VLAN interface + Cause: The VLAN processing code did not reinstall the IP address + from the default nic_iface to the associated VLAN nic_iface. + This was only done on the very first time when the VLAN + interface was created and not on subsequent instances. + Change: Added code to mirror the default nic_iface IP/netmask/ip_config + on the VLAN nic_iface on every new connection request. + + +uIP v0.7.0.9 (Jul 19, 2011) +======================================================= + Fixes + ----- + 1. Problem: Can't offload to 57810 NPAR NIC + Cause: The MF/VF variant of the PCI IDs were not supported previously + Change: Added support for the MF/VF variants for 57800/57810/57840 + + +uIP v0.7.0.8 (Jun 30, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00056522 - Unable to connect to iSCSI target using + netxtreme2 package 7.0.9 + Cause: The iSCSI L2 ring's CID has changed from 17 to 49 + Change: The code now gets L2 iSCSI ring CID from the l2_buf directly. + This will work with any version of the cnic driver because + the location is a zero before this change. + + +uIP v0.7.0.7 (Jun 23, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00056460 - iSCSI Offload boot RHEL5u5 x64 dropped tagged + packets with iSCSI Offload Boot with untagged + Cause: The ICMP echo replies to the target was corrupted in both + 1g and 10g mode + Change: The code will now handle both VLAN stripped and no VLAN stripped + incoming packets correctly. Also modified the transmit routine + to strip out any inline VLAN tag before setting up the hw to + insert VLAN tag. + + +uIP v0.7.0.6 (Jun 21, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00056231 - DHCPv4 not working with iSCSI HBA w/ + linux-nx2 v7.0.7 + Cause: The 10g L2 FW HSI has been modified for PCIe performance + enhancement in the 7.0.7 package (FW 1.70.20) which uIP + has not adapted to. + Change: The eth_rx_cqe size has been increased from 32B to 64B. + + Enhancements + ------------ + 1. Change: The utility name has changed from brcm_iscsiuio to iscsiuio + as preparation for upstream submission. + 2. Change: Updated README + + +uIP v0.7.0.5 (Jun 02, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00055915 - iSCSI does not connect on 57800 in 4-port mode + Cause: The 4-port mode was not being determined correctly + Change: Fixed the PORT4MODE register offset and the QZONE_ID macros + + +uIP v0.7.0.4 (May 24, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00055832 - linux iscsiboot can not login to target using + offload path (57800) + Cause: The device ID comparison routine did not take care of the case + when one device ID is bitwise superset of another. + Change: Fixed the device ID comparison routine. + + +uIP v0.7.0.3 (May. 19, 2011) +======================================================= + Enhancements + ------------ + 1. Change: Updated all fixes to match the released uIP 0.6.4.17 + + 2. Change: Modified source and Copyright info as preparation for upstream + submission + + +uIP v0.7.0.2 (May. 03, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00048972 - brcm-iscsi.log has no max size and would grow + to consume all free space on hard disk + Cause: There was no mechanism to rotate the log + Change: Added logrotate entry and SIGUSR1 signal handling for log rotate + action + + 2. Problem: Cont00054996 - Multi-session, multi-protocol mtu stress + does not recover all sessions + Cause: A segfault was observed during the load/unload module. The + problem was caused by an illegal dereference of a pointer + when IPv6 couldn't find the longest match address from + the ARP (Neighbor) table. + Change: Fixed the dereferencing error + + 3. Problem: Cont00054900 - Linux uIP - Please add ability to connect + to routed target with static iface IPv6 + Cause: Static IPv6 never runs the IPv6 NDP router sol/adv engine. + Change: IPv6 NDP router sol/adv has now been added to static IPv6 + operation. + + 4. Problem: Cont00054996 - Multi-session, multi-protocol mtu stress + does not recover all sessions + Cause: Segfaults were observed caused by the accessing of the IPv6 + NDP structure while the nic is undergoing a reset either + due to a DHCPv4 request from iscsid or the handling of + if_down due to the NL handler from CNIC. + Change: The fix involves the following: + - Fixed the handling of staggered IPv4/v6 DHCP/static requests + - Fixed memory leak due to reallocation of IPv4 and IPv6 + DHCP structs + - Fixed the pthread join stuck problem in the handling + of the if_down NL message + + 5. Problem: Cont00054810 - Linux NMI - bnx2x_init_hw_common:PXP2 CFG + failed running iSCSI MTU stress test + Cause: This only happens in DHCPv4 mode. The problem was caused + by contention between the elongated window of performing + DHCP in the enable_nic thread while receiving the asynchronous + if_down NL message (from the MTU change event) from the + CNIC NL thread. The problem occurs when the enable_nic + thread tries to call bnx2x_open while the other thread + calls the bnx2x_close routine. + Change: Fixed mutex lock bugs for the enable_nic thread. Also + extended the nic_disable timeout to 10s to compensate for + the DHCP operation. + + 6. Problem: Cont00054818 - RH6.0 - Unable to logout of iSCSI session + after running PQA baseline scripts + Cause: This was caused by the call to cancel the enable_nic + thread when disabling the nic but failed to unlock the + nic mutex that the enable_nic thread held. + Change: Wake up the enable_nic thread and wait for it to complete + instead of canceling it in the nic_disable path. + + 7. Problem: Cont00054725 - Previous static HBA IP will be used after + a new static HBA IP has been created + Cause: There was an assumption in the code where if the same + nic_iface structure was found based on the nic/vlan pair, + the specified IP address would not be used. Instead, it + will continue to use the previous defined IP address. + Change: The previous IP address will now be compared against the + the specified IP address before finishing the parce + iface request from iscsid. If different, the current + nic will be disabled and then re-enabled with the newly + specified IP address. + + 8. Problem: Cont00054571 - Unable to connect to routed ipv6 target + with RA address and iface DHCPv6 + Cause: The default router address was not being employed for + the IPv6 neighbor negotiation. Additionally, the return + address of our neighbor advertisement was incorrect as + it should use the best matched src address instead. + Change: Fixed both the IPv6 neighbor solicitation and advertisement + transmission and handling. + + 9. Problem: Cont00054510 - fails to login to 32 session with blanket + login IPv6 + Cause: A bug was introduced in uIP 0.6.4.6 where the NIC_RUNNING + flag might not be set when entering the main loop under + certain situations depending on the nic bring up. + Change: A new NIC_STARTED_RUNNING flag is now defined to fix CQ53511. + + 10. Problem: Cont00053807 - RA and link local are unable to connect if DHCPv6 + fails + Cause: The host link local address was not being searched as one of + the host address to be replied to CNIC for the connect request. + Change: The path reply now includes the search of host link local + address as well. + + 11. Problem: Cont00054236 - iSCSI service must be restarted before an IPv6 + connection can be made to the Equalogic target + Cause: The problem was intermittent as it depends on which IPv6 address + the target was redirecting to. Since uIP was only extracting + the target's IPv6 address + MAC from the target's neighbor + advertisement packet itself and not from the ICMPv6 option, so + the wrong or no MAC address will get send down to CNIC for the + connection establishment; hence the no connect. + Change: Added the updating of the neighbor discovery table to also use + the Target IPv6 address + MAC specified in the incoming neighbor + advertisement's ICMPv6 option field. + + 12. Problem: Cont00053255 - bnx2x panic dump logging into multiple + discovered IPv6 nodes (Equalogic IPv6 target) + Cause: The bnx2x panic was fixed in the 10g fw 6.4.29. + A IPv6 connectivity issue was then found and led to different + kernel/uIP crashes. This was caused by the same IPv6 + connectivity problem mentioned above. + Change: Same as above + + 13. Problem: Cont00053728 - Sessions never recover after doing initiator-side + cable pull test with IPv6 traffic against Equalogic targets + Cause: It was discovered that the Equalogic would send out periodic + neighbor solicitation to maintain the connection to the + initiator. Since uIP was responding with the assigned IPv6 + link local address in the neighbor advertisement + unconditionally, the target was observed to stop transmitting on + the connection specified. + Change: The neighbor advertisement generated will now use the dst IPv6 + address from the input neighbor solicitation packet instead of + the assigned IPv6 link local address for both the packet and the + ICMPv6 source IPv6 address. + + 14. Problem: Compile error under 32-bit OS + Cause: A bug was introduced in the previous release 0.6.4.6 which + caused a compilation error in 32-bit OS (64-bit compiles + fine) + Change: Fixed the bug + + 15. Problem: Cont00053807 - RA and Link local are unable to connect if dhcpv6 + fails + Cause: There was a bug in the nl reply where the RA address will never + be sent back to CNIC for the connection request + Change: The best matched address to the dst will now be sent back to + CNIC in the path rsp. + + Enhancements + ------------ + 1. Change: Updated README to remove the 57713/E references + + 2. Change: Allow the ICMP option field in the IPv6 Neighbor Advertisement + response to be included without discrimination. This fixes + an issue connecting against the EQL via RA for DHCPv6. + + 3. Change: Updated README for the IPv6 operation, VLAN, and discovery. + + +uIP v0.7.0.1 (Mar. 29, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00053511 - bnx2x panic dump during ifup/down stress with + iSCSI traffic + Cause: The panic dump was resolved by the driver's rq dbell size fix. + After that, uIP crashed due to the asynchronous if_down event + that took the chip resources away while the nic thread is still + continuing to try to send DHCP request. + Change: Added synchronization between the two threads so proper clean up + of the threads can occur. + + Enhancements + ------------ + 1. Change: Added support for E3 (57800, 57810, and 57840) + + +uIP v0.6.4.5 (Mar. 23, 2011) +======================================================= + Enhancements + ------------ + 1. Change: Optimized the double VLAN fix of CQ53870 to match + what will be submitted for RHELS5.7 and RHELS6.1 inbox + + +uIP v0.6.4.4 (Mar. 17, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00053870 - Unable to login to iSCSI target via offload + through a Nexus 5020 switch with DCBx enabled + Cause: Double VLAN tagging was observed due to DCBx enabled. + The chip actually adds a VLAN tag if the txbd does not have + VLAN tag enabled under the DCBx environment for PRI setting. + Since uIP does not make use of hw assisted VLAN tagging, + 2 VLAN tag was observed in the data stream. + Change: Enabled hw assisted VLAN tagging in uIP for both 1g and 10g. + + 2. Problem: Cont00053792 - maxconnections intermittently fail and + recover using iface DHCPv4 + Cause: The DHCPv4 engine erroneously keeps on requesting for a + new lease which tremendously hamper normal path_req + operation. The problem is that the lease time parameter + has overflowed when converted to ticks count. + Change: Expanded the lease timer ticks count parameter from 16 to + 32 bits. + + 3. Problem: Cont00053807 - RA and link local are unable to connect if + DHCPv6 fails + Cause: The DHCPv6 engine does not have the failover to use RA + mechanism + Change: Expanded to use best match address instead regardless of + DHCPv6 success or not, or using static v6. + + Enhancements + ------------ + 1. Change: Cont00051823 - Added man page for brcm_iscsiuio + + +uIP v0.6.4.3 (Mar. 15, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00053719 - intermittent logging into targets that + are not in the same subnet as defined in the iface + Cause: The default route was used erroneously due to a miscompare + Change: Fixed this comparison so if the requested dst is not in + in the same subnet, uIP would not even ARP out. + + 2. Problem: Cont00053580 - Unable to do iSCSI boot into Linux OS using + 57710 adapters + Cause: The E1 iro USTORM_RX_PROD_OFFSET doesn't match the t6.4 fw + Change: This is now fixed + + +uIP v0.6.4.2 (Feb. 24, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00050343 - HBA does not follow RFC2131 spec for IPv4 + DHCP lease expiration + Cause: The dhcp engine did not have this feature implemented + Change: Added lease time tracking and renewal + + 2. Problem: Cont00050801 - Unable to connect to target after switching + between DHCPv4 to static v4 + Cause: The configuration flags got corrupted when switching between + dhcp and static or vice versa. + Change: Fixed the flag handling. Also needed to zero out the static + ip address in the host memory when switching to dhcp. + Otherwise, the static ip address will get used mistakenly. + + Enhancements + ------------ + 1. Change: Cont00051936 - Added IPv6 NDP and DHCPv6 support. + + +uIP v0.6.4.1 (Jan. 27, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00049766 - segfault seen while stopping iscsi service + Cause: The logger output routine was accessing the log resource + while another thread calls fini_logger to free the same + resources + Change: Added pthread mutex lock to the logger routine to exclude + the initializer, user, and finisher + + Enhancements + ------------ + 1. Change: Added new t6.4 HSI and 57713 support. + + +uIP v0.6.2.13 (Jan. 04, 2011) +======================================================= + Fixes + ----- + 1. Problem: Cont00049665 - iscsiboot:linux failed to boot into iscsi + boot image in offload path after 5 iterations + Cause: The hw consumer index for the uIP ring got out of sync + with the producer index. This has led to the xmit mutex + lock be held forever so subsequent ARP requests will not + get transmitted to the wire + Change: Added this out of sync detection and rescue the xmit mutex + lock + +uIP v0.6.2.12 (Dec. 21, 2010) +======================================================= + Fixes + ----- + 1. Problem: Cont00051820 - Session fails to reconnect after gateway + fallback + Cause: Under the HSRP test scenario, it was found that an ARP + request from the SUT is required in order for the HSRP + router to begin sending packets downstream to the SUT. + The default ARP age was originally set to 20 minutes + before a new ARP request will get sent, + Change: Changed the ARP age default to Linux default at 5 minutes + +uIP v0.6.2.11 (Dec. 17, 2010) +======================================================= + Fixes + ----- + 1. Problem: For IPv4, the gateway route was not being utilized + when the subnet mask given or calculated does not + match. This resulted in many unwanted connection + attempts. + Cause: A bug was found in the default gateway calculation + logic which prevented the gateway address from being + used. + Change: Fixed the default gateway logic + + 2. Problem: For IPv6, there are scenarios where it won't connect + Cause: The IPv6 subnet mask as extracted from the CIDR + format might contain garbage data. This garbage data + was then used as part of the subnet mask which would + prevent the correct address mask. + Change: Fixed the subnet mask + +uIP v0.6.2.10 (Dec. 15, 2010) +======================================================= + Fixes + ----- + 1. Problem: IPv6 does not connect for non-CIDR iface.ipaddress + specification + Cause: A bug where all ones was used as the IPv6 netmask + instead of all zeroes. This prevented all IPv6 + path requests from being honored + Change: Fixed the subnet mask used + +uIP v0.6.2.9 (Dec. 14, 2010) +======================================================= + Enhancements + ------------ + 1. Change: Added IP address CIDR notation support for the + iface.ipaddress field in the iface file. + This will allow subnet mask to be defined and used. + +uIP v0.6.2.8 (Dec. 9, 2010) +======================================================= + Fixes + ----- + 1. Problem: ipv6 + ifup/down fails to reconnect + + Cause: There were 2 problems found: + - the xmit_mutex lock was being held indefinitely + - the nl_process_if_down flag for 10g doorbell ringing + did not get reinitialized + + Change: Fixed the xmit_mutex deadlock via trylock + Added nl_process_if_down initialization in the IF_DOWN + process + + 2. Problem: Added fix for the NPAR disabled for 57712 + + Cause: The mac address was not handled correctly + + Change: Fixed the mac address handling. Also requires corresponding + kernel component for the complete fix + +uIP v0.6.2.7 (Dec. 7, 2010) +======================================================= + Enhancements + ------------ + 1. Change: Use the gateway address from the DHCP server the + destination IP address is not in the current subnet. + +uIP v0.6.2.6 (Nov. 16, 2010) +======================================================= + Fixes + ----- + 1. Problem: Warning message seen in the kernel logs, + "uio uio2: uevent: unsupported action string" + + Cause: The improper string was echo'ed into the UIO trigger + field. With an improper string, this message would + appear in the kernel logs. + + Change: uIP will now write the string "online" to the UIO + trigger field. This is the string expected by the + Linux kernel base driver. + + 2. Problem: uIP would segfault during a heavily login/logout + iSCSI subsystem reset senario + + Cause: A double free occurred in the logging portion of the + uIP code, but this was root cause to a double free when + manipulating the NetLink buffers. + + Change: Properly look at the return code from the routine which + will read NetLink messages. Also only free buffers + if they are allocated. + + Enhancements + ------------ + 1. Change: Add ability to print kernel version and machine + architecture to further help debug problems. + + 2. Change: Apply the netmask from DHCP if provided. + +uIP v0.6.2.5 (Nov. 10, 2010) +======================================================= + Fixes + ----- + 1. Problem: iscsid would try to conenct with unintended iSCSI + targets + + Cause: uIP would blindly return the iSCSI target MAC address + regardless if the iSCSI target is reachable via the + given port. + + Change: uIP will try to filter the requests coming from CNIC + by automatically generating a network mask based off + the configured IP addressed. Then this netmask is + masked with the destination IP address. If there is + a match, then the path_req is allowed through. + + 2. Problem: Problems reconnecting back to the target when running + MTU stress tests. + + Cause: cnic/bnx2i and uIP could possibly get out of sync when + an if_down message is sent. + + Change: uIP will now immediately react to the if_down message, + and flush all the path req's and then to process to + if_close. + + Enhancements + ------------ + 1. Change: Fix compile warnings for src/unix/nic_nl.c, + and src/unix/main.c + +uIP v0.6.2.4 (Nov. 4, 2010) +======================================================= + Fixes + ----- + 1. Problem: iSCSI HBA: brcm_iscsiuio segfault during ifdown + with many active sessions + + Cause: uIP will segfault when traversing the error path when + an iSCSI connection is starting but the sysfs entries + have not been created yet. + + Change: Use the errno value rather then the one from the file + descriptor because the file descriptor will be NULL and + the NULL dereference will cause a segfault. + + Enhancements + ------------ + 1. Change: Added initial changes for iSCSI multi-function support for + 10G NIC's. + 2. Change: Add more detailed messages for error pathes in nic_utils + +uIP v0.6.2.3 (October 28, 2010) +======================================================= + Enhancements + ------------ + 1. Change: Add support for bnx2x-1.62.x drivers + +uIP v0.6.2.2 (October 18, 2010) +======================================================= + Enhancements + ------------ + 1. Change: Only allow iSCSI connections with known bnx2x HSI's. + +uIP v0.6.2.1 (October 7, 2010) +======================================================= + Fixes + ----- + 1. Problem: After multiple MTU changes, the ethtool IOCTL used to + determine the bnx2x driver version fails and eventually + iSCSI connections would not reconnect. + + Cause: The socket file descriptor used during the ethtool IOCTL + call was never closed and leaked. + + Change: On the error path when calling the ethtool IOCTL, the + file descriptor is now properly closed. + +uIP v0.5.39 (September 15, 2010) +======================================================= + Fixes + ----- + 1. Problem: Could not offload IPv4 VLAN connection when the target tries + to ARP the iSCSI initiator + + Cause: In the ARP reply, the ether field was incorrect. + + Change: Properly set the ether field to 802.1Q type (0x8100) + +uIP v0.5.38 (September 14, 2010) +======================================================= + Fixes + ----- + 1. Problem: uIP would cause a panic dump when the NIC was going down + + Cause: uIP and CNIC where not synchonized on NIC state + + Change: Check if the RX BD's which are zero'ed by CNIC when the + NIC is going down. If the BD addresses are zero, then + uIP will drop the TX packets. + +uIP v0.5.37 (August 21, 2010) +======================================================= + Fixes + ----- + 1. Problem: uIP would segfault on ifup/ifdown stress test when using + DHCP to determine local IP address. + + Cause: The uIP would use a NULL buffer during data transmission. + + Change: Drop packets when there are no buffer avaliable. + +uIP v0.5.36 (August 21, 2010) +======================================================= + Fixes + ----- + 1. Problem: iSCSI boot would not completely login after the pivot + root operation. + + Cause: The uIP would not properly start the NIC interface. + + Change: uIP should only check the NIC state to determine whether + to start the NIC thread or not. + + 2. Problem: uIP would segfault during if'up if'down testing. + + Cause: The uIP would improperly start 2 NIC threads for the + same NIC interface. + + Change: uIP should properly lock the NIC list when disabling/removing + the NIC threads. + + +uIP v0.5.35 (August 20, 2010) +======================================================= + Fixes + ----- + 1. Problem: Sessions would hang with ethtool self-test + + Cause: The uIP would hang because the socket layer was stuck + because there is much contention for that socket. This + would hang the CNIC thread. + + Change: Remove any IOCTL calls in uIP which may colide with + the ethtool self test. The driver version is only + capture during uIP initialization. + + 2. Problem: There were session recovery issue when using DHCP + if up/down tests. + + Cause: The uIP would hang because the DHCP requests would + timeout if the network interface is downed which would + hang all the other uIP threads. + + Change: Ensure that the DHCP state machine had exit points + if the network interface was down'ed. + + +uIP v0.5.34 (August 18, 2010) +======================================================= + Fixes + ----- + 1. Problem: Sessions would not recover with ethtool self-test + + Cause: The uIP would hang because either the NetLink buffer is + full or that any socket operations used to manipulate + multicast addresses would block. + + Change: Ensure that the socket used for multicast addressing is + set to nonblocking. Drain the NetLink buffer without + using the eventing, but with a more aggressive poll routine. + + 2. Problem: Sessions would not recover with L2 driver load/unload on + RHEL 6.0 SS9 + + Cause: The uIP would close the NIC thread too early and would + deadlock on cloing the NIC thread. + + Change: Ensure that the NIC thread is canceled/closed only in one + location, in the NIC remove routine. + + +uIP v0.5.33 (August 17, 2010) +======================================================= + Fixes + ----- + 1. Problem: Error message seen from the uIP stack for valid packets. + + Cause: The uIP was incorrectly marking logging messages for valid + packets as errors because it didn't know how to parase them. + + Change: Changed the following from error to debug message + ipv6: invalid version + ipv4: invalid version or header length. + icmpv6: unknown ICMP message. + ip: neither tcp nor icmp + Changed the following from error to warn message + udp: bad checksum + tcp: bad checksum + tcp: got reset, aborting connection. + + 2. Problem: After multiple iterations the loading and unloading of + the Broadcom Linux drivers with active connections + would not cause the sessions to recover on RHEL 6.0 + snapshot 9. + + Cause: There was a deadlock in the nic mutex + + Change: Lock ordering for the nic mutex and nic list mutex must + be inforced. + + 3. Problem: After multiple iterations of running the ethtool selftest + the Broadcom Linux drivers with active connections + would not cause the sessions to recover on RHEL 5.5. + + Cause: The Netlink buffer between uIP and CNIC would get full. + + Change: Poll more regularly for packets in the Netlink buffer + from 4 times a second to 100 times a 1 second. + Drain packets during the PATH_REQ packet pull. + + +uIP v0.5.32 (August 14, 2010) +======================================================= + Fixes + ----- + 1. Problem: Error message 'nic eth0: Didn't find type 0xaa bb' seen. + + Cause: Valid non-DIX Ethernet packets as being passed to the + uIP. uIP will drop these packets but should be logged + correctly. + + Change: These packets are valid, and should only be logged for + debugging purposes. + + 2. Problem: Error message 'Dropped previous transmitted packet' seen. + + Cause: The TX ring is full, and here uIP is trying to transmit a + packet which will be dropped. This is a valid state but + the log message is marked incorrectly + + Change: These messages are not warnings and should be logging when + debugging is enabled. + + 3. Problem: Error message: "iscsi_ipc eth0 Transport name is not + equal expected: got: bnx2i" seen. + + Cause: The iface_rec structure is different between iscsid version. + For RHEL 5.5, iscsid is versioned 871, for RHEL 6.0 is + versioned 872. + + Change: Allow uIP to compile against a different version of iscsid. + + +uIP v0.5.31 (August 12, 2010) +======================================================= + Fixes + ----- + 1. Problem: Softlock would occur showing that the NetLink table + lock was taken but never released. + + Cause: NetLink socket buffer would fill with constant PATH_REQ + messages preventing PATH_REQ response from libiscsi + + Change: Now uIP will drain the NetLink buffer while looking for + a response. + + Enhancements + ------------ + 1. Change: Add documentation for VLAN configuration and restrictions. + + +uIP v0.5.30 (August 6, 2010) +======================================================= + Fixes + ----- + 1. Problem: iscsid thread will stall if closing the uio files nodes + is stuck + + Cause: uIP would indefinitely block waiting for the mutex shared + by the close routine. + + Change: Now uIP will try and poll a bit for the mutex. If it can't + get this mutex in the iscsid thread then an error is return + rather then hold the thread. + + 2. Problem: IPv6 Unicast Neighbor Adveriserments would have the + ICMPv6 option header specifying a MAC. + + Cause: uIP should use the source IPv6 address to detmine whether + to strip the option header or not and not the target address + in the ICMPv6 field. + + Change: The uIP stack return a unicast IPv6 Neighbor Advertisement + without the ICMPv6 option as a response to unicast + IPv6 Neighbor Solicitations. + + 3. Problem: There would be TCP SYN packets with improper MAC address. + + Cause: A zero'ed MAC address was not passed to CNIC to indicate an + error or if the IP address didn't resolve. + + Change: The uIP stack will now return a zero'ed MAC address if it + can't find any entries. + + +uIP v0.5.29 (August 6, 2010) +======================================================= + Fixes + ----- + 1. Problem: "uip udp: no matching connection found: lport: 35072" + seen numerous times in the brcm_iscsiuio log file + + Cause: This message was incorrectly marked as an error + + Change: These messages are valid log entries especially if the + packet was a broadcast UDP packet not destined for the SUT + I will change the code to mark these logs entries as debug. + + +uIP v0.5.28 (August 5, 2010) +======================================================= + Fixes + ----- + 1. Problem: Can't login into a redirected Equilogic Target + + Cause: The Equilogic Target uses a unicast IPv6 Neighbor + Solicitation to test if the host is up. The uIP stack + would return a Neighbor Advertisement with an unneeded + ICMPv6 option. + + Change: Only have the uIP stack return a unicast IPv6 Neighbor + Advertisement without the ICMPv6 option. + + 2. Problem: With older bnx2/bnx2x/cnic/bnx2i driver combinations + uIP would segfault when these drivers were unloaded. + + Cause: When the older drivers were removed, the underlying uio + instance was removed causing uIP to have a stale file handle. + When uIP finally closes using this stale file handle, either + uIP would segfault, or there would be an error in the + uio_release() path. + + Change: Only have the uIP close if the UIO file node exists. + + +uIP v0.5.27 (July 31, 2010) +======================================================= + Fixes + ----- + 1. Problem: iSCSI HBA: Unable to use DHCP address for iSCSI interface + if a connection was previously made with a static address + on bnx2 devices. + + Cause: Because the device is closed and reopen'ed the TX consumer + indexes were not persisted + + Change: Only discard the TX consumer indexes only when the devices + will be discarded or closed + + Enhancements + ------------ + 1. Change: Change CNIC references to bnx2 in the bnx2 user space + driver. + + +uIP v0.5.26 (July 30, 2010) +======================================================= + Fixes + ----- + 1. Problem: iSCSI HBA: Unable to use DHCP address for iSCSI interface + if a connection was previously made with a static address on + bnx2x devices. + + Cause: Because the device is closed and reopen'ed the TX consumer + indexes were not persisted + + Change: Only discard the TX consumer indexes only when the devices + will be discarded + + 2. Problem: IPv6 using VLAN's didn't login + + Cause: The uIP code used to determine if the packet was an IPv6 + or not was not working. This VLAN packets for IPv6 were + being mis-interpreted. + + Change: Make the function is_ipv6() VLAN aware + + 3. Problem: Persistant targets was not loggin in during boot + + Cause: If udev was slow and the /dev/uio* were creatly slowly + uIP would fail. + + Change: Poll uIP waiting for /dev/uio* file nodes. + +uIP v0.5.25 (July 27, 2010) +======================================================= + Fixes + ----- + 1. Problem: When using IPv4 DHCP, there are no initial DHCP Discover + packets were not seen on the wire. + + Cause: Packets generated from the app handler from the uIP stack + were not placed on the wire. + + Change: Packets originating from the uIP stack are now always placed + on the wire. + +uIP v0.5.24 (July 25, 2010) +======================================================= + Fixes + ----- + 1. Problem: One would see invalid packet packets flow through the + uIP stack, where the logs would indicate there is a packet + with an invalid length + + Cause: The BD and CQE consumer indexes were not properly incremented + and masked. + + Change: The BD index is now properly masked. The CQE index is not + incremented using the CQE index rather the mistaken BD index. + + Impact: 10G only + + 2. Problem: uIP would segfault during the booting of the machine. + + Cause: uIP was using a NULL data pointer because there was an + incorrect packet passed to the stack. + + Change: Only allow uIP to process data if the packet exists. + + 3. Problem: uIP would stop processing packets + + Cause: The uIP code would not properly drain the CQE ring causing + it to eventually be full + + Change: Consume all the CQE elements even if they are ethernet types + or not. + + Impact: 10G only + + 4. Problem: uIP would stop after if/down of the network interface. + + Cause: uIP was not kick starting the NIC loop thread properly. + + Change: Ensure that the NIC loop thread is started by when iscsid + request that the interface start the offload. Mark the NIC + only if the thread is truly canceled. + + +uIP v0.5.23 (July 20, 2010) +======================================================= + Fixes + ----- + 1. Problem: Segfault during brcm_iscsiuio initialization + + Cause: uIP was using a NULL data pointer, because a different + thread re-initialized the uIP stack + + Change: Properly synchronize the initialization of the stack + + 2. Problem: Deadlock during the printing of heavy debug messages + + Cause: The variable macro structures would point to invalid + data + + Change: With each invocation of va_copy() a corresponding + invocation of va_end() in the same function for the proper + cleanup + + 3. Problem: uIP would hang when the interface could go up/down + + Cause: uIP would get out of sync with the state of the network + interface + + Change: Instead of detriving state from the UIO file nodes, uIP + will take direction from iscsid on when interfaces will be + started. + +uIP v0.5.22 (July 15, 2010) +======================================================= + Fixes + ----- + 1. Problem: Unable to reconnect via iSCSI offload after + ifup/ifdown + + Cause: uIP was stuck on the thread when closing the NIC main + loop + + Change: Properly synchronize the NetLink CNIC and uevent threads + + 2. Problem: uIP would crash during boot up. + + Cause: uIP would overwrite a memory location which was already + freed during nic_remove(). + + Change: Since the NIC is freed there is no need to write to + update the NIC flags + + Enhancements + ------------ + + 1. Change: Added IPv6 Link Local support + + +uIP v0.5.21 (July 5, 2010) +======================================================= + Fixes + ----- + 1. Problem: Unable to connect via iSCSI offload after + changing L2 address + + Cause: uIP didn't notice the network inferface going down + + Change: Allow uIP to persist the stack's IP address after + a reset + + 2. Problem: Unable to connect via IPv4 and IPv6 concurrently + + Cause: uIP didn't notice the network inferface going down + + Change: Allow uIP to persist the stack's IP address after + a reset and properly bring up the interface + + 3. Problem: Unable to connect via VLAN + + Cause: IP address was no persisted after a device reset + + Change: When CNIC requests a path request, uIP will use the + VLAN passed by the CNIC. + + +uIP v0.5.20 (June 24, 2010) + + +uIP v0.5.20 (June 24, 2010) +======================================================= + Fixes + ----- + 1. Problem: Certain IPv6 addresses are not repsonded to by + the target. + + Cause: The MAC was generated from the target's IPv6 + address not the deterived multicast IPv6 address. + + Change: The destination MAC address should be deterived + from the packet's destination IPv6 address and + not the target. + + 2. Problem: brcm_iscsiuio would segfault when L2 interface is + bought up and down after being logged into + + Cause: The NIC thread was not stopped properly + + Change: When the UIO device is remove and when the + cooresponding NIC tracked by brcm_iscsiuio, the + daemon would properly wait for the NIC thread to + stop. + + +uIP v0.5.19 (June 22, 2010) +======================================================= + Fixes + ----- + 1. Problem: Can't login after boot + + Cause: If NIC interfaces are brough up and down quickly + uIP wait on an invalid NIC thread + + Change: Only wait for the NIC thread if the NIC thread + exists. + +uIP v0.5.18 (June 21, 2010) +======================================================= + Fixes + ----- + 1. Problem: Does not compile on SLES 11 SP1 + + Cause: Automake cached files were included as part of the + uIP-0.5.17 package + + Change: Remove automake cached files, and allow these files + to be generated each time the source is compiled + + 2. Problem: Does not always receive multicast packets + + Cause: Multicast bit was not set in SORT USER 2 register + + Change: brcm_iscsiuio will now set the SORT USER 2 registers + with both the broadcast and multicast bits. + + 3. Problem: Existing iSCSI connections do not reconnect after + operations which require equivalent driver + load/unload operations + + Cause: Multiple path requests would trample NIC configurations + + Change: Allow only one path request at a time + +uIP v0.5.17 (June 16, 2010) +======================================================= + Fixes + ----- + 1. Problem: IPv6 neighbor solicitations from brcm_iscsiuio could + not be responded to + + Cause: The IPv6 neighbor solicitation packet had an invalid + multicast MAC address + + Change: Properly set the MAC address multicast bit and OR + with the IPv6 destination address + + 2. Problem: NIC state was not properly synchronized and noticed + by Shyam Iyer + + Change: Properly lock the NIC device when changing state + + Enhancements + ------------ + + 1. Change: Listen for iscsid before daemonizing to close a timing + gap which might allow iscsid to start before uIP is + completely initialized. + +uIP v0.5.16 (June 2, 2010) +======================================================= + + Enhancements + ------------ + + 1. Change: Formally add IPv6 support. Only a static IPv6 address + is supported. + +uIP v0.5.15 (May 20, 2010) +======================================================= + + Fixes + ----- + 1. Problem: brcm_iscsiuio would echo packets off the wire + + Cause: Stale packets from the uIP stack could potentially + make it onto the wire causing a network flood + + Change: Only place on the wire packets uIP intended to place + on the wire. Drop all other packets. + +uIP v0.5.14 (May 18, 2010) +======================================================= + + Fixes + ----- + 1. Problem: brcm_iscsiuio would crash when offloading using a + bnx2x device /dev/mem could not be + opened, (ie. SE Linux enabled) + + Cause: /dev/mem could not be opened, (ie. SE Linux enabled) + and then the NIC would be improperly initialized. + + Change: If /dev/mem is not able to be opened, then the device + is closed + + 2. Problem: brcm_iscsiuio would crash when brcm_iscsiuio is + being shutdown + + Cause: The NIC mutex was deferenced imporperly when the NIC + is being closed + + Change: Take the NIC mutex lock only when the NIC is closed. + +uIP v0.5.13 (May 16, 2010) +======================================================= + + Fixes + ----- + 1. Problem: brcm_iscsiuio would crash with heavy traffic directed + at the iSCSI traffic + + Cause: Packets which are sized between 1006-1024 bytes would + crash brcm_iscsiuio because brcm_iscsiuio is not sized + to handle such large packets + + Change: Drop large packets, properly hold the NIC mutex lock + for the duration when NIC fields are being used. + + +uIP v0.5.12 (May 13, 2010) +======================================================= + + Fixes + ----- + 1. Problem: brcm_iscsiuio could crash on when L2 interface is + ifdown'ed + + Cause: The local NIC pointer was not initialized properly + in the routine parse_iface() + + Change: Properly initialize the NIC pointer + + 2. Problem: Documentation referred to older admin_client which + doesn't exist any more because brcm_iscsiuio uses + the iscsid iface file + + Change: Remove the stale references + + +uIP v0.5.11 (May 11, 2010) +======================================================= + + Fixes + ----- + 1. Problem: brcm_iscsiuio could crash on invalid packet sizes + + Cause: The hardware BD could be a large value because of a + hardware error + + Change: Limit the size of the packet dumped to the MTU size + + Enhancements + ------------ + + 1. Change: During the running of the configure script now + the script will check for ar and ranlib binaries + + +uIP v0.5.10 (May 03, 2010) +======================================================= + + Fixes + ----- + 1. Problem: BCM57712 not recognized + + Cause: The PCI ID's in the bnx2x file were missing. + + Change: Added proper BCM57712, BCM57712E, BCM57713, BCM57713E + PCI ID's + + 2. Problem: (CQ 47481) brcm_iscsiuio not installed in correct location + + Cause: Default install path for autoconf is /usr/local + + Change: Change the default prefix to '/' so the brcm_iscsiuio + binary is installed to /sbin/ + + Enhancements + ------------ + + 1. Change: Remove dependency on Yacc and Lex + + +uIP v0.5.9 (April 28, 2010) +======================================================= + + Fixes + ----- + 1. Problem: bnx2x T6.0 driver would not login + + Cause: The bnx2x code was not using the T6.0 HSI offsets + + Change: Determine to bnx2x driver version eariler to properly use the + T4.8 or T6.0 HSI + + Enhancements + ------------ + + 1. Change: Collapse all the various locks to use the NIC lock to shrink + memory footprint + + 2. Change: Consolidate upper layer checksumming code + + +uIP v0.5.5 (March 02, 2010) +======================================================= + + Enhancements + ------------ + + 1. Change: Add support for T6.0 bnx2x HSI and 57712. + + 2. Change: Initial support for IPv6 + +uIP v0.5.8 (April 22, 2010) +======================================================= + + Enhancements + ------------ + + 1. Change: Add support for T6.0 bnx2x HSI and 57712. + + 2. Change: Initial support for IPv6 + +uIP v0.5.7 (March 17, 2010) +======================================================= + + Enhancements + ------------ + + 1. Change: Add to documentation on discovering on a particular + iface before logging in + +uIP v0.5.6 (Mar 05, 2009) +======================================================= + Fixes + ----- + 1. Problem: bnx2x panic dump would be seen when sending + traffic to uIP + + Cause: The TX producer index was not properly + incrementing when the wrapping occured + + Change: Do not skip the last TX producer index like the + TX BD's + + Impact: None. + +uIP v0.5.5 (March 02, 2010) +======================================================= + Initial release + + Enhancements + ------------ + + 1. Change: Add to documentation on debugging/logging for uIP + + +uIP v0.5.4 (Feb 22, 2010) +======================================================= + Fixes + ----- + 1. Problem: Compile error where 'ETHERTYPE_VLAN' define + is missing + + Cause: Certain distributions do not define 'ETHERTYPE_VLAN' + in the header file "net/ethernet.h". + + Change: Added proper defines for ETHERTYPE_VLAN when necessary + + Impact: None. + + +uIP v0.5.3 (Feb 18, 2010) +======================================================= + Fixes + ----- + 1. Problem: Using VLAN's on offloaded iSCSI connections + + Cause: (CQ45983) VLAN tags were not being properly inserted + when sending the ARP request packets + + Change: Added VLAN tags when sending ARP request packets + + Impact: None. + + +uIP v0.5.2 (Dec 10, 2009) +======================================================= + Fixes + ----- + 1. Problem: Switching between 10G and 1G iSCSI offloaded + devices caused login connectivity problems + + Cause: The NIC devices within uIP were not cleanup + properly. + + Change: The NIC structure is not re-initialized and the + NIC thread is destroyed when the host network + interface is brought down. + + Impact: None. + + +uIP v0.5.1 (Dec 9, 2009) +======================================================= + Fixes + ----- + 1. Problem: 10G devices behind PCI bridges would not collect + + Cause: PCI bus:slot.func string was parsed incorrectly + because the bridge string was used + + Change: Parse the proper PCI bus:slot.func string. + + Impact: None. + + +uIP v0.5.0b (Nov 24, 2009) +======================================================= + Initial release + + Enhancements + ------------ + + 1. Change: Add Broadcom 10G iSCSI offload support + + Impact: Linux + diff --git a/iscsiuio/configure.ac b/iscsiuio/configure.ac new file mode 100644 index 0000000..b41df0e --- /dev/null +++ b/iscsiuio/configure.ac @@ -0,0 +1,93 @@ +dnl iscsiuio uIP user space stack configure.ac file +dnl +dnl Copyright (c) 2004-2013 Broadcom Corporation +dnl Copyright (c) 2014, QLogic Corporation +dnl +dnl This program is free software; you can redistribute it and/or modify +dnl it under the terms of the GNU General Public License as published by +dnl the Free Software Foundation. +dnl +dnl Written by: Eddie Wai (eddie.wai@broadcom.com) +dnl Benjamin Li (benli@broadcom.com) +dnl + +PACKAGE=iscsiuio +VERSION=0.7.8.6 + +AC_INIT([iscsiuio], [0.7.8.6], [QLogic-Storage-Upstream@cavium.com]) + +AM_INIT_AUTOMAKE +AC_CONFIG_HEADER(config.h) +AC_PATH_PROGS(BASH, bash) + +AC_PROG_CC +AM_PROG_CC_C_O + +AC_PROG_RANLIB + +AC_GNU_SOURCE +AC_PROG_INSTALL +AC_PROG_GCC_TRADITIONAL + +# Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_C_INLINE +AC_TYPE_OFF_T +AC_TYPE_SIZE_T +AC_CHECK_TYPES(int8_t) +AC_CHECK_TYPES(uint8_t) +AC_CHECK_TYPES(int16_t) +AC_CHECK_TYPES(uint16_t) +AC_CHECK_TYPES(int32_t) +AC_CHECK_TYPES(uint32_t) +AC_CHECK_TYPES(int64_t) +AC_CHECK_TYPES(uint64_t) +AC_CHECK_SIZEOF(short, 2) +AC_CHECK_SIZEOF(int, 4) +AC_CHECK_SIZEOF(long, 4) + +AC_C_BIGENDIAN(AC_SUBST([ENDIAN],[BIG]),AC_SUBST([ENDIAN],[LITTLE])) + +AC_LIBTOOL_DLOPEN + +# libtool stuff +AC_PROG_LIBTOOL + +: ${CFLAGS:="-O2"} +CFLAGS="${CFLAGS} -Wall" +## check for --enable-debug first before checking CFLAGS before +## so that we don't mix -O and -g +AC_ARG_ENABLE(debug, +[ --enable-debug Turn on compiler debugging information (default=no)], + [if eval "test x$enable_debug = xyes"; then + CFLAGS="${CFLAGS} -g -O0" + fi]) +AM_CONDITIONAL([DEBUG], [test x$debug = xtrue]) +## check for systemd support, default on +AC_ARG_WITH([systemd], + AS_HELP_STRING([--without-systemd], [Build without systemd]), + [case "${withval}" in + yes) LDFLAGS="`pkg-config --libs libsystemd`" ;; + no) CFLAGS="${CFLAGS} -DNO_SYSTEMD" ;; + *) AC_MSG_ERROR([bad value $withval for --with-systemd]) ;; + esac],[LDFLAGS="`pkg-config --libs libsystemd`"]) + +AC_CONFIG_COMMANDS([default],[[ + if [ -n "$SOURCE_DATE_EPOCH" ] ; then + echo 'char *build_date = "'`LC_ALL=C.UTF-8 date --date=@$SOURCE_DATE_EPOCH -u`'";' > src/unix/build_date.c + else + echo 'char *build_date = "'`date`'";' > src/unix/build_date.c + fi + echo 'char *build_date;'> src/unix/build_date.h +]],[[]]) + +AC_PREFIX_DEFAULT() + +AC_OUTPUT([Makefile +src/Makefile +src/apps/Makefile +src/apps/dhcpc/Makefile +src/apps/brcm-iscsi/Makefile +src/uip/Makefile +src/unix/Makefile +src/unix/libs/Makefile]) diff --git a/iscsiuio/docs/iscsiuio.8 b/iscsiuio/docs/iscsiuio.8 new file mode 100644 index 0000000..ea2ae02 --- /dev/null +++ b/iscsiuio/docs/iscsiuio.8 @@ -0,0 +1,89 @@ +.\" Copyright (c) 2010-2013 Broadcom Corporation +.\" Copyright (c) 2014, QLogic Corporation +.\" This is free documentation; you can redistribute it and/or +.\" modify it under the terms of the GNU General Public License as +.\" published by the Free Software Foundation. +.\" +.\" bnx2.4,v 0.7.8.1b +.\" +.TH iscsiuio 8 "12/10/2013" "QLogic Corporation" +.\" +.\" NAME part +.\" +.SH NAME +iscsiuio \- iSCSI UserSpace I/O driver +.\" +.\" SYNOPSIS part +.\" +.SH SYNOPSIS +.B iscsiuio +.RB [ -d -f -v ] +.PP +.\" +.\" DESCRIPTION part +.\" +.SH DESCRIPTION +iscsiuio is the UserSpace I/O driver for the QLogic NetXtreme II +BCM5706/5708/5709 series PCI/PCI-X Gigabit Ethernet Network Interface Card +(NIC) and for the QLogic NetXtreme II BCM57710/57711/57712/57800/57810/57840 +series PCI-E 10 Gigabit Ethernet Network Interface Card. +The driver has been tested on 2.6.28 kernels and above. +.PP +Refer to the README.TXT from the driver package on how to +compile and install the driver. +.PP +Refer to various Linux documentations +on how to configure network protocol and address. +.\" +.\" DRIVER DEPENDENCIES part +.\" +.SH DRIVER DEPENDENCIES + +.\" +.\" PARAMETER part +.\" +.SH PARAMETERS +There are very few parameters when running this application. +.TP +.BI -d|--debug +This is to enable debug mode where debug messages will be sent to stdout +The following debug modes are supported +.P +.RS +DEBUG 4 - Print all messages +.P +INFO 3 - Print messages needed to follow the uIP code (default) +.P +WARN 2 - Print warning messages +.P +ERROR 1 - Only print critical errors +.RE +.PP +.TP +.TP +.BI -f|--foreground +This is to enable foreground mode so that this application doesn't get sent +into the background. +.PP +.TP +.BI -v|--version +This is to print the version. +.PP +.TP +.BI -p|--pid +Use pidfile (default /run/iscsiuio.pid ) +.PP +.TP +.BI -h|--help +Display this help and exit. + + +.\" +.\" AUTHOR part +.\" +.SH AUTHOR +Benjamin Li \- benli@broadcom.com +.P +Eddie Wai \- eddie.wai@broadcom.com +.SH Maintained by +QLogic-Storage-Upstream@qlogic.com diff --git a/iscsiuio/iscsiuiolog b/iscsiuio/iscsiuiolog new file mode 100644 index 0000000..360947c --- /dev/null +++ b/iscsiuio/iscsiuiolog @@ -0,0 +1,10 @@ +/var/log/iscsiuio.log { + weekly + missingok + notifempty + rotate 4 + sharedscripts + postrotate + pkill -USR1 iscsiuio 2> /dev/null || true + endscript +} diff --git a/iscsiuio/src/.gitignore b/iscsiuio/src/.gitignore new file mode 100644 index 0000000..10301e2 --- /dev/null +++ b/iscsiuio/src/.gitignore @@ -0,0 +1 @@ +*.a diff --git a/iscsiuio/src/Makefile.am b/iscsiuio/src/Makefile.am new file mode 100644 index 0000000..44b0085 --- /dev/null +++ b/iscsiuio/src/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = apps uip unix diff --git a/iscsiuio/src/README b/iscsiuio/src/README new file mode 100644 index 0000000..9fca6fb --- /dev/null +++ b/iscsiuio/src/README @@ -0,0 +1,13 @@ +uIP is a very small implementation of the TCP/IP stack that is written +by Adam Dunkels . More information can be obtained +at the uIP homepage at http://www.sics.se/~adam/uip/. + +This is version $Name: uip-1-0 $. + +The directory structure look as follows: + +apps/ - Example applications +doc/ - Documentation +lib/ - Library code used by some applications +uip/ - uIP TCP/IP stack code +unix/ - uIP as a user space process under FreeBSD or Linux diff --git a/iscsiuio/src/apps/Makefile.am b/iscsiuio/src/apps/Makefile.am new file mode 100644 index 0000000..08ed18d --- /dev/null +++ b/iscsiuio/src/apps/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = dhcpc brcm-iscsi diff --git a/iscsiuio/src/apps/README b/iscsiuio/src/apps/README new file mode 100644 index 0000000..0096c4e --- /dev/null +++ b/iscsiuio/src/apps/README @@ -0,0 +1,2 @@ +This directory contains a few example applications. They are not all +heavily tested, however. diff --git a/iscsiuio/src/apps/brcm-iscsi/Makefile.am b/iscsiuio/src/apps/brcm-iscsi/Makefile.am new file mode 100644 index 0000000..00cbd8e --- /dev/null +++ b/iscsiuio/src/apps/brcm-iscsi/Makefile.am @@ -0,0 +1,13 @@ +AM_CFLAGS = -I${top_srcdir}/src/unix \ + -I${top_srcdir}/src/uip \ + -I${top_srcdir}/src/apps/dhcpc \ + -I${top_srcdir}/src/apps/brcm-iscsi \ + -I${top_srcdir}/../include \ + -I${top_srcdir}/../usr + +noinst_LIBRARIES = lib_apps_brcm_iscsi.a + +lib_apps_brcm_iscsi_a_SOURCES = brcm_iscsi.c + +lib_apps_brcm_iscsi_a_CFLAGS = $(AM_CFLAGS) \ + -DBYTE_ORDER=@ENDIAN@ diff --git a/iscsiuio/src/apps/brcm-iscsi/Makefile.brcm-iscsi b/iscsiuio/src/apps/brcm-iscsi/Makefile.brcm-iscsi new file mode 100644 index 0000000..732275f --- /dev/null +++ b/iscsiuio/src/apps/brcm-iscsi/Makefile.brcm-iscsi @@ -0,0 +1 @@ +APP_SOURCES += brcm-iscsi.c diff --git a/iscsiuio/src/apps/brcm-iscsi/brcm_iscsi.c b/iscsiuio/src/apps/brcm-iscsi/brcm_iscsi.c new file mode 100644 index 0000000..4223ca4 --- /dev/null +++ b/iscsiuio/src/apps/brcm-iscsi/brcm_iscsi.c @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li + * Based on code example from Adam Dunkels + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * \addtogroup brcm-iscsi + * @{ + */ + +/** + * \file + * An example of how to write uIP applications + * with protosockets + * \author + * Benjamin Li + */ + +/* + * This is a short example of how to write uIP applications using + * protosockets. + */ + +/* + * We define the application state (struct hello_world_state) in the + * hello-world.h file, so we need to include it here. We also include + * uip.h (since this cannot be included in hello-world.h) and + * , since we use the memcpy() function in the code. + */ +#include "brcm_iscsi.h" +#include "uip.h" +#include +#include + +#include "uip_arp.h" + +/*---------------------------------------------------------------------------*/ +/* + * The initialization function. We must explicitly call this function + * from the system initialization code, some time after uip_init() is + * called. + */ +void brcm_iscsi_init(void) +{ +} + +/*---------------------------------------------------------------------------*/ +/* + * In hello-world.h we have defined the UIP_APPCALL macro to + * hello_world_appcall so that this funcion is uIP's application + * function. This function is called whenever an uIP event occurs + * (e.g. when a new connection is established, new data arrives, sent + * data is acknowledged, data needs to be retransmitted, etc.). + */ +void brcm_iscsi_appcall(struct uip_stack *ustack) +{ +} diff --git a/iscsiuio/src/apps/brcm-iscsi/brcm_iscsi.h b/iscsiuio/src/apps/brcm-iscsi/brcm_iscsi.h new file mode 100644 index 0000000..10bfc95 --- /dev/null +++ b/iscsiuio/src/apps/brcm-iscsi/brcm_iscsi.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li + * Based on code example from Adam Dunkels + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * \addtogroup apps + * @{ + */ + +/** + * \defgroup helloworld Hello, world + * @{ + * + * A small example showing how to write applications with + * \ref psock "protosockets". + */ + +/** + * \file + * Header file for an example of how to write uIP applications + * with protosockets. + * \author + * Benjamin Li + */ + +#ifndef __BRCM_ISCSI_H__ +#define __BRCM_ISCSI_H__ + +/* Since this file will be included by uip.h, we cannot include uip.h + here. But we might need to include uipopt.h if we need the u8_t and + u16_t datatypes. */ +#include "uipopt.h" +#include "uip.h" +#include "psock.h" + +/* Next, we define the hello_world_state structure. This is the state + of our application, and the memory required for this state is + allocated together with each TCP connection. One application state + for each TCP connection. */ +struct hello_world_state { + struct psock p; + u8_t inputbuffer[32]; + u8_t name[40]; + + struct uip_udp_conn *conn; +}; + +/* Finally we define the application function to be called by uIP. */ +void brcm_iscsi_appcall(struct uip_stack *ustack); +#ifndef UIP_APPCALL +#define UIP_APPCALL brcm_iscsi_appcall +#endif /* UIP_APPCALL */ + +void brcm_iscsi_init(void); + +#endif /* __BRCM_ISCSI_H__ */ +/** @} */ +/** @} */ diff --git a/iscsiuio/src/apps/dhcpc/Makefile.am b/iscsiuio/src/apps/dhcpc/Makefile.am new file mode 100644 index 0000000..1c97993 --- /dev/null +++ b/iscsiuio/src/apps/dhcpc/Makefile.am @@ -0,0 +1,13 @@ +AM_CFLAGS = -I${top_srcdir}/src/unix \ + -I${top_srcdir}/src/uip \ + -I${top_srcdir}/src/apps/dhcpc \ + -I${top_srcdir}/src/apps/brcm-iscsi \ + -I${top_srcdir}/../include \ + -I${top_srcdir}/../usr + +noinst_LIBRARIES = lib_apps_dhcpc.a + +lib_apps_dhcpc_a_SOURCES = dhcpc.c dhcpv6.c + +lib_apps_dhcpc_a_CFLAGS = $(AM_CFLAGS) \ + -DBYTE_ORDER=@ENDIAN@ diff --git a/iscsiuio/src/apps/dhcpc/Makefile.dhcpc b/iscsiuio/src/apps/dhcpc/Makefile.dhcpc new file mode 100644 index 0000000..f84c84f --- /dev/null +++ b/iscsiuio/src/apps/dhcpc/Makefile.dhcpc @@ -0,0 +1 @@ +APP_SOURCES += dhcpc.c timer.c diff --git a/iscsiuio/src/apps/dhcpc/dhcpc.c b/iscsiuio/src/apps/dhcpc/dhcpc.c new file mode 100644 index 0000000..f4a9994 --- /dev/null +++ b/iscsiuio/src/apps/dhcpc/dhcpc.c @@ -0,0 +1,417 @@ +/* + * Copyright (c) 2005, Swedish Institute of Computer Science + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack + * + */ + +#include +#include +#include +#include +#include +#include + +#include "uip.h" +#include "dhcpc.h" +#include "timer.h" +#include "pt.h" + +#include "debug.h" +#include "logger.h" +#include "nic.h" +#include "nic_utils.h" + +struct __attribute__ ((__packed__)) dhcp_msg { + u8_t op, htype, hlen, hops; + u8_t xid[4]; + u16_t secs, flags; + u8_t ciaddr[4]; + u8_t yiaddr[4]; + u8_t siaddr[4]; + u8_t giaddr[4]; + u8_t chaddr[16]; +#ifndef UIP_CONF_DHCP_LIGHT + u8_t sname[64]; + u8_t file[128]; +#endif + u8_t options[312]; +}; + +#define BOOTP_BROADCAST 0x8000 + +#define DHCP_REQUEST 1 +#define DHCP_REPLY 2 +#define DHCP_HTYPE_ETHERNET 1 +#define DHCP_HLEN_ETHERNET 6 +#define DHCP_MSG_LEN 236 + +#define DHCPC_SERVER_PORT 67 +#define DHCPC_CLIENT_PORT 68 + +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPDECLINE 4 +#define DHCPACK 5 +#define DHCPNAK 6 +#define DHCPRELEASE 7 + +#define DHCP_OPTION_SUBNET_MASK 1 +#define DHCP_OPTION_ROUTER 3 +#define DHCP_OPTION_DNS_SERVER 6 +#define DHCP_OPTION_REQ_IPADDR 50 +#define DHCP_OPTION_LEASE_TIME 51 +#define DHCP_OPTION_MSG_TYPE 53 +#define DHCP_OPTION_SERVER_ID 54 +#define DHCP_OPTION_REQ_LIST 55 +#define DHCP_OPTION_END 255 + +static u8_t xid[4] = { 0xad, 0xde, 0x12, 0x23 }; +static const u8_t magic_cookie[4] = { 99, 130, 83, 99 }; + +struct dhcpc_options dhcpc_opt = { + .enable_random_xid = 1, +}; + +/*---------------------------------------------------------------------------*/ +static u8_t *add_msg_type(u8_t *optptr, u8_t type) +{ + *optptr++ = DHCP_OPTION_MSG_TYPE; + *optptr++ = 1; + *optptr++ = type; + return optptr; +} + +/*---------------------------------------------------------------------------*/ +static u8_t *add_server_id(struct dhcpc_state *s, u8_t *optptr) +{ + *optptr++ = DHCP_OPTION_SERVER_ID; + *optptr++ = 4; + memcpy(optptr, s->serverid, 4); + return optptr + 4; +} + +/*---------------------------------------------------------------------------*/ +static u8_t *add_req_ipaddr(struct dhcpc_state *s, u8_t *optptr) +{ + *optptr++ = DHCP_OPTION_REQ_IPADDR; + *optptr++ = 4; + memcpy(optptr, s->ipaddr, 4); + return optptr + 4; +} + +/*---------------------------------------------------------------------------*/ +static u8_t *add_req_options(u8_t *optptr) +{ + *optptr++ = DHCP_OPTION_REQ_LIST; + *optptr++ = 3; + *optptr++ = DHCP_OPTION_SUBNET_MASK; + *optptr++ = DHCP_OPTION_ROUTER; + *optptr++ = DHCP_OPTION_DNS_SERVER; + return optptr; +} + +/*---------------------------------------------------------------------------*/ +static u8_t *add_end(u8_t *optptr) +{ + *optptr++ = DHCP_OPTION_END; + return optptr; +} + +/*---------------------------------------------------------------------------*/ +static void create_msg(struct dhcpc_state *s, struct dhcp_msg *m) +{ + m->op = DHCP_REQUEST; + m->htype = DHCP_HTYPE_ETHERNET; + m->hlen = s->mac_len; + m->hops = 0; + memcpy(m->xid, xid, sizeof(m->xid)); + m->secs = 0; + m->flags = const_htons(BOOTP_BROADCAST); /* Broadcast bit. */ + /* uip_ipaddr_copy(m->ciaddr, uip_hostaddr); */ + memcpy(m->ciaddr, s->ustack->hostaddr, sizeof(m->ciaddr)); + memset(m->yiaddr, 0, sizeof(m->yiaddr)); + memset(m->siaddr, 0, sizeof(m->siaddr)); + memset(m->giaddr, 0, sizeof(m->giaddr)); + memcpy(m->chaddr, s->mac_addr, s->mac_len); + memset(&m->chaddr[s->mac_len], 0, sizeof(m->chaddr) - s->mac_len); +#ifndef UIP_CONF_DHCP_LIGHT + memset(m->sname, 0, sizeof(m->sname)); + memset(m->file, 0, sizeof(m->file)); +#endif + + memcpy(m->options, magic_cookie, sizeof(magic_cookie)); +} + +/*---------------------------------------------------------------------------*/ +static void send_discover(struct dhcpc_state *s) +{ + u8_t *end; + struct dhcp_msg *m = (struct dhcp_msg *)s->ustack->uip_appdata; + + create_msg(s, m); + + end = add_msg_type(&m->options[4], DHCPDISCOVER); + end = add_req_options(end); + end = add_end(end); + + uip_appsend(s->ustack, s->ustack->uip_appdata, + end - (u8_t *) s->ustack->uip_appdata); +} + +/*---------------------------------------------------------------------------*/ +static void send_request(struct dhcpc_state *s) +{ + u8_t *end; + struct dhcp_msg *m = (struct dhcp_msg *)s->ustack->uip_appdata; + + create_msg(s, m); + + end = add_msg_type(&m->options[4], DHCPREQUEST); + end = add_server_id(s, end); + end = add_req_ipaddr(s, end); + end = add_end(end); + + uip_appsend(s->ustack, s->ustack->uip_appdata, + end - (u8_t *) s->ustack->uip_appdata); +} + +/*---------------------------------------------------------------------------*/ +static u8_t parse_options(struct dhcpc_state *s, u8_t *optptr, int len) +{ + u8_t *end = optptr + len; + u8_t type = 0; + + while (optptr < end) { + switch (*optptr) { + case DHCP_OPTION_SUBNET_MASK: + memcpy(s->netmask, optptr + 2, 4); + break; + case DHCP_OPTION_ROUTER: + memcpy(s->default_router, optptr + 2, 4); + break; + case DHCP_OPTION_DNS_SERVER: + memcpy(s->dnsaddr, optptr + 2, 4); + break; + case DHCP_OPTION_MSG_TYPE: + type = *(optptr + 2); + break; + case DHCP_OPTION_SERVER_ID: + memcpy(s->serverid, optptr + 2, 4); + break; + case DHCP_OPTION_LEASE_TIME: + memcpy(s->lease_time, optptr + 2, 4); + break; + case DHCP_OPTION_END: + return type; + } + + optptr += optptr[1] + 2; + } + return type; +} + +/*---------------------------------------------------------------------------*/ +static u8_t parse_msg(struct dhcpc_state *s) +{ + struct dhcp_msg *m = (struct dhcp_msg *)s->ustack->uip_appdata; + + if (m->op == DHCP_REPLY && + memcmp(m->xid, xid, sizeof(xid)) == 0 && + memcmp(m->chaddr, s->mac_addr, s->mac_len) == 0) { + memcpy(s->ipaddr, m->yiaddr, 4); + return parse_options(s, &m->options[4], uip_datalen(s->ustack)); + } + return 0; +} + +/*---------------------------------------------------------------------------*/ +static PT_THREAD(handle_dhcp(struct uip_stack *ustack)) +{ + struct dhcpc_state *s; + s = ustack->dhcpc; + + if (s == NULL) { + LOG_WARN("Could not find dhcpc state"); + return PT_ENDED; + } + + PT_BEGIN(&s->pt); + + /* try_again: */ + s->state = STATE_SENDING; + s->ticks = CLOCK_SECOND; + + do { + send_discover(s); + timer_set(&s->timer, s->ticks); + PT_WAIT_UNTIL(&s->pt, uip_newdata(s->ustack) + || timer_expired(&s->timer)); + + if (uip_newdata(s->ustack) && parse_msg(s) == DHCPOFFER) { + s->state = STATE_OFFER_RECEIVED; + break; + } + + if (s->ticks < CLOCK_SECOND * 60) + s->ticks += CLOCK_SECOND; + else + PT_RESTART(&s->pt); + } while (s->state != STATE_OFFER_RECEIVED); + + s->ticks = CLOCK_SECOND; + + do { + send_request(s); + timer_set(&s->timer, s->ticks); + s->ustack->uip_flags &= ~UIP_NEWDATA; + PT_WAIT_UNTIL(&s->pt, uip_newdata(s->ustack) + || timer_expired(&s->timer)); + + if (uip_newdata(s->ustack) && parse_msg(s) == DHCPACK) { + s->state = STATE_CONFIG_RECEIVED; + break; + } + + if (s->ticks <= CLOCK_SECOND * 10) + s->ticks += CLOCK_SECOND; + else + PT_RESTART(&s->pt); + } while (s->state != STATE_CONFIG_RECEIVED); + + LOG_INFO("Got IP address %d.%d.%d.%d", + uip_ipaddr1(s->ipaddr), uip_ipaddr2(s->ipaddr), + uip_ipaddr3(s->ipaddr), uip_ipaddr4(s->ipaddr)); + LOG_INFO("Got netmask %d.%d.%d.%d", + uip_ipaddr1(s->netmask), uip_ipaddr2(s->netmask), + uip_ipaddr3(s->netmask), uip_ipaddr4(s->netmask)); + LOG_INFO("Got DNS server %d.%d.%d.%d", + uip_ipaddr1(s->dnsaddr), uip_ipaddr2(s->dnsaddr), + uip_ipaddr3(s->dnsaddr), uip_ipaddr4(s->dnsaddr)); + LOG_INFO("Got default router %d.%d.%d.%d", + uip_ipaddr1(s->default_router), uip_ipaddr2(s->default_router), + uip_ipaddr3(s->default_router), + uip_ipaddr4(s->default_router)); + s->lease_time_nl32 = + ntohs(s->lease_time[0]) * 65536ul + ntohs(s->lease_time[1]); + LOG_INFO("Lease expires in %ld seconds", s->lease_time_nl32); + + s->last_update = time(NULL); + + set_uip_stack(s->ustack, + (uip_ip4addr_t *) s->ipaddr, + (uip_ip4addr_t *) s->netmask, + (uip_ip4addr_t *) s->default_router, + (uint8_t *) s->mac_addr); + + /* Put the stack thread back into a long sleep */ + s->nic->flags |= NIC_LONG_SLEEP; + + /* timer_stop(&s.timer); */ + + /* Handle DHCP lease expiration */ + s->ticks = CLOCK_SECOND * s->lease_time_nl32; + timer_set(&s->timer, s->ticks); + PT_WAIT_UNTIL(&s->pt, timer_expired(&s->timer)); + LOG_INFO("Lease expired, re-acquire IP address"); + s->nic->flags &= ~NIC_LONG_SLEEP; + PT_RESTART(&s->pt); + + /* + * PT_END restarts the thread so we do this instead. Eventually we + * should reacquire expired leases here. + */ + + while (1) + PT_YIELD(&s->pt); + + PT_END(&(s->pt)); +} + +/*---------------------------------------------------------------------------*/ +int dhcpc_init(nic_t *nic, struct uip_stack *ustack, + const void *mac_addr, int mac_len) +{ + uip_ip4addr_t addr; + struct dhcpc_state *s = ustack->dhcpc; + + if (s) { + LOG_DEBUG("DHCP: DHCP context already allocated"); + return -EALREADY; + } + s = malloc(sizeof(*s)); + if (s == NULL) { + LOG_ERR("Couldn't allocate size for dhcpc info"); + return -ENOMEM; + } + + memset(s, 0, sizeof(*s)); + s->nic = nic; + s->ustack = ustack; + s->mac_addr = mac_addr; + s->mac_len = mac_len; + s->state = STATE_INITIAL; + + /* Initialize XID to randomly */ + if (dhcpc_opt.enable_random_xid == 1) { + u32_t gen_xid; + gen_xid = random(); + memcpy(xid, &gen_xid, sizeof(gen_xid)); + } + uip_ipaddr(addr, 255, 255, 255, 255); + s->conn = uip_udp_new(ustack, &addr, const_htons(DHCPC_SERVER_PORT)); + if (s->conn != NULL) + uip_udp_bind(s->conn, const_htons(DHCPC_CLIENT_PORT)); + + ustack->dhcpc = s; + + /* Let the RX poll value take over */ + nic->flags &= ~NIC_LONG_SLEEP; + + PT_INIT(&s->pt); + + return 0; +} + +/*---------------------------------------------------------------------------*/ +void dhcpc_appcall(struct uip_stack *ustack) +{ + handle_dhcp(ustack); +} + +/*---------------------------------------------------------------------------*/ +void dhcpc_request(struct uip_stack *ustack) +{ + struct dhcpc_state *s = ustack->dhcpc; + + if (s != NULL && s->state == STATE_INITIAL) + handle_dhcp(ustack); +} + +/*---------------------------------------------------------------------------*/ diff --git a/iscsiuio/src/apps/dhcpc/dhcpc.h b/iscsiuio/src/apps/dhcpc/dhcpc.h new file mode 100644 index 0000000..89cf086 --- /dev/null +++ b/iscsiuio/src/apps/dhcpc/dhcpc.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2005, Swedish Institute of Computer Science + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack + * + */ +#ifndef __DHCPC_H__ +#define __DHCPC_H__ + +#include + +#include "nic.h" +#include "timer.h" +#include "pt.h" +#include "uip.h" + +#define STATE_INITIAL 0 +#define STATE_SENDING 1 +#define STATE_OFFER_RECEIVED 2 +#define STATE_CONFIG_RECEIVED 3 + +struct dhcpc_state { + struct pt pt; + + nic_t *nic; + struct uip_stack *ustack; + char state; + struct uip_udp_conn *conn; + struct timer timer; + u32_t ticks; + const void *mac_addr; + int mac_len; + + u8_t serverid[4]; + + u16_t lease_time[2]; + u32_t lease_time_nl32; + u16_t ipaddr[2]; + u16_t netmask[2]; + u16_t dnsaddr[2]; + u16_t default_router[2]; + + time_t last_update; +}; + +struct dhcpc_options { + u8_t enable_random_xid; + u8_t xid[4]; +}; + +int dhcpc_init(nic_t *nic, struct uip_stack *ustack, + const void *mac_addr, int mac_len); +void dhcpc_request(struct uip_stack *ustack); + +void dhcpc_appcall(struct uip_stack *ustack); + +void dhcpc_configured(const struct dhcpc_state *s); + +#define UIP_UDP_APPCALL dhcpc_appcall + +#endif /* __DHCPC_H__ */ diff --git a/iscsiuio/src/apps/dhcpc/dhcpv6.c b/iscsiuio/src/apps/dhcpc/dhcpv6.c new file mode 100644 index 0000000..461af0e --- /dev/null +++ b/iscsiuio/src/apps/dhcpc/dhcpv6.c @@ -0,0 +1,517 @@ +/* + * Copyright (c) 2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Eddie Wai + * Based on code from Kevin Tran's iSCSI boot code + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * dhcpv6.c - DHCPv6 engine + * + */ +#include +#include + +#include "ipv6.h" +#include "ipv6_pkt.h" +#include "dhcpv6.h" +#include "logger.h" + +/* Local function prototypes */ +static int dhcpv6_send_solicit_packet(struct dhcpv6_context *context); +static int dhcpv6_send_request_packet(struct dhcpv6_context *context); +static u16_t dhcpv6_init_packet(struct dhcpv6_context *context, u8_t type); +static void dhcpv6_init_dhcpv6_server_addr(struct ipv6_addr *addr); +static void dhcpv6_handle_advertise(struct dhcpv6_context *context, + u16_t dhcpv6_len); +static void dhcpv6_handle_reply(struct dhcpv6_context *context, + u16_t dhcpv6_len); +static int dhcpv6_process_opt_ia_na(struct dhcpv6_context *context, + struct dhcpv6_opt_hdr *opt_hdr); +static void dhcpv6_process_opt_dns_servers(struct dhcpv6_context *context, + struct dhcpv6_opt_hdr *opt_hdr); +static void dhcpv6_parse_vendor_option(struct dhcpv6_context *context, + u8_t *option, int len); + +void dhcpv6_init(struct dhcpv6_context *context) +{ + context->seconds = 0; + context->our_mac_addr = + ipv6_get_link_addr(context->ipv6_context); + + /* Use the last four bytes of MAC address as base of the transaction + ID */ + context->dhcpv6_transaction_id = context->our_mac_addr->last_4_bytes; + + context->dhcpv6_done = FALSE; + strcpy(context->dhcp_vendor_id, "BRCM ISAN"); +} + +int dhcpv6_do_discovery(struct dhcpv6_context *context) +{ + int retc = ISCSI_FAILURE; + + context->eth = + (struct eth_hdr *)context->ipv6_context->ustack->data_link_layer; + context->ipv6 = + (struct ipv6_hdr *)context->ipv6_context->ustack->network_layer; + context->udp = + (struct udp_hdr *)((u8_t *)context->ipv6 + sizeof(struct ipv6_hdr)); + + /* Send out DHCPv6 Solicit packet. */ + dhcpv6_send_solicit_packet(context); + + return retc; +} + +static int dhcpv6_send_solicit_packet(struct dhcpv6_context *context) +{ + u16_t packet_len; + + LOG_DEBUG("DHCPV6: Send solicit"); + packet_len = dhcpv6_init_packet(context, DHCPV6_SOLICIT); + context->dhcpv6_state = DHCPV6_STATE_SOLICIT_SENT; + ipv6_send_udp_packet(context->ipv6_context, packet_len); + + return 0; +} + +static int dhcpv6_send_request_packet(struct dhcpv6_context *context) +{ + u16_t packet_len; + + LOG_DEBUG("DHCPV6: Send request"); + packet_len = dhcpv6_init_packet(context, DHCPV6_REQUEST); + + context->dhcpv6_state = DHCPV6_STATE_REQ_SENT; + ipv6_send_udp_packet(context->ipv6_context, packet_len); + + return 0; +} + +static u16_t dhcpv6_init_packet(struct dhcpv6_context *context, u8_t type) +{ + u16_t pkt_len; + struct udp_hdr *udp = context->udp; + union dhcpv6_hdr *dhcpv6; + struct dhcpv6_option *opt; + u16_t len; + + /* Initialize dest IP with well-known DHCP server address */ + dhcpv6_init_dhcpv6_server_addr(&context->ipv6->ipv6_dst); + /* Initialize dest MAC based on MC dest IP */ + ipv6_mc_init_dest_mac(context->eth, context->ipv6); + + /* Initialize UDP header */ + udp->src_port = HOST_TO_NET16(DHCPV6_CLIENT_PORT); + udp->dest_port = HOST_TO_NET16(DHCPV6_SERVER_PORT); + + /* + * DHCPv6 section has the following format per RFC 3315 + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | msg-type | transaction-id | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * . options . + * . (variable) . + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + dhcpv6 = (union dhcpv6_hdr *)((u8_t *)udp + sizeof(struct udp_hdr)); + + if (dhcpv6->dhcpv6_type != type) + context->dhcpv6_transaction_id++; + + dhcpv6->dhcpv6_trans_id = HOST_TO_NET16(context->dhcpv6_transaction_id); + dhcpv6->dhcpv6_type = type; + + /* Keep track of length of all DHCP options. */ + pkt_len = sizeof(union dhcpv6_hdr); + + if (dhcpv6->dhcpv6_type == DHCPV6_REQUEST) { + /* We will send back whatever DHCPv6 sent us */ + return ((u8_t *)udp - (u8_t *)context->eth + + NET_TO_HOST16(udp->length)); + } + + opt = (struct dhcpv6_option *)((u8_t *)dhcpv6 + + sizeof(union dhcpv6_hdr)); + /* Add client ID option */ + opt->hdr.type = HOST_TO_NET16(DHCPV6_OPT_CLIENTID); + opt->hdr.length = HOST_TO_NET16(sizeof(struct dhcpv6_opt_client_id)); + opt->type.client_id.duid_type = + HOST_TO_NET16(DHCPV6_DUID_TYPE_LINK_LAYER_AND_TIME); + opt->type.client_id.hw_type = HOST_TO_NET16(DHCPV6_HW_TYPE_ETHERNET); + opt->type.client_id.time = HOST_TO_NET32(clock_time()/1000 - + 0x3A4FC880); + memcpy((char *)&opt->type.client_id.link_layer_addr, + (char *)context->our_mac_addr, sizeof(struct mac_address)); + pkt_len += sizeof(struct dhcpv6_opt_client_id) + + sizeof(struct dhcpv6_opt_hdr); + opt = (struct dhcpv6_option *)((u8_t *)opt + + sizeof(struct dhcpv6_opt_client_id) + + sizeof(struct dhcpv6_opt_hdr)); + + /* Add Vendor Class option if it's configured */ + len = strlen(context->dhcp_vendor_id); + if (len > 0) { + opt->hdr.type = HOST_TO_NET16(DHCPV6_OPT_VENDOR_CLASS); + opt->hdr.length = + HOST_TO_NET16(sizeof(struct dhcpv6_vendor_class) + + len - 1); + opt->type.vendor_class.enterprise_number = + HOST_TO_NET32(IANA_ENTERPRISE_NUM_BROADCOM); + opt->type.vendor_class.vendor_class_length = HOST_TO_NET16(len); + memcpy((char *)&opt->type.vendor_class. + vendor_class_data[0], + (char *)context->dhcp_vendor_id, len); + pkt_len += + sizeof(struct dhcpv6_vendor_class) - 1 + len + + sizeof(struct dhcpv6_opt_hdr); + opt = + (struct dhcpv6_option *)((u8_t *)opt + + sizeof(struct dhcpv6_vendor_class) - 1 + len + + sizeof(struct dhcpv6_opt_hdr)); + } + + /* Add IA_NA option */ + opt->hdr.type = HOST_TO_NET16(DHCPV6_OPT_IA_NA); + opt->hdr.length = HOST_TO_NET16(sizeof(struct dhcpv6_opt_id_assoc_na)); + opt->type.ida_na.iaid = htonl(context->our_mac_addr->last_4_bytes); + opt->type.ida_na.t1 = 0; + opt->type.ida_na.t2 = 0; + pkt_len += sizeof(struct dhcpv6_opt_id_assoc_na) + + sizeof(struct dhcpv6_opt_hdr); + opt = (struct dhcpv6_option *)((u8_t *)opt + + sizeof(struct dhcpv6_opt_id_assoc_na) + + sizeof(struct dhcpv6_opt_hdr)); + /* Add Elapsed Time option */ + opt->hdr.type = HOST_TO_NET16(DHCPV6_OPT_ELAPSED_TIME); + opt->hdr.length = HOST_TO_NET16(sizeof(struct dhcpv6_opt_elapse_time)); + opt->type.elapsed_time.time = HOST_TO_NET16(context->seconds); + pkt_len += sizeof(struct dhcpv6_opt_elapse_time) + + sizeof(struct dhcpv6_opt_hdr); + + /* Add Option Request List */ + opt = (struct dhcpv6_option *)((u8_t *)opt + + sizeof(struct dhcpv6_opt_elapse_time) + + sizeof(struct dhcpv6_opt_hdr)); + opt->hdr.type = HOST_TO_NET16(DHCPV6_OPT_ORO); + opt->hdr.length = HOST_TO_NET16(3 * + sizeof(struct dhcpv6_opt_request_list)); + opt->type.list.request_code[0] = HOST_TO_NET16(DHCPV6_OPT_VENDOR_CLASS); + opt->type.list.request_code[1] = HOST_TO_NET16(DHCPV6_OPT_VENDOR_OPTS); + opt->type.list.request_code[2] = HOST_TO_NET16(DHCPV6_OPT_DNS_SERVERS); + pkt_len += 3 * sizeof(struct dhcpv6_opt_request_list) + + sizeof(struct dhcpv6_opt_hdr); + + udp->length = HOST_TO_NET16(sizeof(struct udp_hdr) + pkt_len); + + pkt_len += + ((u8_t *)udp - (u8_t *)context->eth) + sizeof(struct udp_hdr); + + return pkt_len; +} + +static void dhcpv6_init_dhcpv6_server_addr(struct ipv6_addr *addr) +{ + /* Well-known DHCPv6 server address is ff02::1:2 */ + memset((char *)addr, 0, sizeof(struct ipv6_addr)); + addr->addr8[0] = 0xff; + addr->addr8[1] = 0x02; + addr->addr8[13] = 0x01; + addr->addr8[15] = 0x02; +} + +void ipv6_udp_handle_dhcp(struct dhcpv6_context *context) +{ + union dhcpv6_hdr *dhcpv6; + u16_t dhcpv6_len; + + if (context->dhcpv6_done == TRUE) + return; + + dhcpv6 = (union dhcpv6_hdr *)((u8_t *)context->udp + + sizeof(struct udp_hdr)); + + if (dhcpv6->dhcpv6_trans_id != + HOST_TO_NET16(context->dhcpv6_transaction_id)) { + LOG_ERR("DHCPv6 transaction-id error, sent %x, received %x", + HOST_TO_NET16(context->dhcpv6_transaction_id), + dhcpv6->dhcpv6_trans_id); + return; + } + + dhcpv6_len = + NET_TO_HOST16(context->udp->length) - sizeof(struct udp_hdr); + + switch (dhcpv6->dhcpv6_type) { + case DHCPV6_ADVERTISE: + dhcpv6_handle_advertise(context, dhcpv6_len); + break; + + case DHCPV6_REPLY: + dhcpv6_handle_reply(context, dhcpv6_len); + break; + + default: + break; + } +} + +static void dhcpv6_handle_advertise(struct dhcpv6_context *context, + u16_t dhcpv6_len) +{ + union dhcpv6_hdr *dhcpv6 = + (union dhcpv6_hdr *)((u8_t *)context->udp + + sizeof(struct udp_hdr)); + struct dhcpv6_opt_hdr *opt; + u16_t type; + int i; + int opt_len; + u8_t *vendor_id = NULL; + u16_t vendor_id_len = 0; + u8_t *vendor_opt_data = NULL; + int vendor_opt_len = 0; + int addr_cnt = 0; + + /* We only handle DHCPv6 advertise if we recently sent DHCPv6 solicit */ + if (context->dhcpv6_state != DHCPV6_STATE_SOLICIT_SENT) + return; + + LOG_DEBUG("DHCPV6: handle advertise"); + context->dhcpv6_state = DHCPV6_STATE_ADV_RCVD; + + i = 0; + while (i < (dhcpv6_len - sizeof(union dhcpv6_hdr))) { + opt = (struct dhcpv6_opt_hdr *)((u8_t *)dhcpv6 + + sizeof(union dhcpv6_hdr) + i); + opt_len = NET_TO_HOST16(opt->length); + + type = NET_TO_HOST16(opt->type); + + /* We only care about some of the options */ + switch (type) { + case DHCPV6_OPT_IA_NA: + if (context-> + dhcpv6_task & DHCPV6_TASK_GET_IP_ADDRESS) { + addr_cnt += + dhcpv6_process_opt_ia_na(context, opt); + } + break; + + case DHCPV6_OPT_VENDOR_CLASS: + vendor_id_len = + NET_TO_HOST16(((struct dhcpv6_option *)opt)->type. + vendor_class.vendor_class_length); + vendor_id = + &((struct dhcpv6_option *)opt)->type.vendor_class. + vendor_class_data[0]; + break; + + case DHCPV6_OPT_VENDOR_OPTS: + vendor_opt_len = opt_len - 4; + vendor_opt_data = + &((struct dhcpv6_option *)opt)->type.vendor_opts. + vendor_opt_data[0]; + break; + + case DHCPV6_OPT_DNS_SERVERS: + if (context->dhcpv6_task & DHCPV6_TASK_GET_OTHER_PARAMS) + dhcpv6_process_opt_dns_servers(context, opt); + break; + + default: + break; + } + + i += NET_TO_HOST16(opt->length) + sizeof(struct dhcpv6_opt_hdr); + } + + if (context->dhcpv6_task & DHCPV6_TASK_GET_OTHER_PARAMS) { + if ((vendor_id_len > 0) && + (strncmp((char *)vendor_id, + (char *)context->dhcp_vendor_id, + vendor_id_len) == 0)) { + dhcpv6_parse_vendor_option(context, + vendor_opt_data, + vendor_opt_len); + context->dhcpv6_done = TRUE; + } + } + + if (context->dhcpv6_task & DHCPV6_TASK_GET_IP_ADDRESS) { + if (addr_cnt > 0) { + /* + * If we need to acquire IP address from the server, + * we need to send Request to server to confirm. + */ + dhcpv6_send_request_packet(context); + context->dhcpv6_done = TRUE; + } + } + + if (context->dhcpv6_done) { + /* Keep track of IPv6 address of DHCHv6 server */ + memcpy((char *)&context->dhcp_server, + (char *)&context->ipv6->ipv6_src, + sizeof(struct ipv6_addr)); + } +} + +static int dhcpv6_process_opt_ia_na(struct dhcpv6_context *context, + struct dhcpv6_opt_hdr *opt_hdr) +{ + int i; + int opt_len; + struct dhcpv6_option *opt; + int len; + int addr_cnt; + opt_len = NET_TO_HOST16(opt_hdr->length) - + sizeof(struct dhcpv6_opt_id_assoc_na); + + i = 0; + addr_cnt = 0; + while (i < opt_len) { + opt = + (struct dhcpv6_option *)((u8_t *)opt_hdr + + sizeof(struct dhcpv6_opt_hdr) + + sizeof(struct dhcpv6_opt_id_assoc_na) + i); + + len = NET_TO_HOST16(opt->hdr.length); + switch (NET_TO_HOST16(opt->hdr.type)) { + case DHCPV6_OPT_IAADDR: + if (len > + (sizeof(struct dhcpv6_opt_hdr) + + sizeof(struct dhcpv6_opt_iaa_addr))) { + struct dhcpv6_option *in_opt; + + in_opt = (struct dhcpv6_option *)((u8_t *)opt + + sizeof(struct dhcpv6_opt_hdr) + + sizeof(struct dhcpv6_opt_iaa_addr)); + if (in_opt->hdr.type == + HOST_TO_NET16(DHCPV6_OPT_STATUS_CODE)) { + /* This entry has error! */ + if (in_opt->type.sts.status != 0) + break; + } + } + LOG_INFO("DHCPv6: Got IP Addr"); + /* Status is OK, let's add this addr to our address + list */ + ipv6_add_prefix_entry(context->ipv6_context, + &opt->type.iaa_addr.addr, 64); + + /* Add multicast address for this address */ + ipv6_add_solit_node_address(context-> + ipv6_context, + &opt->type.iaa_addr.addr); + addr_cnt++; + break; + + default: + break; + } + + i += len + sizeof(struct dhcpv6_opt_hdr); + } + + return addr_cnt; +} + +static void dhcpv6_process_opt_dns_servers(struct dhcpv6_context *context, + struct dhcpv6_opt_hdr *opt_hdr) +{ + int opt_len; + + opt_len = NET_TO_HOST16(opt_hdr->length); + + if (opt_len >= sizeof(struct ipv6_addr)) + memcpy((char *)&context->primary_dns_server, + (char *)&((struct dhcpv6_option *)opt_hdr)->type.dns. + primary_addr, sizeof(struct ipv6_addr)); + + if (opt_len >= 2 * sizeof(struct ipv6_addr)) + memcpy((char *)&context->secondary_dns_server, + (char *)&((struct dhcpv6_option *)opt_hdr)->type.dns. + secondary_addr, sizeof(struct ipv6_addr)); +} + +static void dhcpv6_handle_reply(struct dhcpv6_context *context, + u16_t dhcpv6_len) +{ + if (context->dhcpv6_state != DHCPV6_STATE_REQ_SENT) + return; + + context->dhcpv6_done = TRUE; +} + +static void dhcpv6_parse_vendor_option(struct dhcpv6_context *context, + u8_t *option, int len) +{ + struct dhcpv6_option *opt; + u16_t type; + int opt_len; + int data_len; + int i; + u8_t *data; + + for (i = 0; i < len; i += opt_len + sizeof(struct dhcpv6_opt_hdr)) { + opt = (struct dhcpv6_option *)((u8_t *)option + i); + type = HOST_TO_NET16(opt->hdr.type); + opt_len = HOST_TO_NET16(opt->hdr.length); + data = &opt->type.data[0]; + data_len = strlen((char *)data); + + switch (type) { + case 201: + /* iSCSI target 1 */ + break; + + case 202: + /* iSCSI target 2 */ + break; + + case 203: + if (data_len > ISCSI_MAX_ISCSI_NAME_LENGTH) + data_len = ISCSI_MAX_ISCSI_NAME_LENGTH; + data[data_len] = '\0'; + strcpy(context->initiatorName, (char *)data); + break; + + default: + break; + } + } +} diff --git a/iscsiuio/src/apps/dhcpc/dhcpv6.h b/iscsiuio/src/apps/dhcpc/dhcpv6.h new file mode 100644 index 0000000..d4ec4b9 --- /dev/null +++ b/iscsiuio/src/apps/dhcpc/dhcpv6.h @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Eddie Wai + * Based on code from Kevin Tran's iSCSI boot code + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * dhcpv6.h - DHCPv6 engine header + * + */ +#ifndef __IDHCPV6_H__ +#define __IDHCPV6_H__ + +#include "ipv6_ndpc.h" +#include "ipv6.h" + +#define ISCSI_MAX_ISCSI_NAME_LENGTH 128 +/* DHCPv6 Message types. */ +#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_INFO_REQUEST 11 +#define DHCPV6_RELAY_FORW 12 +#define DHCPV6_RELAY_REPL 13 + +/* Option codes. */ +#define DHCPV6_OPT_CLIENTID 1 /* Client ID option - built by stack */ +#define DHCPV6_OPT_SERVERID 2 /* Server ID option - built by stack */ +#define DHCPV6_OPT_IA_NA 3 /* IA_NA option - built by user */ +#define DHCPV6_OPT_IA_TA 4 /* IA_TA option - not supported */ +#define DHCPV6_OPT_IAADDR 5 /* IA_ADDR option - built by user */ +#define DHCPV6_OPT_ORO 6 /* Option Request Option - built by + stack */ +#define DHCPV6_OPT_PREFERENCE 7 /* Preference option - built by server + */ +#define DHCPV6_OPT_ELAPSED_TIME 8 /* Elapsed Time option - built by stack + */ +#define DHCPV6_OPT_RELAY_MSG 9 /* Relay Message option - not supported + */ +#define DHCPV6_OPT_AUTH 11 /* Authentication option - built by + stack */ +#define DHCPV6_OPT_UNICAST 12 /* Server Unicast option - built by + server */ +#define DHCPV6_OPT_STATUS_CODE 13 /* Status Code option - built by stack + */ +#define DHCPV6_OPT_RAPID_COMMIT 14 /* Rapid Commit option - built by user + */ +#define DHCPV6_OPT_USER_CLASS 15 /* User Class option - built by user */ +#define DHCPV6_OPT_VENDOR_CLASS 16 /* Vendor Class option - built by user + */ +#define DHCPV6_OPT_VENDOR_OPTS 17 /* Vendor-Specific Information option - + build by user */ +#define DHCPV6_OPT_INTERFACE_ID 18 /* Interface ID option - not supported + */ +#define DHCPV6_OPT_RECONF_MSG 19 /* Reconfigure Message option - built + by server */ +#define DHCPV6_OPT_RECONF_ACCEPT 20 /* Reconfigure Accept option - built by + user */ +#define DHCPV6_OPT_SIP_SERVER_D 21 /* NOT SUPPORTED - included for + completeness only */ +#define DHCPV6_OPT_SIP_SERVER_A 22 /* NOT SUPPORTED - included for + completeness only */ +#define DHCPV6_OPT_DNS_SERVERS 23 /* DNS Recursive Name Server option - + built by server */ +#define DHCPV6_OPT_DOMAIN_LIST 24 /* Domain Search List option - not + supported */ +#define DHCPV6_MAX_OPT_CODES 25 /* This will be the count + 1 since + the parsing array starts + at [1] instead of [0] */ + +/* Authentication protocol types. */ +#define DHCPV6_DELAYED_AUTH_PROT 2 /* Delayed Authentication protocol. */ +#define DHCPV6_RECON_KEY_AUTH_PROT 3 /* Reconfigure Key Authentication + protocol. */ + +struct dhcpv6_context { +#define DHCP_VENDOR_ID_LEN 128 + char dhcp_vendor_id[DHCP_VENDOR_ID_LEN]; + struct mac_address *our_mac_addr; + u32_t dhcpv6_transaction_id; + u16_t seconds; + int timeout; + int dhcpv6_done; + +#define DHCPV6_STATE_UNKNOWN 0 +#define DHCPV6_STATE_SOLICIT_SENT 1 +#define DHCPV6_STATE_ADV_RCVD 2 +#define DHCPV6_STATE_REQ_SENT 3 +#define DHCPV6_STATE_CONFIRM_SENT 4 + int dhcpv6_state; + u16_t dhcpv6_task; + struct ipv6_context *ipv6_context; + struct eth_hdr *eth; + struct ipv6_hdr *ipv6; + struct udp_hdr *udp; + + char initiatorName[ISCSI_MAX_ISCSI_NAME_LENGTH]; + struct ipv6_addr dhcp_server; + struct ipv6_addr primary_dns_server; + struct ipv6_addr secondary_dns_server; + +}; + +union dhcpv6_hdr { + struct { + u32_t type:8; + u32_t trans_id:24; + } field; + + u32_t type_transaction; +}; + +#define dhcpv6_type field.type +#define dhcpv6_trans_id field.trans_id + +struct dhcpv6_opt_hdr { + u16_t type; + u16_t length; +}; + +struct dhcpv6_opt_client_id { + u16_t duid_type; +#define DHCPV6_DUID_TYPE_LINK_LAYER_AND_TIME 1 +#define DHCPV6_DUID_TYPE_VENDOR_BASED 2 +#define DHCPV6_DUID_TYPE_LINK_LAYER 3 + u16_t hw_type; +#define DHCPV6_HW_TYPE_ETHERNET 1 + u32_t time; + struct mac_address link_layer_addr; +}; + +struct dhcpv6_opt_id_assoc_na { + u32_t iaid; +#define DHCPV6_OPT_IA_NA_IAID 0x306373L + u32_t t1; + u32_t t2; +}; + +struct dhcpv6_opt_elapse_time { + u16_t time; +}; + +struct dhcpv6_opt_iaa_addr { + struct ipv6_addr addr; + u32_t preferred_lifetime; + u32_t valid_lifetime; +}; + +struct dhcpv6_opt_status { + u16_t status; +}; + +struct dhcpv6_opt_request_list { + u16_t request_code[1]; +}; + +struct dhcpv6_opt_dns { + struct ipv6_addr primary_addr; + struct ipv6_addr secondary_addr; +}; + +struct dhcpv6_vendor_class { + u32_t enterprise_number; + u16_t vendor_class_length; + u8_t vendor_class_data[1]; +}; + +struct dhcpv6_vendor_opts { + u32_t enterprise_number; + u8_t vendor_opt_data[1]; +}; + +struct dhcpv6_option { + struct dhcpv6_opt_hdr hdr; + union { + struct dhcpv6_vendor_opts vendor_opts; + struct dhcpv6_vendor_class vendor_class; + struct dhcpv6_opt_client_id client_id; + struct dhcpv6_opt_id_assoc_na ida_na; + struct dhcpv6_opt_elapse_time elapsed_time; + struct dhcpv6_opt_iaa_addr iaa_addr; + struct dhcpv6_opt_status sts; + struct dhcpv6_opt_request_list list; + struct dhcpv6_opt_dns dns; + u8_t data[1]; + } type; +}; + +#define DHCPV6_NUM_OF_RETRY 4 + +#define DHCPV6_ACK_TIMEOUT 2 + +#define IANA_ENTERPRISE_NUM_BROADCOM 0x113d + +/* QLogic Extended DHCP options used in iSCSI boot */ +#define DHCPV6_TAG_FIRST_ISCSI_TARGET_NAME 201 +#define DHCPV6_TAG_SECOND_ISCSI_TARGET_NAME 202 +#define DHCPV6_TAG_ISCSI_INITIATOR_NAME 203 + +#define MAX_DHCP_RX_OFFERS 4 +#define MAX_DHCP_OPTION43_LENGTH 1024 + +#define DHCPV6_TASK_GET_IP_ADDRESS 0x1 +#define DHCPV6_TASK_GET_OTHER_PARAMS 0x2 + +enum { + ISCSI_FAILURE, + ISCSI_USER_ABORT, + ISCSI_SUCCESS +}; + +/* Function prototypes */ +int dhcpv6_do_discovery(struct dhcpv6_context *context); +void ipv6_udp_handle_dhcp(struct dhcpv6_context *context); +void dhcpv6_init(struct dhcpv6_context *context); + +#endif /* __IDHCPV6_H__ */ diff --git a/iscsiuio/src/uip-1.0-changelog.txt b/iscsiuio/src/uip-1.0-changelog.txt new file mode 100644 index 0000000..800e444 --- /dev/null +++ b/iscsiuio/src/uip-1.0-changelog.txt @@ -0,0 +1,98 @@ +* A new API: protosockets that are similar to BSD sockets but does not + require any underlying multithreading system. + +* Very rudimentary IPv6 support + +* New application: DHCP client. Web server rewritten with protosockets. + +* Removed uIP zero-copy functionality in order to simplify uIP device + driver coding: outbound packets are now *always* stored in full in + the uip_buf buffer. + +* Checksum computation is now part of uip.c, but it still is possible + to implement them in assembly code by specifying a configuration + option. Checksum code now runs on architectures with 2-byte alignment. + +* Added TCP persistent timer. + +* Made all IP address representations use the new uip_ipaddr_ip + datatype for clarity. + +* Updated window behavior so that sending to a host with a small open + window works better now. + +* UDP API change: uip_udp_new() now takes port numbers in network byte + order like TCP functions. + +* Allow reception of packets when no IP address is configured to make + DHCP work. + +* Moved Ethernet address into main uIP module from ARP module. + +* Made constants explicit #defines and moved them out of the code + (header sizes, TCP options, TCP header length field). + +* If uip_len is less than that reported by the IP header, the packet + is discarded. If uip_len is greater than the length reported by the + IP header, uip_len is adjusted. + +* Moved header size definitions into header file. + +* Added uIP call for polling an application without triggering any + timer events. Removed redundant assignments of uip_len and uip_slen. + +* Removed compiler warning about icmp_input label being defined when + UIP_PINGADDRCONF was not used. + +* Added UIP_APPDATA_SIZE macro that holds the available buffer size + for user data. + +* Added uip_udp_bind() call. + +* Moved checksum code into main uIP module. + +* Switched the TCP, UDP and IP header structures to be structs rather + than typedefs. + +* Prefixed TCP state names with UIP_ to avoid name space + contamination. + +* Changed declarations of uip_appdatap and friends to void * to avoid + explicit typecasts. + +* Bugfixes + + o TCP: Fixed bug with high byte of peer window size. + + o TCP: Fixed bug that in some cases prevented concurrent reception and + transmission of TCP data. + + o TCP: uip_connect() didn't correctly calculate age of TIME_WAIT + connections. + + o TCP: Array index for uip_conns[] array was out of bounds in + comparison. Comparison changed to make index within bounds. + + o TCP: if the remote host crashes and tries to reestablish an old + connection, uIP should respond with an ACK with the correct + sequence and acknowledgment numbers, to which the remote host + should respond with an ACK. uIP did not respond with the correct + ACK. + + o TCP: Fixed check for SYNACK segment: now checks only relevant TCP + control flags and discards flags reserved for future expansion. + + o TCP: Fixed bug where uIP did not inform application that a connection + had been aborted during an active open. + + o TCP: FIN segment was accepted even though application had stopped + incoming data with uip_stop(). + + o TCP: A FINACK segment would not always correctly acknowledge data. + + o UDP: checksums are now calculated after all fields have been + filled in. + + o UDP: network byte order on lastport in uip_udp_new(). + + o IP: memset() bugs in IP fragment reassembly code fixed. diff --git a/iscsiuio/src/uip/Makefile.am b/iscsiuio/src/uip/Makefile.am new file mode 100644 index 0000000..16170d7 --- /dev/null +++ b/iscsiuio/src/uip/Makefile.am @@ -0,0 +1,18 @@ +AM_CFLAGS = -I${top_srcdir}/src/unix \ + -I${top_srcdir}/src/apps/dhcpc \ + -I${top_srcdir}/src/apps/brcm-iscsi \ + -I${top_srcdir}/../include \ + -I${top_srcdir}/../usr + +noinst_LIBRARIES = lib_iscsi_uip.a + +lib_iscsi_uip_a_SOURCES = uip.c \ + uip_arp.c \ + psock.c \ + timer.c \ + uip-neighbor.c \ + uip_eth.c \ + ipv6_ndpc.c \ + ipv6.c + +lib_iscsi_uip_a_CFLAGS = -DBYTE_ORDER=@ENDIAN@ $(AM_CFLAGS) diff --git a/iscsiuio/src/uip/Makefile.include b/iscsiuio/src/uip/Makefile.include new file mode 100644 index 0000000..aa3ac63 --- /dev/null +++ b/iscsiuio/src/uip/Makefile.include @@ -0,0 +1,47 @@ + + +ifdef APPS + APPDIRS = $(foreach APP, $(APPS), ../apps/$(APP)) + -include $(foreach APP, $(APPS), ../apps/$(APP)/Makefile.$(APP)) + CFLAGS += $(addprefix -I../apps/,$(APPS)) +endif + +ifndef CCDEP + CCDEP = $(CC) +endif +ifndef CCDEPCFLAGS + CCDEPCFLAGS = $(CFLAGS) +endif +ifndef OBJECTDIR + OBJECTDIR = obj +endif + +ifeq (${wildcard $(OBJECTDIR)},) + DUMMY := ${shell mkdir $(OBJECTDIR)} +endif + + +vpath %.c . ../uip ../lib $(APPDIRS) + +$(OBJECTDIR)/%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +$(OBJECTDIR)/%.d: %.c + @set -e; rm -f $@; \ + $(CCDEP) -MM $(CCDEPCFLAGS) $< > $@.$$$$; \ + sed 's,\($*\)\.o[ :]*,$(OBJECTDIR)/\1.o $@ : ,g' < $@.$$$$ > $@; \ + rm -f $@.$$$$ + +UIP_SOURCES=uip.c uip_arp.c uiplib.c psock.c timer.c uip-neighbor.c uip_eth.c ipv6_ndp.c ipv6.c + + +ifneq ($(MAKECMDGOALS),clean) +-include $(addprefix $(OBJECTDIR)/,$(UIP_SOURCES:.c=.d) \ + $(APP_SOURCES:.c=.d)) +endif + +libuip.a: ${addprefix $(OBJECTDIR)/, $(UIP_SOURCES:.c=.o)} + $(AR) rc $@ $^ + +libapps.a: ${addprefix $(OBJECTDIR)/, $(APP_SOURCES:.c=.o)} + $(AR) rc $@ $^ diff --git a/iscsiuio/src/uip/clock.h b/iscsiuio/src/uip/clock.h new file mode 100644 index 0000000..d79326b --- /dev/null +++ b/iscsiuio/src/uip/clock.h @@ -0,0 +1,87 @@ +/** + * \defgroup clock Clock interface + * + * The clock interface is the interface between the \ref timer "timer library" + * and the platform specific clock functionality. The clock + * interface must be implemented for each platform that uses the \ref + * timer "timer library". + * + * The clock interface does only one this: it measures time. The clock + * interface provides a macro, CLOCK_SECOND, which corresponds to one + * second of system time. + * + * \sa \ref timer "Timer library" + * + * @{ + */ + +/* + * Copyright (c) 2004, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack + * + * Author: Adam Dunkels + * + */ +#ifndef __CLOCK_H__ +#define __CLOCK_H__ + +#include "clock-arch.h" + +/** + * Initialize the clock library. + * + * This function initializes the clock library and should be called + * from the main() function of the system. + * + */ +void clock_init(void); + +/** + * Get the current clock time. + * + * This function returns the current system clock time. + * + * \return The current clock time, measured in system ticks. + */ +clock_time_t clock_time(void); + +/** + * A second, measured in system clock time. + * + * \hideinitializer + */ +#ifdef CLOCK_CONF_SECOND +#define CLOCK_SECOND CLOCK_CONF_SECOND +#else +#define CLOCK_SECOND (clock_time_t)32 +#endif + +#endif /* __CLOCK_H__ */ + +/** @} */ diff --git a/iscsiuio/src/uip/debug.h b/iscsiuio/src/uip/debug.h new file mode 100644 index 0000000..a58fa7a --- /dev/null +++ b/iscsiuio/src/uip/debug.h @@ -0,0 +1,13 @@ +#ifndef __DEBUG_H__ +#define __DEBUG_H__ + +#ifdef DEBUG +#define UIP_DEBUG(args...) \ + do { \ + fprintf(stdout, args); \ + fflush(stdout); \ + } while (0); +#else +#endif + +#endif diff --git a/iscsiuio/src/uip/icmpv6.h b/iscsiuio/src/uip/icmpv6.h new file mode 100644 index 0000000..cbf9aa8 --- /dev/null +++ b/iscsiuio/src/uip/icmpv6.h @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Eddie Wai (eddie.wai@broadcom.com) + * Based on Kevin Tran's iSCSI boot code + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * icmpv6.h - This file contains macro definitions pertaining to ICMPv6 + * + * RFC 2463 : ICMPv6 Specification + * RFC 2461 : Neighbor Discovery for IPv6 + * + */ +#ifndef __ICMPV6_H__ +#define __ICMPV6_H__ + +/* Base ICMP Header sizes */ +#define IPV6_RTR_SOL_HDR_SIZE 8 +#define IPV6_RTR_ADV_HDR_SIZE 16 +#define IPV6_NEIGH_SOL_HDR_SIZE 24 +#define IPV6_NEIGH_ADV_HDR_SIZE 24 +#define IPV6_LINK_LAYER_OPT_SIZE 2 +#define IPV6_LINK_LAYER_OPT_LENGTH 8 +#define IPV6_MTU_OPT_SIZE 8 +#define IPV6_PREFIX_OPT_SIZE 32 +#define IPV6_ECHO_REQUEST_HDR_SIZE 8 +#define IPV6_ECHO_REPLY_HDR_SIZE 8 +#define IPV6_REDIRECT_SIZE 40 +#define IPV6_DHAAD_REQ_HDR_SIZE 8 +#define IPV6_DHAAD_REPLY_HDR_SIZE 8 +#define IPV6_PRFXSOL_HDR_SIZE 8 +#define IPV6_PRFXADV_HDR_SIZE 8 +#define IPV6_RTR_ADV_INT_OPT_SIZE 8 + +/* ICMP Message Types */ +/* Error messages are always less than 128 */ +#define ICMPV6_DST_UNREACH 1 /* Destination Unreachable */ +#define ICMPV6_PACKET_TOO_BIG 2 /* Packet Too Big */ +#define ICMPV6_TIME_EXCEEDED 3 /* Time Exceeded */ +#define ICMPV6_PARAM_PROB 4 /* Parameter Problem */ + +#define ICMPV6_RTR_SOL 133 /* Router Solicitation */ +#define ICMPV6_RTR_ADV 134 /* Router Advertisement */ +#define ICMPV6_NEIGH_SOL 135 /* Neighbor Solicitation */ +#define ICMPV6_NEIGH_ADV 136 /* Neighbor Advertisement */ +#define ICMPV6_REDIRECT 137 /* Redirect */ +#define ICMPV6_ECHO_REQUEST 128 /* Echo Request */ +#define ICMPV6_ECHO_REPLY 129 /* Echo Reply */ +#define ICMPV6_WRUREQUEST 139 /* Who Are You Request */ +#define ICMPV6_WRUREPLY 140 /* Who Are You Reply */ +#define ICMPV6_ROUTER_RENUMBERING 138 /* Router Renumbering */ +#define ICMPV6_HA_ADDR_DISC_REQ 144 /* Dynamic Home Agent Address + Discovery Request */ +#define ICMPV6_HA_ADDR_DISC_REPLY 145 /* Dynamic Home Agent Address + Discovery Reply */ +#define ICMPV6_MP_SOLICIT 146 /* Mobile Prefix Solicitation */ +#define ICMPV6_MP_ADV 147 /* Mobile Prefix Reply */ + +/* Destination Unreachable Codes */ +#define ICMPV6_DST_UNREACH_NOROUTE 0 +#define ICMPV6_DST_UNREACH_ADMIN 1 +#define ICMPV6_DST_UNREACH_ADDRESS 3 +#define ICMPV6_DST_UNREACH_PORT 4 + +/* Time Exceeded Codes */ +#define ICMPV6_TIME_EXCD_HPLMT 0 /* Hop Limit exceeded in transit */ +#define ICMPV6_TIME_EXCD_REASM 1 /* Fragment reassembly time exceeded */ + +/* Parameter Problem Codes */ +#define ICMPV6_PARM_PROB_HEADER 0 +#define ICMPV6_PARM_PROB_NEXT_HDR 1 +#define ICMPV6_PARM_PROB_OPTION 2 + +/* ICMP Option Types */ +#define IPV6_ICMP_OPTION_SRC_ADDR 1 /* Source Link-Layer Address */ +#define IPV6_ICMP_OPTION_TAR_ADDR 2 /* Target Link-Layer Address */ +#define IPV6_ICMP_OPTION_PREFIX 3 /* Prefix */ +#define IPV6_ICMP_OPTION_RED_HDR 4 /* Redirect Header */ +#define IPV6_ICMP_OPTION_MTU 5 /* Link MTU */ +#define IPV6_ICMP_OPTION_RTR_ADV_INT 7 /* Rtr Advertisement Interval */ + +/* ICMP Offsets */ +#define IPV6_ICMP_TYPE_OFFSET 0 +#define IPV6_ICMP_CODE_OFFSET 1 +#define IPV6_ICMP_CKSUM_OFFSET 2 +#define IPV6_ICMP_RESERVED_OFFSET 4 +#define IPV6_ICMP_DATA_OFFSET 8 + +/* ICMP Router Solicitation Offsets */ +#define IPV6_ICMP_RTR_SOL_RES_OFFSET 4 +#define IPV6_ICMP_RTR_SOL_OPTIONS_OFFSET 8 + +/* ICMP Router Advertisement Offsets */ +#define IPV6_ICMP_RTR_ADV_CURHOPLMT_OFFSET 4 +#define IPV6_ICMP_RTR_ADV_MGDANDCFG_BIT_OFFSET 5 +#define IPV6_ICMP_RTR_ADV_RTR_LIFETIME_OFFSET 6 +#define IPV6_ICMP_RTR_ADV_RCHBL_TIME_OFFSET 8 +#define IPV6_ICMP_RTR_ADV_RTRNS_TMR_OFFSET 12 +#define IPV6_ICMP_RTR_ADV_OPTIONS_OFFSET 16 + +/* ICMP Neighbor Solicitation Offsets */ +#define IPV6_ICMP_NEIGH_SOL_RES_OFFSET 4 +#define IPV6_ICMP_NEIGH_SOL_TRGT_ADDRS_OFFSET 8 +#define IPV6_ICMP_NEIGH_SOL_OPTIONS_OFFSET 24 + +/* ICMP Neighbor Advertisement Offsets */ +#define IPV6_ICMP_NEIGH_ADV_FLAG_OFFSET 4 +#define IPV6_ICMP_NEIGH_ADV_TRGT_ADDRS_OFFSET 8 +#define IPV6_ICMP_NEIGH_ADV_OPTIONS_OFFSET 24 + +/* ICMP Redirect Offsets */ +#define IPV6_ICMP_REDIRECT_TRGT_ADDRS_OFFSET 8 +#define IPV6_ICMP_REDIRECT_DEST_ADDRS_OFFSET 24 +#define IPV6_ICMP_REDIRECT_OPTIONS_OFFSET 40 + +/* ICMP Option Offsets */ +#define IPV6_ICMP_OPTION_TYPE_OFFSET 0 +#define IPV6_ICMP_OPTION_LENGTH_OFFSET 1 + +/* ICMP Link-Layer Address Option Offsets */ +#define IPV6_ICMP_LL_OPTION_ADDRESS_OFFSET 2 + +/* ICMP Prefix Option Offsets */ +#define IPV6_ICMP_PREFIX_PRE_LENGTH_OFFSET 2 +#define IPV6_ICMP_PREFIX_FLAG_OFFSET 3 +#define IPV6_ICMP_PREFIX_VALID_LIFETIME_OFFSET 4 +#define IPV6_ICMP_PREFIX_PREF_LIFETIME_OFFSET 8 +#define IPV6_ICMP_PREFIX_RES2_OFFSET 12 +#define IPV6_ICMP_PREFIX_PREFIX_OFFSET 16 + +/* ICMP Redirected Header Option Offsets */ +#define IPV6_ICMP_RED_OPTION_TYPE_OFFSET 0 +#define IPV6_ICMP_RED_OPTION_LEN_OFFSET 1 +#define IPV6_ICMP_RED_OPTION_RES1_OFFSET 2 +#define IPV6_ICMP_RED_OPTION_RES2_OFFSET 4 +#define IPV6_ICMP_RED_OPTION_DATA_OFFSET 8 + +/* ICMP MTU Option Offsets */ +#define IPV6_ICMP_MTU_RESERVED_OFFSET 2 +#define IPV6_ICMP_MTU_OFFSET 4 + +/* ICMP Echo Request Offsets */ +#define IPV6_ICMP_ECHO_ID 4 +#define IPV6_ICMP_ECHO_SEQ 6 +#define IPV6_ICMP_ECHO_DATA 8 + +/* ICMP Destination Unreachable Offsets */ +#define IPV6_DST_UNREACH_UNUSED 4 +#define IPV6_DST_UNREACH_DATA 8 + +/* ICMP Parameter Problem Offsets */ +#define IPV6_PARAM_PROB_PTR 4 +#define IPV6_PARAM_PROT_DATA 8 + +/* ICMP Time Exceeded Offsets */ +#define IPV6_TIME_EXCEEDED_DATA 8 + +/* ICMP Packet Too Big Offsets */ +#define IPV6_PKT_TOO_BIG_MTU 4 +#define IPV6_PKT_TOO_BIG_DATA 8 + +/* Home Agent Address Discovery Request Header Offsets */ +#define ICMPV6_HA_ADDR_DISC_REQ_ID_OFFSET 4 +#define ICMPV6_HA_ADDR_DISC_REQ_RSVD_OFFSET 6 + +/* Home Agent Address Discovery Reply Header Offsets */ +#define ICMPV6_HA_ADDR_DISC_REPLY_ID_OFFSET 4 +#define ICMPV6_HA_ADDR_DISC_REPLY_RSVD_OFFSET 6 +#define ICMPV6_HA_ADDR_DISC_REPLY_HA_ADDR_OFFSET 8 + +/* Mobile Prefix Solicitation Header Offsets */ +#define ICMPV6_MP_SOLICIT_ID_OFFSET 4 +#define ICMPV6_MP_SOLICIT_RSVD_OFFSET 6 + +/* Mobile Prefix Advertisement Header Offsets */ +#define ICMPV6_MP_ADV_ID_OFFSET 4 +#define ICMPV6_MP_ADV_MGDANDCFG_BIT_OFFSET 6 +#define ICMPV6_MP_ADV_OPT_OFFSET 8 + +/* Advertisement Interval Option Header Offsets */ +#define ICMPV6_ADV_INT_TYPE_OFFSET 0 +#define ICMPV6_ADV_INT_LEN_OFFSET 1 +#define ICMPV6_ADV_INT_RSVD_OFFSET 2 +#define ICMPV6_ADV_INT_ADV_INT_OFFSET 4 + +#define ICMPV6_HEADER_LEN 4 + +#define IPV6_PREFIX_FLAG_ONLINK 0x80 +#define IPV6_PREFIX_FLAG_AUTO 0x40 +#define IPV6_PREFIX_FLAG_ROUTER 0x20 + +#define IPV6_NA_FLAG_ROUTER 0x80 +#define IPV6_NA_FLAG_SOLICITED 0x40 +#define IPV6_NA_FLAG_OVERRIDE 0x20 + +/* Router Advertisement Flags */ +#define IPV6_RA_MANAGED_FLAG 0x80 +#define IPV6_RA_CONFIG_FLAG 0x40 + +/* Mobile Prefix Advertisement Flags */ +#define IPV6_PA_MANAGED_FLAG 0x80 +#define IPV6_PA_CONFIG_FLAG 0x40 + +/* Validation Values */ +#define ICMPV6_VALID_HOP_LIMIT 255 /* Valid Hop Limit */ +#define ICMPV6_VALID_CODE 0 /* Valid Code */ +#define ICMPV6_RTRSOL_MIN_LENGTH 8 /* Minimum valid length for + Router Solicitation */ +#define ICMPV6_RTRADV_MIN_LENGTH 16 /* Minimum valid length for + Router Advertisement */ +#define ICMPV6_NEIGHSOL_MIN_LENGTH 24 /* Minimum valid length for + Neighbor Solicitation */ +#define ICMPV6_NEIGHADV_MIN_LENGTH 24 /* Minimum valid length for + Neighbor Advertisement */ +#define ICMPV6_REDIRECT_MIN_LENGTH 40 /* Minimum valid length for + Neighbor Advertisement */ + +/* ICMPV6 Header */ +struct icmpv6_hdr { + u8_t icmpv6_type; /* type field */ + u8_t icmpv6_code; /* code field */ + u16_t icmpv6_cksum; /* checksum field */ + union { + u32_t icmpv6_un_data32[1]; /* type-specific field */ + u16_t icmpv6_un_data16[2]; /* type-specific field */ + u8_t icmpv6_un_data8[4]; /* type-specific field */ + } data; +}; + +#define icmpv6_data data.icmpv6_un_data32[0] + +struct icmpv6_opt_hdr { + u8_t type; + u8_t len; +}; + +struct icmpv6_opt_link_addr { + struct icmpv6_opt_hdr hdr; + u8_t link_addr[6]; +}; + +struct icmpv6_opt_prefix { + struct icmpv6_opt_hdr hdr; + u8_t prefix_len; + u8_t flags; +#define ICMPV6_OPT_PREFIX_FLAG_ON_LINK (1 << 7) +#define ICMPV6_OPT_PREFIX_FLAG_BIT_A (1 << 6) + u32_t valid_lifetime; + u32_t preferred_lifetime; + u32_t reserved; + struct ipv6_addr prefix; +}; + +/* Neighbor Solicitation */ +struct icmpv6_nd_solicit { + struct icmpv6_hdr nd_ns_hdr; +}; + +/* Router Advertisement */ +struct icmpv6_router_advert { + struct icmpv6_hdr header; + u32_t reachable_time; + u32_t retransmit_timer; +}; + +#define nd_ra_type header.icmpv6_type +#define nd_ra_code header.icmpv6_code +#define nd_ra_cksum header.icmpv6_cksum +#define nd_ra_curhoplimit header.data.icmpv6_un_data8[0] +#define nd_ra_flags_reserved header.data.icmpv6_un_data8[1] +#define nd_ra_router_lifetime header.data.icmpv6_un_data16[1] + +#endif /* __ICMPV6_H__ */ diff --git a/iscsiuio/src/uip/ipv6.c b/iscsiuio/src/uip/ipv6.c new file mode 100644 index 0000000..5710199 --- /dev/null +++ b/iscsiuio/src/uip/ipv6.c @@ -0,0 +1,1306 @@ +/* + * Copyright (c) 2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Eddie Wai (eddie.wai@broadcom.com) + * Based on Kevin Tran's iSCSI boot code + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ipv6.c - This file contains simplifed IPv6 processing code. + * + */ +#include +#include +#include +#include "logger.h" +#include "uip.h" +#include "ipv6.h" +#include "ipv6_pkt.h" +#include "icmpv6.h" +#include "uipopt.h" +#include "dhcpv6.h" +#include "ping.h" + +static inline int best_match_bufcmp(u8_t *a, u8_t *b, int len) +{ + int i; + + for (i = 0; i < len; i++) { + if (a[i] != b[i]) + break; + } + return i; +} + +/* Local function prototypes */ +static int ipv6_is_it_our_address(struct ipv6_context *context, + struct ipv6_addr *ip_addr); +static void ipv6_insert_protocol_chksum(struct ipv6_hdr *ipv6); +static void ipv6_update_arp_table(struct ipv6_context *context, + struct ipv6_addr *ip_addr, + struct mac_address *mac_addr); +static void ipv6_icmp_init_link_option(struct ipv6_context *context, + struct icmpv6_opt_link_addr *link_opt, + u8_t type); +static void ipv6_icmp_rx(struct ipv6_context *context); +static void ipv6_icmp_handle_nd_adv(struct ipv6_context *context); +static void ipv6_icmp_handle_nd_sol(struct ipv6_context *context); +static void ipv6_icmp_handle_echo_request(struct ipv6_context *context); +static void ipv6_icmp_handle_router_adv(struct ipv6_context *context); +static void ipv6_icmp_process_prefix(struct ipv6_context *context, + struct icmpv6_opt_prefix *icmp_prefix); +static void ipv6_udp_rx(struct ipv6_context *context); + +int iscsiL2Send(struct ipv6_context *context, int pkt_len) +{ + LOG_DEBUG("IPv6: iscsiL2Send"); + uip_send(context->ustack, + (void *)context->ustack->data_link_layer, pkt_len); + + return pkt_len; +} + +int iscsiL2AddMcAddr(struct ipv6_context *context, + struct mac_address *new_mc_addr) +{ + int i; + struct mac_address *mc_addr; + const struct mac_address all_zeroes_mc = { { { 0, 0, 0, 0, 0, 0 } } }; + + mc_addr = context->mc_addr; + for (i = 0; i < MAX_MCADDR_TABLE; i++, mc_addr++) + if (!memcmp((char *)mc_addr, + (char *)new_mc_addr, sizeof(struct mac_address))) + return TRUE; /* Already in the mc table */ + + mc_addr = context->mc_addr; + for (i = 0; i < MAX_MCADDR_TABLE; i++, mc_addr++) { + if (!memcmp((char *)mc_addr, + (char *)&all_zeroes_mc, sizeof(struct mac_address))) { + memcpy((char *)mc_addr, + (char *)new_mc_addr, sizeof(struct mac_address)); + LOG_DEBUG("IPv6: mc_addr added " + "%02x:%02x:%02x:%02x:%02x:%02x", + *(u8_t *)new_mc_addr, + *((u8_t *)new_mc_addr + 1), + *((u8_t *)new_mc_addr + 2), + *((u8_t *)new_mc_addr + 3), + *((u8_t *)new_mc_addr + 4), + *((u8_t *)new_mc_addr + 5)); + return TRUE; + } + } + return FALSE; +} + +int iscsiL2IsOurMcAddr(struct ipv6_context *context, + struct mac_address *dest_mac) +{ + int i; + struct mac_address *mc_addr; + + mc_addr = context->mc_addr; + for (i = 0; i < MAX_MCADDR_TABLE; i++, mc_addr++) + if (!memcmp((char *)mc_addr, + (char *)dest_mac->addr, sizeof(struct mac_address))) + return TRUE; + return FALSE; +} + +void ipv6_init(struct ndpc_state *ndp, int cfg) +{ + int i; + struct ipv6_context *context = (struct ipv6_context *)ndp->ipv6_context; + struct mac_address *mac_addr = (struct mac_address *)ndp->mac_addr; + struct ipv6_arp_entry *ipv6_arp_table; + struct ipv6_prefix_entry *ipv6_prefix_table; + struct mac_address mc_addr; + + if (context == NULL) { + LOG_ERR("IPV6: INIT ipv6_context is NULL"); + return; + } + + memset((char *)context, 0, sizeof(struct ipv6_context)); + + /* Associate the nic_iface's ustack to this ipv6_context */ + context->ustack = ndp->ustack; + + ipv6_arp_table = &context->ipv6_arp_table[0]; + ipv6_prefix_table = &context->ipv6_prefix_table[0]; + + memset((char *)ipv6_arp_table, 0, sizeof(*ipv6_arp_table)); + memset((char *)ipv6_prefix_table, 0, sizeof(*ipv6_prefix_table)); + memcpy((char *)&context->mac_addr, + (char *)mac_addr, sizeof(struct mac_address)); + /* + * Per RFC 2373. + * There are two types of local-use unicast addresses defined. These + * are Link-Local and Site-Local. The Link-Local is for use on a single + * link and the Site-Local is for use in a single site. Link-Local + * addresses have the following format: + * + * | 10 | + * | bits | 54 bits | 64 bits | + * +----------+-------------------------+----------------------------+ + * |1111111010| 0 | interface ID | + * +----------+-------------------------+----------------------------+ + */ + if (context->ustack->linklocal_autocfg != IPV6_LL_AUTOCFG_OFF) { + context->link_local_addr.addr8[0] = 0xfe; + context->link_local_addr.addr8[1] = 0x80; + /* Bit 1 is 1 to indicate universal scope. */ + context->link_local_addr.addr8[8] = mac_addr->addr[0] | 0x2; + context->link_local_addr.addr8[9] = mac_addr->addr[1]; + context->link_local_addr.addr8[10] = mac_addr->addr[2]; + context->link_local_addr.addr8[11] = 0xff; + context->link_local_addr.addr8[12] = 0xfe; + context->link_local_addr.addr8[13] = mac_addr->addr[3]; + context->link_local_addr.addr8[14] = mac_addr->addr[4]; + context->link_local_addr.addr8[15] = mac_addr->addr[5]; + + context->link_local_multi.addr8[0] = 0xff; + context->link_local_multi.addr8[1] = 0x02; + context->link_local_multi.addr8[11] = 0x01; + context->link_local_multi.addr8[12] = 0xff; + context->link_local_multi.addr8[13] |= + context->link_local_addr.addr8[13]; + context->link_local_multi.addr16[7] = + context->link_local_addr.addr16[7]; + + /* Default Prefix length is 64 */ + /* Add Link local address to the head of the ipv6 address + list */ + ipv6_add_prefix_entry(context, + &context->link_local_addr, 64); + } + /* + * Convert Multicast IP address to Multicast MAC adress per + * RFC 2464: Transmission of IPv6 Packets over Ethernet Networks + * + * An IPv6 packet with a multicast destination address DST, consisting + * of the sixteen octets DST[1] through DST[16], is transmitted to the + * Ethernet multicast address whose first two octets are the value 3333 + * hexadecimal and whose last four octets are the last four octets of + * DST. + * + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |0 0 1 1 0 0 1 1|0 0 1 1 0 0 1 1| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | DST[13] | DST[14] | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | DST[15] | DST[16] | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * IPv6 requires the following Multicast IP addresses setup per node. + */ + for (i = 0; i < 3; i++) { + mc_addr.addr[0] = 0x33; + mc_addr.addr[1] = 0x33; + mc_addr.addr[2] = 0x0; + mc_addr.addr[3] = 0x0; + mc_addr.addr[4] = 0x0; + + switch (i) { + case 0: + /* All Nodes Multicast IPv6 address : ff02::1 */ + mc_addr.addr[5] = 0x1; + break; + + case 1: + /* All Host Multicast IPv6 address : ff02::3 */ + mc_addr.addr[5] = 0x3; + break; + + case 2: + /* Solicited Node Multicast Address: ff02::01:ffxx:yyzz + */ + mc_addr.addr[2] = 0xff; + mc_addr.addr[3] = mac_addr->addr[3]; + mc_addr.addr[4] = mac_addr->addr[4]; + mc_addr.addr[5] = mac_addr->addr[5]; + break; + + default: + break; + } + iscsiL2AddMcAddr(context, &mc_addr); + } + + /* Default HOP number */ + context->hop_limit = IPV6_HOP_LIMIT; +} + +int ipv6_add_prefix_entry(struct ipv6_context *context, + struct ipv6_addr *ip_addr, u8_t prefix_len) +{ + int i; + struct ipv6_prefix_entry *prefix_entry; + struct ipv6_prefix_entry *ipv6_prefix_table = + context->ipv6_prefix_table; + char addr_str[INET6_ADDRSTRLEN]; + + /* Check if there is an valid entry already. */ + for (i = 0; i < IPV6_NUM_OF_ADDRESS_ENTRY; i++) { + prefix_entry = &ipv6_prefix_table[i]; + + if (prefix_entry->prefix_len != 0) { + if (memcmp((char *)&prefix_entry->ip_addr, + (char *)ip_addr, + sizeof(struct ipv6_addr)) == 0) { + /* We already initialize on this interface. + There is nothing to do */ + return 0; + } + } + } + + /* Find an unused entry */ + for (i = 0; i < IPV6_NUM_OF_ADDRESS_ENTRY; i++) { + prefix_entry = &ipv6_prefix_table[i]; + + if (prefix_entry->prefix_len == 0) + break; + } + + if (prefix_entry->prefix_len != 0) + return -1; + + prefix_entry->prefix_len = prefix_len / 8; + + memcpy((char *)&prefix_entry->ip_addr, + (char *)ip_addr, sizeof(struct ipv6_addr)); + + inet_ntop(AF_INET6, &prefix_entry->ip_addr.addr8, addr_str, + sizeof(addr_str)); + + LOG_DEBUG("IPv6: add prefix IP addr %s", addr_str); + + /* Put it on the list on head of the list. */ + if (context->addr_list != NULL) + prefix_entry->next = context->addr_list; + else + prefix_entry->next = NULL; + + context->addr_list = prefix_entry; + + return 0; +} + +void ipv6_rx_packet(struct ipv6_context *context, u16_t len) +{ + struct ipv6_hdr *ipv6; + u16_t protocol; + + if (!context->ustack) { + LOG_WARN("ipv6 rx pkt ipv6_context = %p ustack = %p", context, + context->ustack); + return; + } + ipv6 = (struct ipv6_hdr *)context->ustack->network_layer; + /* Make sure it's an IPv6 packet */ + if ((ipv6->ipv6_version_fc & 0xf0) != IPV6_VERSION) { + /* It's not an IPv6 packet. Drop it. */ + LOG_WARN("IPv6 version 0x%x not IPv6", ipv6->ipv6_version_fc); + return; + } + protocol = ipv6_process_rx(ipv6); + + switch (protocol) { + case IPPROTO_ICMPV6: + ipv6_icmp_rx(context); + break; + + case IPPROTO_UDP: + /* Indicate to UDP processing code */ + ipv6_udp_rx(context); + break; + + default: + break; + } +} + +void ipv6_mc_init_dest_mac(struct eth_hdr *eth, struct ipv6_hdr *ipv6) +{ + int i; + /* + * Initialize address mapping of IPV6 Multicast to multicast MAC + * address per RFC 2464. + * + * An IPv6 packet with a multicast destination address DST, consisting + * of the sixteen octets DST[1] through DST[16], is transmitted to the + * Ethernet multicast address whose first two octets are the value 3333 + * hexadecimal and whose last four octets are the last four octets of + * DST. + * + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |0 0 1 1 0 0 1 1|0 0 1 1 0 0 1 1| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | DST[13] | DST[14] | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | DST[15] | DST[16] | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + eth->dest_mac.addr[0] = 0x33; + eth->dest_mac.addr[1] = 0x33; + for (i = 0; i < 4; i++) + eth->dest_mac.addr[2 + i] = ipv6->ipv6_dst.addr8[12 + i]; +} + +int ipv6_autoconfig(struct ipv6_context *context) +{ + return ipv6_discover_address(context); +} + +int ipv6_discover_address(struct ipv6_context *context) +{ + struct eth_hdr *eth = + (struct eth_hdr *)context->ustack->data_link_layer; + struct ipv6_hdr *ipv6 = + (struct ipv6_hdr *)context->ustack->network_layer; + struct icmpv6_hdr *icmp = (struct icmpv6_hdr *)((u8_t *)ipv6 + + sizeof(struct ipv6_hdr)); + int rc = 0; + + /* Retrieve tx buffer */ + if (eth == NULL || ipv6 == NULL) + return -EAGAIN; + + /* Setup IPv6 All Routers Multicast address : ff02::2 */ + memset((char *)&ipv6->ipv6_dst, 0, sizeof(struct ipv6_addr)); + ipv6->ipv6_dst.addr8[0] = 0xff; + ipv6->ipv6_dst.addr8[1] = 0x02; + ipv6->ipv6_dst.addr8[15] = 0x02; + ipv6->ipv6_hop_limit = 255; + + /* Initialize MAC header based on destination MAC address */ + ipv6_mc_init_dest_mac(eth, ipv6); + ipv6->ipv6_nxt_hdr = IPPROTO_ICMPV6; + + icmp->icmpv6_type = ICMPV6_RTR_SOL; + icmp->icmpv6_code = 0; + icmp->icmpv6_data = 0; + icmp->icmpv6_cksum = 0; + ipv6_icmp_init_link_option(context, + (struct icmpv6_opt_link_addr *)((u8_t *)icmp + + sizeof(struct icmpv6_hdr)), + IPV6_ICMP_OPTION_SRC_ADDR); + ipv6->ipv6_plen = HOST_TO_NET16((sizeof(struct icmpv6_hdr) + + sizeof(struct icmpv6_opt_link_addr))); + memcpy((char *)&ipv6->ipv6_src, + (char *)&context->link_local_addr, + sizeof(struct ipv6_addr)); + + icmp->icmpv6_cksum = 0; + LOG_DEBUG("IPv6: Send rtr sol"); + ipv6_send(context, (u8_t *) icmp - (u8_t *) eth + + sizeof(struct icmpv6_hdr) + + sizeof(struct icmpv6_opt_link_addr)); + return rc; +} + +u16_t ipv6_process_rx(struct ipv6_hdr *ipv6) +{ + return ipv6->ipv6_nxt_hdr; +} + +int ipv6_send(struct ipv6_context *context, u16_t packet_len) +{ + struct eth_hdr *eth = + (struct eth_hdr *)context->ustack->data_link_layer; + struct ipv6_hdr *ipv6 = + (struct ipv6_hdr *)context->ustack->network_layer; + + ipv6_setup_hdrs(context, eth, ipv6, packet_len); + + return iscsiL2Send(context, packet_len); +} + +void ipv6_send_udp_packet(struct ipv6_context *context, u16_t packet_len) +{ + struct eth_hdr *eth = + (struct eth_hdr *)context->ustack->data_link_layer; + struct ipv6_hdr *ipv6 = + (struct ipv6_hdr *)context->ustack->network_layer; + struct udp_hdr *udp = (struct udp_hdr *)((u8_t *)ipv6 + + sizeof(struct ipv6_hdr)); + + ipv6->ipv6_nxt_hdr = IPPROTO_UDP; + ipv6->ipv6_plen = + HOST_TO_NET16(packet_len - ((u8_t *)udp - (u8_t *)eth)); + + udp->chksum = 0; + + /* + * We only use UDP packet for DHCPv6. The source address is always + * link-local address. + */ + ipv6->ipv6_src.addr[0] = 0; + + /* Hop limit is always 1 for DHCPv6 packet. */ + ipv6->ipv6_hop_limit = 1; + + ipv6_send(context, packet_len); +} + +void ipv6_setup_hdrs(struct ipv6_context *context, struct eth_hdr *eth, + struct ipv6_hdr *ipv6, u16_t packet_len) +{ + struct ipv6_addr *our_address; + + /* VLAN will be taken cared of in the nic layer */ + eth->len_type = HOST_TO_NET16(LAYER2_TYPE_IPV6); + memcpy((char *)ð->src_mac, + (char *)&context->mac_addr, sizeof(struct mac_address)); + + /* Put the traffic class into the packet. */ + memset(&ipv6->ipv6_version_fc, 0, sizeof(u32_t)); + ipv6->ipv6_version_fc = IPV6_VERSION; + if (ipv6->ipv6_hop_limit == 0) + ipv6->ipv6_hop_limit = context->hop_limit; + + if (ipv6->ipv6_src.addr[0] == 0) { + /* Need to initialize source IP address. */ + our_address = ipv6_our_address(context); + if (our_address != NULL) { + /* Assume that caller has filled in the destination + IP address */ + memcpy((char *)&ipv6->ipv6_src, + (char *)our_address, sizeof(struct ipv6_addr)); + } + } + + ipv6_insert_protocol_chksum(ipv6); +} + +static void ipv6_insert_protocol_chksum(struct ipv6_hdr *ipv6) +{ + u32_t sum; + u16_t *ptr; + u16_t *protocol_data_ptr; + int i; + u16_t protocol_data_len; + u16_t checksum; + + /* + * This routine assumes that there is no extension header. This driver + * doesn't user extension header to keep driver small and simple. + * + * Pseudo check consists of the following: + * SRC IP, DST IP, Protocol Data Length, and Next Header. + */ + sum = 0; + ptr = (u16_t *)&ipv6->ipv6_src; + + for (i = 0; i < sizeof(struct ipv6_addr); i += 2) { + sum += HOST_TO_NET16(*ptr); + ptr++; + } + + /* Keep track where the layer header is */ + protocol_data_ptr = ptr; + + protocol_data_len = HOST_TO_NET16(ipv6->ipv6_plen); + sum += protocol_data_len; + sum += ipv6->ipv6_nxt_hdr; + /* Sum now contains sum of IPv6 pseudo header. Let's add the data + streams. */ + if (protocol_data_len & 1) { + /* Length of data is odd */ + *((u8_t *) ptr + protocol_data_len) = 0; + protocol_data_len++; + } + + for (i = 0; i < protocol_data_len / 2; i++) { + sum += HOST_TO_NET16(*ptr); + ptr++; + } + + sum = (sum >> 16) + (sum & 0xffff); + sum += (sum >> 16); + sum &= 0xffff; + checksum = (u16_t) (~sum); + checksum = HOST_TO_NET16(checksum); + + switch (ipv6->ipv6_nxt_hdr) { + case IPPROTO_ICMPV6: + /* Insert correct ICMPv6 checksum */ + ((struct icmpv6_hdr *)(protocol_data_ptr))->icmpv6_cksum = + checksum; + break; + case IPPROTO_UDP: + /* Insert correct UDP checksum */ + ((struct udp_hdr *)protocol_data_ptr)->chksum = checksum; + break; + default: + break; + } +} + +int ipv6_is_it_our_link_local_address(struct ipv6_context *context, + struct ipv6_addr *ip_addr) +{ + u8_t *test_addr = (u8_t *) ip_addr->addr8; + u8_t test_remainder; + + if (test_addr[0] != context->link_local_addr.addr8[0]) + return FALSE; + + test_remainder = (test_addr[1] & 0xC0) >> 6; + if (test_remainder != 2) + return FALSE; + + return TRUE; +} + +static int ipv6_is_it_our_address(struct ipv6_context *context, + struct ipv6_addr *ipv6_addr) +{ + struct ipv6_prefix_entry *ipv6_prefix; + + for (ipv6_prefix = context->addr_list; ipv6_prefix != NULL; + ipv6_prefix = ipv6_prefix->next) { + if (IPV6_ARE_ADDR_EQUAL(&ipv6_prefix->ip_addr, ipv6_addr)) + return TRUE; + } + + return FALSE; +} + +struct ipv6_addr *ipv6_our_address(struct ipv6_context *context) +{ + return &context->link_local_addr; +} + +int ipv6_ip_in_arp_table(struct ipv6_context *context, + struct ipv6_addr *ip_addr, + struct mac_address *mac_addr) +{ + struct ipv6_arp_entry *arp_entry; + int i; + + for (i = 0; i < UIP_ARPTAB_SIZE; i++) { + arp_entry = &context->ipv6_arp_table[i]; + + if (IPV6_ARE_ADDR_EQUAL(&arp_entry->ip_addr, ip_addr)) { + memcpy((char *)mac_addr, &arp_entry->mac_addr, + sizeof(struct mac_address)); + return 1; + } + } + return 0; +} + +struct ipv6_addr *ipv6_find_longest_match(struct ipv6_context *context, + struct ipv6_addr *ip_addr) +{ + struct ipv6_prefix_entry *ipv6_prefix; + struct ipv6_prefix_entry *best_match = NULL; + int longest_len = -1; + int len; + + for (ipv6_prefix = context->addr_list; ipv6_prefix != NULL; + ipv6_prefix = ipv6_prefix->next) { + if (!IPV6_IS_ADDR_LINKLOCAL(&ipv6_prefix->ip_addr)) { + len = best_match_bufcmp((u8_t *)&ipv6_prefix->ip_addr, + (u8_t *)ip_addr, + sizeof(struct ipv6_addr)); + if (len > longest_len) { + best_match = ipv6_prefix; + longest_len = len; + } + } + } + + if (best_match) + return &best_match->ip_addr; + + return NULL; +} + +void ipv6_arp_out(struct ipv6_context *context, int *uip_len) +{ + /* Empty routine */ +} + + +static void ipv6_update_arp_table(struct ipv6_context *context, + struct ipv6_addr *ip_addr, + struct mac_address *mac_addr) +{ + struct ipv6_arp_entry *arp_entry; + int i; + struct ipv6_arp_entry *ipv6_arp_table = context->ipv6_arp_table; + + LOG_DEBUG("IPv6: Neighbor update"); + /* + * Walk through the ARP mapping table and try to find an entry to + * update. If none is found, the IP -> MAC address mapping is + * inserted in the ARP table. + */ + for (i = 0; i < UIP_ARPTAB_SIZE; i++) { + arp_entry = &ipv6_arp_table[i]; + + /* Only check those entries that are actually in use. */ + if (arp_entry->ip_addr.addr[0] != 0) { + /* + * Check if the source IP address of the incoming + * packet matches the IP address in this ARP table + * entry. + */ + if (IPV6_ARE_ADDR_EQUAL(&arp_entry->ip_addr, ip_addr)) { + /* An old entry found, update this and return */ + memcpy((char *)&arp_entry->mac_addr, + (char *)mac_addr, + sizeof(struct mac_address)); + arp_entry->time = context->arptime; + return; + } + } + } + + /* + * If we get here, no existing ARP table entry was found, so we + * create one. + * + * First, we try to find an unused entry in the ARP table. + */ + for (i = 0; i < UIP_ARPTAB_SIZE; i++) { + arp_entry = &ipv6_arp_table[i]; + + if (arp_entry->ip_addr.addr[0] == 0) + break; + } + + if (i == UIP_ARPTAB_SIZE) + return; + + /* Index j is the entry that is least used */ + arp_entry = &ipv6_arp_table[i]; + memcpy((char *)&arp_entry->ip_addr, (char *)ip_addr, + sizeof(struct ipv6_addr)); + memcpy((char *)&arp_entry->mac_addr, + (char *)mac_addr, sizeof(struct mac_address)); + + arp_entry->time = context->arptime; +} + +/* DestIP is intact */ +int ipv6_send_nd_solicited_packet(struct ipv6_context *context, + struct eth_hdr *eth, struct ipv6_hdr *ipv6) +{ + struct icmpv6_hdr *icmp; + int pkt_len = 0; + struct ipv6_addr *longest_match_addr; + char addr_str[INET6_ADDRSTRLEN]; + + ipv6->ipv6_nxt_hdr = IPPROTO_ICMPV6; + + /* Depending on the IPv6 address of the target, we'll need to determine + whether we use the assigned IPv6 address/RA or the link local address + */ + /* Use Link-local as source address */ + if (ipv6_is_it_our_link_local_address(context, &ipv6->ipv6_dst) == + TRUE) { + LOG_DEBUG("IPv6: NS using link local"); + memcpy((char *)&ipv6->ipv6_src, + (char *)&context->link_local_addr, + sizeof(struct ipv6_addr)); + } else { + longest_match_addr = + ipv6_find_longest_match(context, &ipv6->ipv6_dst); + if (longest_match_addr) { + LOG_DEBUG("IPv6: NS using longest match addr"); + memcpy((char *)&ipv6->ipv6_src, + (char *)longest_match_addr, + sizeof(struct ipv6_addr)); + } else { + LOG_DEBUG("IPv6: NS using link local instead"); + memcpy((char *)&ipv6->ipv6_src, + (char *)&context->link_local_addr, + sizeof(struct ipv6_addr)); + } + } + icmp = (struct icmpv6_hdr *)((u8_t *)ipv6 + sizeof(struct ipv6_hdr)); + + inet_ntop(AF_INET6, &ipv6->ipv6_src.addr8, addr_str, sizeof(addr_str)); + LOG_DEBUG("IPv6: NS host IP addr: %s", addr_str); + /* + * Destination IP address to be resolved is after the ICMPv6 + * header. + */ + memcpy((char *)((u8_t *)icmp + sizeof(struct icmpv6_hdr)), + (char *)&ipv6->ipv6_dst, sizeof(struct ipv6_addr)); + + /* + * Destination IP in the IPv6 header contains solicited-node multicast + * address corresponding to the target address. + * + * ff02::01:ffxx:yyzz. Where xyz are least + * significant of 24-bit MAC address. + */ + memset((char *)&ipv6->ipv6_dst, 0, sizeof(struct ipv6_addr) - 3); + ipv6->ipv6_dst.addr8[0] = 0xff; + ipv6->ipv6_dst.addr8[1] = 0x02; + ipv6->ipv6_dst.addr8[11] = 0x01; + ipv6->ipv6_dst.addr8[12] = 0xff; + ipv6_mc_init_dest_mac(eth, ipv6); + ipv6->ipv6_hop_limit = 255; + + icmp->icmpv6_type = ICMPV6_NEIGH_SOL; + icmp->icmpv6_code = 0; + icmp->icmpv6_data = 0; + icmp->icmpv6_cksum = 0; + ipv6_icmp_init_link_option(context, + (struct icmpv6_opt_link_addr *)((u8_t *)icmp + + sizeof(struct icmpv6_hdr) + + sizeof(struct ipv6_addr)), + IPV6_ICMP_OPTION_SRC_ADDR); + ipv6->ipv6_plen = HOST_TO_NET16((sizeof(struct icmpv6_hdr) + + sizeof(struct icmpv6_opt_link_addr) + + sizeof(struct ipv6_addr))); + /* Total packet size */ + pkt_len = (u8_t *) icmp - (u8_t *) eth + + sizeof(struct icmpv6_hdr) + + sizeof(struct icmpv6_opt_link_addr) + sizeof(struct ipv6_addr); + ipv6_setup_hdrs(context, eth, ipv6, pkt_len); + return pkt_len; +} + +static void ipv6_icmp_init_link_option(struct ipv6_context *context, + struct icmpv6_opt_link_addr *link_opt, + u8_t type) +{ + link_opt->hdr.type = type; + link_opt->hdr.len = sizeof(struct icmpv6_opt_link_addr) / 8; + memcpy((char *)&link_opt->link_addr, + (char *)&context->mac_addr, sizeof(struct mac_address)); +} + +static void ipv6_icmp_rx(struct ipv6_context *context) +{ + struct ipv6_hdr *ipv6 = + (struct ipv6_hdr *)context->ustack->network_layer; + struct icmpv6_hdr *icmp = (struct icmpv6_hdr *)((u8_t *)ipv6 + + sizeof(struct ipv6_hdr)); + uip_icmp_echo_hdr_t *icmp_echo_hdr = + (uip_icmp_echo_hdr_t *)((u8_t *)ipv6 + + sizeof(struct ipv6_hdr)); + + switch (icmp->icmpv6_type) { + case ICMPV6_RTR_ADV: + ipv6_icmp_handle_router_adv(context); + break; + + case ICMPV6_NEIGH_SOL: + ipv6_icmp_handle_nd_sol(context); + break; + + case ICMPV6_NEIGH_ADV: + ipv6_icmp_handle_nd_adv(context); + break; + + case ICMPV6_ECHO_REQUEST: + /* Response with ICMP reply */ + ipv6_icmp_handle_echo_request(context); + break; + + case ICMPV6_ECHO_REPLY: + /* Handle ICMP reply */ + process_icmp_packet(icmp_echo_hdr, context->ustack); + break; + + default: + break; + } +} + +static void ipv6_icmp_handle_router_adv(struct ipv6_context *context) +{ + struct ipv6_hdr *ipv6 = + (struct ipv6_hdr *)context->ustack->network_layer; + struct icmpv6_router_advert *icmp = + (struct icmpv6_router_advert *)((u8_t *)ipv6 + sizeof(struct ipv6_hdr)); + struct icmpv6_opt_hdr *icmp_opt; + u16_t opt_len; + u16_t len; + char addr_str[INET6_ADDRSTRLEN]; + + if (context->flags & IPV6_FLAGS_ROUTER_ADV_RECEIVED) + return; + + opt_len = HOST_TO_NET16(ipv6->ipv6_plen) - + sizeof(struct icmpv6_router_advert); + + icmp_opt = (struct icmpv6_opt_hdr *)((u8_t *)icmp + + sizeof(struct icmpv6_router_advert)); + len = 0; + while (len < opt_len) { + icmp_opt = (struct icmpv6_opt_hdr *)((u8_t *)icmp + + sizeof(struct icmpv6_router_advert) + + len); + + switch (icmp_opt->type) { + case IPV6_ICMP_OPTION_PREFIX: + ipv6_icmp_process_prefix(context, + (struct icmpv6_opt_prefix *)icmp_opt); + context->flags |= IPV6_FLAGS_ROUTER_ADV_RECEIVED; + break; + + default: + break; + } + + len += icmp_opt->len * 8; + } + + if (context->flags & IPV6_FLAGS_ROUTER_ADV_RECEIVED) { + LOG_DEBUG("IPv6: RTR ADV nd_ra_flags = 0x%x", + icmp->nd_ra_flags_reserved); + if (icmp->nd_ra_curhoplimit > 0) + context->hop_limit = icmp->nd_ra_curhoplimit; + + if (icmp->nd_ra_flags_reserved & IPV6_RA_MANAGED_FLAG) + context->flags |= IPV6_FLAGS_MANAGED_ADDR_CONFIG; + + if (icmp->nd_ra_flags_reserved & IPV6_RA_CONFIG_FLAG) + context->flags |= IPV6_FLAGS_OTHER_STATEFUL_CONFIG; + + if (icmp->nd_ra_router_lifetime != 0) { + /* There is a default router. */ + if (context->ustack->router_autocfg != + IPV6_RTR_AUTOCFG_OFF) + memcpy( + (char *)&context->default_router, + (char *)&ipv6->ipv6_src, + sizeof(struct ipv6_addr)); + inet_ntop(AF_INET6, &context->default_router, + addr_str, sizeof(addr_str)); + LOG_DEBUG("IPv6: Got default router IP addr: %s", + addr_str); + } + } +} + +static void ipv6_icmp_process_prefix(struct ipv6_context *context, + struct icmpv6_opt_prefix *icmp_prefix) +{ + struct ipv6_addr addr; + char addr_str[INET6_ADDRSTRLEN]; + + /* we only process on-link address info */ + if (!(icmp_prefix->flags & ICMPV6_OPT_PREFIX_FLAG_ON_LINK)) + return; + + /* + * We only process prefix length of 64 since our Identifier is 64-bit + */ + if (icmp_prefix->prefix_len == 64) { + /* Copy 64-bit from the local-link address to create + IPv6 address */ + memcpy((char *)&addr, + (char *)&icmp_prefix->prefix, 8); + memcpy((char *)&addr.addr8[8], + &context->link_local_addr.addr8[8], 8); + inet_ntop(AF_INET6, &addr, addr_str, sizeof(addr_str)); + LOG_DEBUG("IPv6: Got RA ICMP option IP addr: %s", addr_str); + ipv6_add_prefix_entry(context, &addr, 64); + } +} + +static void ipv6_icmp_handle_nd_adv(struct ipv6_context *context) +{ + struct eth_hdr *eth = + (struct eth_hdr *)context->ustack->data_link_layer; + struct ipv6_hdr *ipv6 = + (struct ipv6_hdr *)context->ustack->network_layer; + struct icmpv6_hdr *icmp = (struct icmpv6_hdr *)((u8_t *)ipv6 + + sizeof(struct ipv6_hdr)); + struct icmpv6_opt_link_addr *link_opt = + (struct icmpv6_opt_link_addr *)((u8_t *)icmp + + sizeof(struct icmpv6_hdr) + sizeof(struct ipv6_addr)); + struct ipv6_addr *tar_addr6; + char addr_str[INET6_ADDRSTRLEN]; + + /* Added the multicast check for ARP table update */ + /* Should we qualify for only our host's multicast and our + link_local_multicast?? */ + LOG_DEBUG("IPv6: Handle nd adv"); + if ((ipv6_is_it_our_address(context, &ipv6->ipv6_dst) == TRUE) || + (memcmp((char *)&context->link_local_multi, + (char *)&ipv6->ipv6_dst, sizeof(struct ipv6_addr)) == 0) || + (memcmp((char *)&context->multi, + (char *)&ipv6->ipv6_dst, sizeof(struct ipv6_addr)) == 0)) { + /* + * This is an ARP reply for our addresses. Let's update the + * ARP table. + */ + ipv6_update_arp_table(context, &ipv6->ipv6_src, + ð->src_mac); + + /* Now check for the target address option and update that as + well */ + if (link_opt->hdr.type == IPV6_ICMP_OPTION_TAR_ADDR) { + tar_addr6 = (struct ipv6_addr *)((u8_t *)icmp + + sizeof(struct icmpv6_hdr)); + LOG_DEBUG("IPV6: Target MAC " + "%02x:%02x:%02x:%02x:%02x:%02x", + link_opt->link_addr[0], link_opt->link_addr[1], + link_opt->link_addr[2], link_opt->link_addr[3], + link_opt->link_addr[4], link_opt->link_addr[5]); + inet_ntop(AF_INET6, &tar_addr6->addr8, addr_str, + sizeof(addr_str)); + LOG_DEBUG("IPv6: Target IP addr %s", addr_str); + ipv6_update_arp_table(context, tar_addr6, + (struct mac_address *)link_opt->link_addr); + } + + } +} + +static void ipv6_icmp_handle_nd_sol(struct ipv6_context *context) +{ + struct eth_hdr *eth = + (struct eth_hdr *)context->ustack->data_link_layer; + struct ipv6_hdr *ipv6 = + (struct ipv6_hdr *)context->ustack->network_layer; + struct icmpv6_hdr *icmp = (struct icmpv6_hdr *)((u8_t *)ipv6 + + sizeof(struct ipv6_hdr)); + struct icmpv6_opt_link_addr *link_opt = + (struct icmpv6_opt_link_addr *)((u8_t *)icmp + + sizeof(struct icmpv6_hdr) + sizeof(struct ipv6_addr)); + int icmpv6_opt_len = 0; + struct ipv6_addr tmp; + struct ipv6_addr *longest_match_addr, *tar_addr6; + + LOG_DEBUG("IPv6: Handle nd sol"); + + if ((memcmp((char *)&context->mac_addr, + (char *)ð->dest_mac, sizeof(struct mac_address)) != 0) && + (iscsiL2IsOurMcAddr(context, (struct mac_address *)ð->dest_mac) + == FALSE)) { + /* This packet is not for us to handle */ + LOG_DEBUG("IPv6: MAC not addressed to us " + "%02x:%02x:%02x:%02x:%02x:%02x", + eth->dest_mac.addr[0], eth->dest_mac.addr[1], + eth->dest_mac.addr[2], eth->dest_mac.addr[3], + eth->dest_mac.addr[4], eth->dest_mac.addr[5]); + return; + } + + /* Also check for the icmpv6_data before generating the reply */ + if (ipv6_is_it_our_address(context, + (struct ipv6_addr *) ((u8_t *) icmp + + sizeof(struct icmpv6_hdr))) + == FALSE) { + /* This packet is not for us to handle */ + LOG_DEBUG("IPv6: IP not addressed to us"); + return; + } + + /* Copy source MAC to Destination MAC */ + memcpy((char *)ð->dest_mac, + (char *)ð->src_mac, sizeof(struct mac_address)); + + /* Dest IP contains source IP */ + memcpy((char *)&tmp, + (char *)&ipv6->ipv6_dst, sizeof(struct ipv6_addr)); + memcpy((char *)&ipv6->ipv6_dst, + (char *)&ipv6->ipv6_src, sizeof(struct ipv6_addr)); + + /* Examine the Neighbor Solicitation ICMPv6 target address field. + If target address exist, use that to find best match src address + for the reply */ + if (link_opt->hdr.type == IPV6_ICMP_OPTION_SRC_ADDR) { + tar_addr6 = (struct ipv6_addr *)((u8_t *)icmp + + sizeof(struct icmpv6_hdr)); + if (ipv6_is_it_our_link_local_address(context, tar_addr6) + == TRUE) { + LOG_DEBUG("IPv6: NA using link local"); + memcpy((char *)&ipv6->ipv6_src, + (char *)&context->link_local_addr, + sizeof(struct ipv6_addr)); + } else { + longest_match_addr = + ipv6_find_longest_match(context, tar_addr6); + if (longest_match_addr) { + LOG_DEBUG("IPv6: NA using longest match addr"); + memcpy((char *)&ipv6->ipv6_src, + (char *)longest_match_addr, + sizeof(struct ipv6_addr)); + } else { + LOG_DEBUG("IPv6: NA using link local instead"); + memcpy((char *)&ipv6->ipv6_src, + (char *)&context->link_local_addr, + sizeof(struct ipv6_addr)); + } + } + } else { + /* No target link address, just use whatever it sent to us */ + LOG_DEBUG("IPv6: NA use dst addr"); + memcpy((char *)&ipv6->ipv6_src, + (char *)&tmp, + sizeof(struct ipv6_addr)); + } + ipv6->ipv6_hop_limit = 255; + icmp->icmpv6_type = ICMPV6_NEIGH_ADV; + icmp->icmpv6_code = 0; + icmp->icmpv6_data = 0; + icmp->icmpv6_cksum = 0; + icmp->data.icmpv6_un_data8[0] = + IPV6_NA_FLAG_SOLICITED | IPV6_NA_FLAG_OVERRIDE; + memcpy((char *)((u8_t *)icmp + sizeof(struct icmpv6_hdr)), + (char *)&ipv6->ipv6_src, + sizeof(struct ipv6_addr)); + + /* Add the target link address option only for all solicitation */ + ipv6_icmp_init_link_option(context, + (struct icmpv6_opt_link_addr *)((u8_t *)icmp + + sizeof(struct icmpv6_hdr) + + sizeof(struct ipv6_addr)), + IPV6_ICMP_OPTION_TAR_ADDR); + icmpv6_opt_len = sizeof(struct icmpv6_opt_link_addr); + ipv6->ipv6_plen = HOST_TO_NET16((sizeof(struct icmpv6_hdr) + + icmpv6_opt_len + sizeof(struct ipv6_addr))); + LOG_DEBUG("IPv6: Send nd adv"); + ipv6_send(context, + (u8_t *) icmp - (u8_t *) eth + + sizeof(struct icmpv6_hdr) + + sizeof(struct icmpv6_opt_link_addr) + + sizeof(struct ipv6_addr)); + return; +} + +static void ipv6_icmp_handle_echo_request(struct ipv6_context *context) +{ + struct eth_hdr *eth = + (struct eth_hdr *)context->ustack->data_link_layer; + struct ipv6_hdr *ipv6 = + (struct ipv6_hdr *)context->ustack->network_layer; + struct icmpv6_hdr *icmp = (struct icmpv6_hdr *)((u8_t *)ipv6 + + sizeof(struct ipv6_hdr)); + struct ipv6_addr temp; + + /* Copy source MAC to Destination MAC */ + memcpy((char *)ð->dest_mac, + (char *)ð->src_mac, sizeof(struct mac_address)); + + memcpy((char *)&temp, + (char *)&ipv6->ipv6_dst, sizeof(struct ipv6_addr)); + + /* Dest IP contains source IP */ + memcpy((char *)&ipv6->ipv6_dst, + (char *)&ipv6->ipv6_src, sizeof(struct ipv6_addr)); + /* Use Link-local as source address */ + memcpy((char *)&ipv6->ipv6_src, + (char *)&temp, sizeof(struct ipv6_addr)); + + ipv6->ipv6_hop_limit = context->hop_limit; + icmp->icmpv6_type = ICMPV6_ECHO_REPLY; + icmp->icmpv6_code = 0; + icmp->icmpv6_cksum = 0; + LOG_DEBUG("IPv6: Send echo reply"); + ipv6_send(context, (u8_t *) icmp - (u8_t *) eth + + sizeof(struct ipv6_hdr) + HOST_TO_NET16(ipv6->ipv6_plen)); + return; +} + +void ipv6_set_ip_params(struct ipv6_context *context, + struct ipv6_addr *src_ip, u8_t prefix_len, + struct ipv6_addr *default_gateway, + struct ipv6_addr *linklocal) +{ + if (!(IPV6_IS_ADDR_UNSPECIFIED(src_ip))) { + ipv6_add_prefix_entry(context, src_ip, prefix_len); + /* Create the multi_dest address */ + memset(&context->multi_dest, 0, sizeof(struct ipv6_addr)); + context->multi_dest.addr8[0] = 0xff; + context->multi_dest.addr8[1] = 0x02; + context->multi_dest.addr8[11] = 0x01; + context->multi_dest.addr8[12] = 0xff; + context->multi_dest.addr8[13] = src_ip->addr8[13]; + context->multi_dest.addr16[7] = src_ip->addr16[7]; + /* Create the multi address */ + memset(&context->multi, 0, sizeof(struct ipv6_addr)); + context->multi.addr8[0] = 0xfc; + context->multi.addr8[2] = 0x02; + context->multi.addr16[7] = src_ip->addr16[7]; + } + + if (!(IPV6_IS_ADDR_UNSPECIFIED(default_gateway))) { + /* Override the default gateway addr */ + memcpy((char *)&context->default_router, + (char *)default_gateway, sizeof(struct ipv6_addr)); + ipv6_add_prefix_entry(context, default_gateway, + prefix_len); + } + if (!(IPV6_IS_ADDR_UNSPECIFIED(linklocal))) { + /* Override the linklocal addr */ + memcpy((char *)&context->link_local_addr, + (char *)linklocal, sizeof(struct ipv6_addr)); + context->link_local_multi.addr8[0] = 0xff; + context->link_local_multi.addr8[1] = 0x02; + context->link_local_multi.addr8[11] = 0x01; + context->link_local_multi.addr8[12] = 0xff; + context->link_local_multi.addr8[13] |= + context->link_local_addr.addr8[13]; + context->link_local_multi.addr16[7] = + context->link_local_addr.addr16[7]; + + /* Default Prefix length is 64 */ + /* Add Link local address to the head of the ipv6 address + list */ + ipv6_add_prefix_entry(context, + &context->link_local_addr, 64); + } +} + +int ipv6_get_source_ip_addrs(struct ipv6_context *context, + struct ipv6_addr_entry *addr_list) +{ + struct ipv6_prefix_entry *ipv6_prefix; + int i; + + for (i = 0, ipv6_prefix = context->addr_list; ipv6_prefix != NULL; + ipv6_prefix = ipv6_prefix->next) { + memcpy((char *)&addr_list->ip_addr, + (char *)&ipv6_prefix->ip_addr, + sizeof(struct ipv6_addr)); + addr_list->prefix_len = ipv6_prefix->prefix_len * 8; + + i++; + addr_list++; + } + + return i; +} + +int ipv6_get_default_router_ip_addrs(struct ipv6_context *context, + struct ipv6_addr *ip_addr) +{ + /* This is a default router. */ + memcpy((char *)ip_addr, + (char *)&context->default_router, + sizeof(struct ipv6_addr)); + + return 1; +} + +static void ipv6_udp_rx(struct ipv6_context *context) +{ + struct eth_hdr *eth = + (struct eth_hdr *)context->ustack->data_link_layer; + struct ipv6_hdr *ipv6 = + (struct ipv6_hdr *)context->ustack->network_layer; + struct udp_hdr *udp = (struct udp_hdr *)((u8_t *)ipv6 + + sizeof(struct ipv6_hdr)); + struct dhcpv6_context *dhcpv6c; + + /* + * We only care about DHCPv6 packets from the DHCPv6 server. We drop + * all others. + */ + if (!(context->flags & IPV6_FLAGS_DISABLE_DHCPV6)) { + if ((udp->src_port == HOST_TO_NET16(DHCPV6_SERVER_PORT)) && + (udp->dest_port == HOST_TO_NET16(DHCPV6_CLIENT_PORT))) { + dhcpv6c = context->dhcpv6_context; + dhcpv6c->eth = eth; + dhcpv6c->ipv6 = ipv6; + dhcpv6c->udp = udp; + ipv6_udp_handle_dhcp(dhcpv6c); + } + } +} + +struct mac_address *ipv6_get_link_addr(struct ipv6_context *context) +{ + return &context->mac_addr; +} + +u16_t ipv6_do_stateful_dhcpv6(struct ipv6_context *context, u32_t flags) +{ + u16_t task = 0; + u16_t ra_flags; + + ra_flags = context->flags & + (IPV6_FLAGS_MANAGED_ADDR_CONFIG | IPV6_FLAGS_OTHER_STATEFUL_CONFIG); + + if (!(context->flags & IPV6_FLAGS_ROUTER_ADV_RECEIVED)) { + LOG_DEBUG("IPv6: There is no IPv6 router on the network"); + ra_flags |= + (IPV6_FLAGS_MANAGED_ADDR_CONFIG | + IPV6_FLAGS_OTHER_STATEFUL_CONFIG); + } + + if ((flags & ISCSI_FLAGS_DHCP_TCPIP_CONFIG) && + (ra_flags & IPV6_FLAGS_MANAGED_ADDR_CONFIG)) + task |= DHCPV6_TASK_GET_IP_ADDRESS; + + if ((flags & ISCSI_FLAGS_DHCP_ISCSI_CONFIG) && + (ra_flags & IPV6_FLAGS_OTHER_STATEFUL_CONFIG)) + task |= DHCPV6_TASK_GET_OTHER_PARAMS; + + LOG_DEBUG("IPv6: Stateful flags = 0x%x, ra_flags = 0x%x, task = 0x%x", + flags, ra_flags, task); + + return task; +} + +void ipv6_add_solit_node_address(struct ipv6_context *context, + struct ipv6_addr *ip_addr) +{ + struct mac_address mac_addr; + + /* + * Add Solicited Node Multicast Address for statically configured IPv6 + * address. + */ + mac_addr.addr[0] = 0x33; + mac_addr.addr[1] = 0x33; + mac_addr.addr[2] = 0xff; + mac_addr.addr[3] = ip_addr->addr8[13]; + mac_addr.addr[4] = ip_addr->addr8[14]; + mac_addr.addr[5] = ip_addr->addr8[15]; + iscsiL2AddMcAddr(context, (struct mac_address *)&mac_addr); +} + +void ipv6_cfg_link_local_addr(struct ipv6_context *context, + struct ipv6_addr *ip_addr) +{ + memcpy((char *)&context->link_local_addr, + (char *)ip_addr, sizeof(struct ipv6_addr)); +} + +void ipv6_disable_dhcpv6(struct ipv6_context *context) +{ + context->flags |= IPV6_FLAGS_DISABLE_DHCPV6; +} diff --git a/iscsiuio/src/uip/ipv6.h b/iscsiuio/src/uip/ipv6.h new file mode 100644 index 0000000..3586437 --- /dev/null +++ b/iscsiuio/src/uip/ipv6.h @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Eddie Wai (eddie.wai@broadcom.com) + * Based on Kevin Tran's iSCSI boot code + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ipv6.h - This file contains macro definitions pertaining to IPv6. + * + * RFC 2460 : IPv6 Specification. + * RFC 2373 : IPv6 Addressing Architecture. + * RFC 2462 : IPv6 Stateless Address Autoconfiguration. + * RFC 2464 : Transmission of IPv6 Packets over Ethernet Networks. + * + */ +#ifndef __IPV6_H__ +#define __IPV6_H__ + +#include "ipv6_ndpc.h" + +#define FALSE 0 +#define TRUE 1 + +#define LINK_LOCAL_PREFIX_LENGTH 2 +#define LAYER2_HEADER_LENGTH 14 +#define LAYER2_VLAN_HEADER_LENGTH 16 +#define LAYER2_TYPE_IPV6 0x86dd + +struct ipv6_addr { + union { + u8_t addr8[16]; + u16_t addr16[8]; + u32_t addr[4]; + }; +}; + +struct udp_hdr { + u16_t src_port; + u16_t dest_port; + u16_t length; + u16_t chksum; +}; + +struct mac_address { + union { + u8_t addr[6]; + struct { + u16_t first_2_bytes; + u32_t last_4_bytes; + } __attribute__ ((packed)); + }; +}; + +#define HOST_TO_NET16(a) htons(a) +#define HOST_TO_NET32(a) htonl(a) +#define NET_TO_HOST16(a) ntohs(a) +/* + * Local definition for masks + */ +#define IPV6_MASK0 { { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } } +#define IPV6_MASK32 { { { 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } } +#define IPV6_MASK64 { { { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } } +#define IPV6_MASK96 { { { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 } } } +#define IPV6_MASK128 { { { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } } } + +#ifdef BIG_ENDIAN +#define IPV6_ADDR_INT32_ONE 1 +#define IPV6_ADDR_INT32_TWO 2 +#define IPV6_ADDR_INT32_MNL 0xff010000 +#define IPV6_ADDR_INT32_MLL 0xff020000 +#define IPV6_ADDR_INT32_SMP 0x0000ffff +#define IPV6_ADDR_INT16_ULL 0xfe80 +#define IPV6_ADDR_INT16_USL 0xfec0 +#define IPV6_ADDR_INT16_MLL 0xff02 +#else /* LITTE ENDIAN */ +#define IPV6_ADDR_INT32_ONE 0x01000000 +#define IPV6_ADDR_INT32_TWO 0x02000000 +#define IPV6_ADDR_INT32_MNL 0x000001ff +#define IPV6_ADDR_INT32_MLL 0x000002ff +#define IPV6_ADDR_INT32_SMP 0xffff0000 +#define IPV6_ADDR_INT16_ULL 0x80fe +#define IPV6_ADDR_INT16_USL 0xc0fe +#define IPV6_ADDR_INT16_MLL 0x02ff +#endif + +/* + * Definition of some useful macros to handle IP6 addresses + */ +#define IPV6_ADDR_ANY_INIT \ + { { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } } +#define IPV6_ADDR_LOOPBACK_INIT \ + { { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } } +#define IPV6_ADDR_NODELOCAL_ALLNODES_INIT \ + { { { 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } } +#define IPV6_ADDR_INTFACELOCAL_ALLNODES_INIT \ + { { { 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } } +#define IPV6_ADDR_LINKLOCAL_ALLNODES_INIT \ + { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } } +#define IPV6_ADDR_LINKLOCAL_ALLROUTERS_INIT \ + { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } } } + +#define IPV6_ARE_ADDR_EQUAL(a, b) \ + (memcmp((char *)a, (char *)b, sizeof(struct ipv6_addr)) == 0) + +/* Unspecified IPv6 address */ +#define IPV6_IS_ADDR_UNSPECIFIED(a) \ + ((((a)->addr[0]) == 0) && \ + (((a)->addr[1]) == 0) && \ + (((a)->addr[2]) == 0) && \ + (((a)->addr[3]) == 0)) + +/* IPv6 Scope Values */ +#define IPV6_ADDR_SCOPE_INTFACELOCAL 0x01 /* Node-local scope */ +#define IPV6_ADDR_SCOPE_LINKLOCAL 0x02 /* Link-local scope */ +#define IPV6_ADDR_SCOPE_SITELOCAL 0x05 /* Site-local scope */ +#define IPV6_ADDR_SCOPE_ORGLOCAL 0x08 /* Organization-local scope */ +#define IPV6_ADDR_SCOPE_GLOBAL 0x0e /* Global scope */ + +/* Link-local Unicast : 10-bits much be 1111111010b --> 0xfe80. */ +#define IPV6_IS_ADDR_LINKLOCAL(a) \ + (((a)->addr8[0] == 0xfe) && (((a)->addr8[1] & 0xc0) == 0x80)) + +/* Site-local Unicast : 10-bits much be 1111111011b --> 0xfec0. */ +#define IPV6_IS_ADDR_SITELOCAL(a) \ + (((a)->addr8[0] == 0xfe) && (((a)->addr8[1] & 0xc0) == 0xc0)) + +/* Multicast : 10bits much be 11111111b. Next 4 bits is flags | 4-bit scope */ +#define IPV6_IS_ADDR_MULTICAST(a) ((a)->addr8[0] == 0xff) + +#define IPV6_ADDR_MC_SCOPE(a) ((a)->addr8[1] & 0x0f) + +/* Multicast Scope */ + +struct eth_hdr { + struct mac_address dest_mac; + struct mac_address src_mac; + u16_t len_type; +}; + +struct ipv6_hdr { + union { + struct { + u32_t ipv6_flow; /* Version (4-bit) | + Traffic Class (8-bit) | + Flow ID (20-bit) */ + u16_t ipv6_plen; /* Payload length */ + u8_t ipv6_nxt_hdr; /* Next Header */ + u8_t ipv6_hop_limit; /* hop limit */ + } ipv6_dw1; + + u8_t ipv6_version_fc; /* 4 bits version, top 4 bits class */ + } ipv6_ctrl; + + struct ipv6_addr ipv6_src; /* Source address */ + struct ipv6_addr ipv6_dst; /* Destination address */ +}; + +#define ipv6_version_fc ipv6_ctrl.ipv6_version_fc +#define ipv6_flow ipv6_ctrl.ipv6_dw1.ipv6_flow +#define ipv6_plen ipv6_ctrl.ipv6_dw1.ipv6_plen +#define ipv6_nxt_hdr ipv6_ctrl.ipv6_dw1.ipv6_nxt_hdr +#define ipv6_hop_limit ipv6_ctrl.ipv6_dw1.ipv6_hop_limit + +#define IPV6_VERSION 0x60 +#define IPV6_VERSION_MASK 0xf0 +#define IPV6_HOP_LIMIT 64 + +/* Length of the IP header with no next header */ +#define IPV6_HEADER_LEN sizeof(struct ipv6_hdr) + +#ifdef BIG_ENDIAN +#define IPV6_FLOWINFO_MASK 0x0fffffff /* flow info (28 bits) */ +#define IPV6_FLOWLABEL_MASK 0x000fffff /* flow label (20 bits) */ +#else /* LITTLE_ENDIAN */ +#define IPV6_FLOWINFO_MASK 0xffffff0f /* flow info (28 bits) */ +#define IPV6_FLOWLABEL_MASK 0xffff0f00 /* flow label (20 bits) */ +#endif + +struct packet_ipv6 { + struct mac_address dest_mac; + struct mac_address src_mac; + u16_t len_type; + struct ipv6_hdr ipv6; + union { + struct udp_hdr udp; + } layer4_prot; +}; + +struct packet_ipv6_vlan { + struct mac_address dest_mac; + struct mac_address src_mac; + u16_t len_type; + u16_t vlan_id; + struct ipv6_hdr ipv6; + union { + struct udp_hdr udp; + } layer4_prot; +}; + +struct ipv6_arp_entry { + struct ipv6_addr ip_addr; + struct mac_address mac_addr; + u8_t time; +}; + +#define IPV6_NUM_OF_ADDRESS_ENTRY 4 + +struct ipv6_prefix_entry { + struct ipv6_prefix_entry *next; + struct ipv6_addr ip_addr; + u8_t prefix_len; +}; + +struct ipv6_addr_entry { + struct ipv6_addr ip_addr; + u8_t prefix_len; +}; + +struct ipv6_context { + u16_t flags; +#define IPV6_FLAGS_MANAGED_ADDR_CONFIG (1 << 0) +#define IPV6_FLAGS_OTHER_STATEFUL_CONFIG (1 << 1) +#define IPV6_FLAGS_ROUTER_ADV_RECEIVED (1 << 2) +#define IPV6_FLAGS_DISABLE_DHCPV6 (1 << 3) + + struct mac_address mac_addr; + struct ipv6_addr link_local_addr; + struct ipv6_addr link_local_multi; + struct ipv6_addr multi; /* For Static IPv6 only */ + struct ipv6_addr multi_dest; /* For Static IPv6 only */ + struct ipv6_addr default_router; + struct ipv6_prefix_entry *addr_list; + u8_t hop_limit; +#define UIP_ARPTAB_SIZE 16 + + struct uip_stack *ustack; +#define MAX_MCADDR_TABLE 5 + struct mac_address mc_addr[MAX_MCADDR_TABLE]; + u8_t arptime; + struct ipv6_arp_entry ipv6_arp_table[UIP_ARPTAB_SIZE]; + struct ipv6_prefix_entry ipv6_prefix_table[IPV6_NUM_OF_ADDRESS_ENTRY]; + + /* VLAN support */ + + void *dhcpv6_context; +}; + +#define ISCSI_FLAGS_DHCP_TCPIP_CONFIG (1<<0) +#define ISCSI_FLAGS_DHCP_ISCSI_CONFIG (1<<1) + +#define IPV6_MAX_ROUTER_SOL_DELAY 4 +#define IPV6_MAX_ROUTER_SOL_RETRY 3 + +#define DHCPV6_CLIENT_PORT 546 +#define DHCPV6_SERVER_PORT 547 + +/* Function prototype */ +void ipv6_init(struct ndpc_state *ndp, int cfg); +int ipv6_autoconfig(struct ipv6_context *context); +int ipv6_discover_address(struct ipv6_context *context); +struct ipv6_addr *ipv6_our_address(struct ipv6_context *context); +int ipv6_ip_in_arp_table(struct ipv6_context *context, + struct ipv6_addr *ipv6_addr, + struct mac_address *mac_addr); +void ipv6_arp_timer(struct ipv6_context *context); +void ipv6_arp_out(struct ipv6_context *context, int *uip_len); +int ipv6_add_prefix_entry(struct ipv6_context *context, + struct ipv6_addr *ip_addr, u8_t prefix_len); +void ipv6_set_ip_params(struct ipv6_context *context, + struct ipv6_addr *src_ip, u8_t prefix_len, + struct ipv6_addr *default_gateway, + struct ipv6_addr *linklocal); +void ipv6_set_host_addr(struct ipv6_context *context, struct ipv6_addr *src_ip); +int ipv6_get_default_router_ip_addrs(struct ipv6_context *context, + struct ipv6_addr *ip_addr); +struct mac_address *ipv6_get_link_addr(struct ipv6_context *context); +u16_t ipv6_do_stateful_dhcpv6(struct ipv6_context *context, u32_t flags); +void ipv6_add_solit_node_address(struct ipv6_context *context, + struct ipv6_addr *ip_addr); +int ipv6_get_source_ip_addrs(struct ipv6_context *context, + struct ipv6_addr_entry *addr_list); +void ipv6_cfg_link_local_addr(struct ipv6_context *context, + struct ipv6_addr *ip_addr); +void ipv6_disable_dhcpv6(struct ipv6_context *context); +int ipv6_send_nd_solicited_packet(struct ipv6_context *context, + struct eth_hdr *eth, struct ipv6_hdr *ipv6); +int ipv6_is_it_our_link_local_address(struct ipv6_context *context, + struct ipv6_addr *ip_addr); +void ipv6_mc_init_dest_mac(struct eth_hdr *eth, struct ipv6_hdr *ipv6); +struct ipv6_addr *ipv6_find_longest_match(struct ipv6_context *context, + struct ipv6_addr *ip_addr); + +#endif /* __IPV6_H__ */ diff --git a/iscsiuio/src/uip/ipv6_ndpc.c b/iscsiuio/src/uip/ipv6_ndpc.c new file mode 100644 index 0000000..bb07c1d --- /dev/null +++ b/iscsiuio/src/uip/ipv6_ndpc.c @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Eddie Wai (eddie.wai@broadcom.com) + * Based on the Swedish Institute of Computer Science's + * dhcpc.c code + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ipv6_ndpc.c - Top level IPv6 Network Discovery Protocol Engine (RFC4861) + * + */ +#include +#include +#include +#include +#include +#include + +#include "uip.h" +#include "ipv6_ndpc.h" +#include "timer.h" +#include "pt.h" + +#include "debug.h" +#include "logger.h" +#include "nic.h" +#include "nic_utils.h" +#include "ipv6.h" +#include "ipv6_pkt.h" +#include "dhcpv6.h" + +const int dhcpv6_retry_timeout[DHCPV6_NUM_OF_RETRY] = { 1, 2, 4, 8 }; + +static PT_THREAD(handle_ndp(struct uip_stack *ustack, int force)) +{ + struct ndpc_state *s; + struct ipv6_context *ipv6c; + struct dhcpv6_context *dhcpv6c = NULL; + u16_t task = 0; + char buf[INET6_ADDRSTRLEN]; + + s = ustack->ndpc; + if (s == NULL) { + LOG_DEBUG("NDP: Could not find ndpc state"); + return PT_ENDED; + } + + ipv6c = s->ipv6_context; + if (!ipv6c) + goto ndpc_state_null; + + dhcpv6c = s->dhcpv6_context; + + PT_BEGIN(&s->pt); + + if (s->state == NDPC_STATE_BACKGROUND_LOOP) + goto ipv6_loop; + + if (s->state == NDPC_STATE_RTR_ADV) + goto rtr_adv; + + /* For AUTOCFG == DHCPv6, do all + For == ND, skip DHCP only and do RTR + For == UNUSED/UNSPEC, do all as according to DHCP or not */ + s->state = NDPC_STATE_RTR_SOL; + /* try_again: */ + s->ticks = CLOCK_SECOND * IPV6_MAX_ROUTER_SOL_DELAY; + s->retry_count = 0; + do { + /* Perform router solicitation and wait for + router advertisement */ + LOG_DEBUG("%s: ndpc_handle send rtr sol", s->nic->log_name); + ipv6_autoconfig(s->ipv6_context); + + timer_set(&s->timer, s->ticks); +wait_rtr: + s->ustack->uip_flags &= ~UIP_NEWDATA; + LOG_DEBUG("%s: ndpc_handle wait for rtr adv flags=0x%x", + s->nic->log_name, ipv6c->flags); + PT_WAIT_UNTIL(&s->pt, uip_newdata(s->ustack) + || timer_expired(&s->timer) || force); + + if (uip_newdata(s->ustack)) { + /* Validate incoming packets + Note that the uip_len is init from nic loop */ + ipv6_rx_packet(ipv6c, (u16_t) uip_datalen(s->ustack)); + if (ipv6c->flags & IPV6_FLAGS_ROUTER_ADV_RECEIVED) { + LOG_INFO("%s: ROUTER_ADV_RECEIVED", + s->nic->log_name); + /* Success */ + break; + } else if (!timer_expired(&s->timer)) { + /* Yes new data, but not what we want, + check for timer expiration before bumping + tick */ + goto wait_rtr; + } + } + s->retry_count++; + if (s->retry_count >= IPV6_MAX_ROUTER_SOL_RETRY) + /* Max router solicitation retry reached. Move to + IPv6 loop (no DHCPv6) */ + goto no_rtr_adv; + + } while (!(ipv6c->flags & IPV6_FLAGS_ROUTER_ADV_RECEIVED)); + + LOG_DEBUG("%s: ndpc_handle got rtr adv", s->nic->log_name); + s->retry_count = 0; + +no_rtr_adv: + s->state = NDPC_STATE_RTR_ADV; + +rtr_adv: + if (!(ustack->ip_config & IPV6_CONFIG_DHCP)) + goto staticv6; + + /* Only DHCPv6 comes here */ + task = ipv6_do_stateful_dhcpv6(ipv6c, ISCSI_FLAGS_DHCP_TCPIP_CONFIG); + if (task) { + /* Run the DHCPv6 engine */ + + if (!dhcpv6c) + goto ipv6_loop; + + dhcpv6c->dhcpv6_task = task; + s->retry_count = 0; + s->state = NDPC_STATE_DHCPV6_DIS; + do { + /* Do dhcpv6 */ + dhcpv6c->timeout = dhcpv6_retry_timeout[s->retry_count]; + s->ticks = CLOCK_SECOND * dhcpv6c->timeout; + LOG_DEBUG("%s: ndpc_handle send dhcpv6 sol retry " + "cnt=%d", s->nic->log_name, s->retry_count); + dhcpv6_do_discovery(dhcpv6c); + + timer_set(&s->timer, s->ticks); +wait_dhcp: + s->ustack->uip_flags &= ~UIP_NEWDATA; + PT_WAIT_UNTIL(&s->pt, uip_newdata(s->ustack) + || timer_expired(&s->timer) || force); + + if (uip_newdata(s->ustack)) { + /* Validate incoming packets + Note that the uip_len is init from nic + loop */ + ipv6_rx_packet(ipv6c, + (u16_t) uip_datalen(s->ustack)); + if (dhcpv6c->dhcpv6_done == TRUE) + break; + else if (!timer_expired(&s->timer)) { + /* Yes new data, but not what we want, + check for timer expiration before + bumping tick */ + goto wait_dhcp; + } + } + s->retry_count++; + if (s->retry_count < DHCPV6_NUM_OF_RETRY) { + dhcpv6c->seconds += dhcpv6c->timeout; + } else { + LOG_DEBUG("%s: ndpc_handle DHCP failed", + s->nic->log_name); + /* Allow to goto background loop */ + goto ipv6_loop; + } + } while (dhcpv6c->dhcpv6_done == FALSE); + s->state = NDPC_STATE_DHCPV6_DONE; + + LOG_DEBUG("%s: ndpc_handle got dhcpv6", s->nic->log_name); + + /* End of DHCPv6 engine */ + } else { + /* Static IPv6 */ + if (ustack->ip_config == IPV6_CONFIG_DHCP) { + s->retry_count++; + if (s->retry_count > DHCPV6_NUM_OF_RETRY) { + LOG_DEBUG("%s: ndpc_handle DHCP failed", + s->nic->log_name); + } else { + PT_RESTART(&s->pt); + } + } +staticv6: + ipv6_disable_dhcpv6(ipv6c); + } + /* Copy out the default_router_addr6 and ll */ + if (ustack->router_autocfg != IPV6_RTR_AUTOCFG_OFF) + memcpy(&ustack->default_route_addr6, + &ipv6c->default_router, sizeof(struct ipv6_addr)); + inet_ntop(AF_INET6, &ustack->default_route_addr6, + buf, sizeof(buf)); + LOG_INFO("%s: Default router IP: %s", s->nic->log_name, + buf); + + if (ustack->linklocal_autocfg != IPV6_LL_AUTOCFG_OFF) + memcpy(&ustack->linklocal6, &ipv6c->link_local_addr, + sizeof(struct ipv6_addr)); + inet_ntop(AF_INET6, &ustack->linklocal6, + buf, sizeof(buf)); + LOG_INFO("%s: Linklocal IP: %s", s->nic->log_name, + buf); + +ipv6_loop: + s->state = NDPC_STATE_BACKGROUND_LOOP; + LOG_DEBUG("%s: Loop", s->nic->log_name); + /* Background IPv6 loop */ + while (1) { + /* Handle all neightbor solicitation/advertisement here */ + s->ustack->uip_flags &= ~UIP_NEWDATA; + PT_WAIT_UNTIL(&s->pt, uip_newdata(s->ustack)); + + /* Validate incoming packets */ + ipv6_rx_packet(ipv6c, (u16_t) uip_datalen(s->ustack)); + } + +ndpc_state_null: + + while (1) + PT_YIELD(&s->pt); + + PT_END(&(s->pt)); +} + +/*---------------------------------------------------------------------------*/ +int ndpc_init(nic_t *nic, struct uip_stack *ustack, + const void *mac_addr, int mac_len) +{ + struct ipv6_context *ipv6c; + struct dhcpv6_context *dhcpv6c; + struct ndpc_state *s = ustack->ndpc; + struct ipv6_addr src, gw, ll; + char buf[INET6_ADDRSTRLEN]; + + if (s) { + LOG_DEBUG("NDP: NDP context already allocated"); + /* Already allocated, skip*/ + return -EALREADY; + } + s = malloc(sizeof(*s)); + if (s == NULL) { + LOG_ERR("%s: Couldn't allocate size for ndpc info", + nic->log_name); + goto error; + } + memset(s, 0, sizeof(*s)); + + if (s->ipv6_context) { + LOG_DEBUG("NDP: IPv6 context already allocated"); + ipv6c = s->ipv6_context; + goto init1; + } + ipv6c = malloc(sizeof(struct ipv6_context)); + if (ipv6c == NULL) { + LOG_ERR("%s: Couldn't allocate mem for IPv6 context info", + nic->log_name); + goto error1; + } +init1: + if (s->dhcpv6_context) { + LOG_DEBUG("NDP: DHCPv6 context already allocated"); + dhcpv6c = s->dhcpv6_context; + goto init2; + } + dhcpv6c = malloc(sizeof(struct dhcpv6_context)); + if (dhcpv6c == NULL) { + LOG_ERR("%s: Couldn't allocate mem for DHCPv6 context info", + nic->log_name); + goto error2; + } +init2: + memset(s, 0, sizeof(*s)); + memset(ipv6c, 0, sizeof(*ipv6c)); + memset(dhcpv6c, 0, sizeof(*dhcpv6c)); + + s->ipv6_context = ipv6c; + s->dhcpv6_context = dhcpv6c; + + s->nic = nic; + s->ustack = ustack; + s->mac_addr = (void *)mac_addr; + s->mac_len = mac_len; + s->state = NDPC_STATE_INIT; + + /* Init IPV6_CONTEXT */ + ipv6_init(s, ustack->ip_config); + + dhcpv6c->ipv6_context = ipv6c; + ipv6c->dhcpv6_context = dhcpv6c; + + /* Init DHCPV6_CONTEXT */ + dhcpv6_init(dhcpv6c); + + ustack->ndpc = s; + + PT_INIT(&s->pt); + + if (ustack->ip_config == IPV6_CONFIG_DHCP) { + /* DHCPv6 specific */ + memset(&src, 0, sizeof(src)); + } else { + /* Static v6 specific */ + memcpy(&src.addr8, &ustack->hostaddr6, + sizeof(struct ipv6_addr)); + ipv6_add_solit_node_address(ipv6c, &src); + + inet_ntop(AF_INET6, &src.addr8, buf, sizeof(buf)); + LOG_INFO("%s: Static hostaddr IP: %s", s->nic->log_name, + buf); + } + /* Copy out the default_router_addr6 and ll */ + if (ustack->router_autocfg == IPV6_RTR_AUTOCFG_OFF) + memcpy(&gw.addr8, &ustack->default_route_addr6, + sizeof(struct ipv6_addr)); + else + memset(&gw, 0, sizeof(gw)); + + if (ustack->linklocal_autocfg == IPV6_LL_AUTOCFG_OFF) + memcpy(&ll.addr8, &ustack->linklocal6, + sizeof(struct ipv6_addr)); + else + memset(&ll, 0, sizeof(ll)); + ipv6_set_ip_params(ipv6c, &src, + ustack->prefix_len, &gw, &ll); + + return 0; +error2: + free(ipv6c); + s->ipv6_context = NULL; +error1: + free(s); + ustack->ndpc = NULL; +error: + return -ENOMEM; +} + +/*---------------------------------------------------------------------------*/ +void ndpc_call(struct uip_stack *ustack) +{ + handle_ndp(ustack, 0); +} + +void ndpc_exit(struct ndpc_state *ndp) +{ + LOG_DEBUG("NDP - Exit ndpc_state = %p", ndp); + if (!ndp) + return; + if (ndp->ipv6_context) + free(ndp->ipv6_context); + if (ndp->dhcpv6_context) + free(ndp->dhcpv6_context); + free(ndp); +} + +int ndpc_request(struct uip_stack *ustack, void *in, void *out, int request) +{ + struct ndpc_state *s; + struct ipv6_context *ipv6c; + int ret = 0; + + if (!ustack) { + LOG_DEBUG("NDP: ustack == NULL"); + return -EINVAL; + } + s = ustack->ndpc; + if (s == NULL) { + LOG_DEBUG("NDP: Could not find ndpc state for request %d", + request); + return -EINVAL; + } + while (s->state != NDPC_STATE_BACKGROUND_LOOP) { + LOG_DEBUG("%s: ndpc state not in background loop, run handler " + "request = %d", s->nic->log_name, request); + handle_ndp(ustack, 1); + } + + ipv6c = s->ipv6_context; + switch (request) { + case NEIGHBOR_SOLICIT: + *(int *)out = ipv6_send_nd_solicited_packet(ipv6c, + (struct eth_hdr *)((struct ndpc_reqptr *)in)->eth, + (struct ipv6_hdr *)((struct ndpc_reqptr *)in)->ipv6); + break; + case CHECK_LINK_LOCAL_ADDR: + *(int *)out = ipv6_is_it_our_link_local_address(ipv6c, + (struct ipv6_addr *)in); + break; + case CHECK_ARP_TABLE: + *(int *)out = ipv6_ip_in_arp_table(ipv6c, + (struct ipv6_addr *)((struct ndpc_reqptr *)in)->ipv6, + (struct mac_address *)((struct ndpc_reqptr *)in)->eth); + break; + case GET_HOST_ADDR: + *(struct ipv6_addr **)out = ipv6_find_longest_match(ipv6c, + (struct ipv6_addr *)in); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +/*---------------------------------------------------------------------------*/ diff --git a/iscsiuio/src/uip/ipv6_ndpc.h b/iscsiuio/src/uip/ipv6_ndpc.h new file mode 100644 index 0000000..709a050 --- /dev/null +++ b/iscsiuio/src/uip/ipv6_ndpc.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Eddie Wai (eddie.wai@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ipv6_ndpc.h - Top level IPv6 Network Discovery Protocol Engine (RFC4861) + * + */ +#ifndef __NDPC_H__ +#define __NDPC_H__ + +#include + +#include "nic.h" +#include "timer.h" +#include "pt.h" + +struct ndpc_reqptr { + void *eth; + void *ipv6; +}; + +struct ndpc_state { + struct pt pt; + + nic_t *nic; + struct uip_stack *ustack; + char state; + struct timer timer; + u16_t ticks; + void *mac_addr; + int mac_len; + int retry_count; + + time_t last_update; + + void *ipv6_context; + void *dhcpv6_context; +}; + +enum { + NDPC_STATE_INIT, + NDPC_STATE_RTR_SOL, + NDPC_STATE_RTR_ADV, + NDPC_STATE_DHCPV6_DIS, + NDPC_STATE_DHCPV6_DONE, + NDPC_STATE_BACKGROUND_LOOP +}; + +int ndpc_init(nic_t *nic, struct uip_stack *ustack, + const void *mac_addr, int mac_len); +void ndpc_call(struct uip_stack *ustack); +void ndpc_exit(struct ndpc_state *ndp); + +enum { + NEIGHBOR_SOLICIT, + CHECK_LINK_LOCAL_ADDR, + GET_LINK_LOCAL_ADDR, + GET_DEFAULT_ROUTER_ADDR, + CHECK_ARP_TABLE, + GET_HOST_ADDR +}; + +int ndpc_request(struct uip_stack *ustack, void *in, void *out, int request); + +#define UIP_NDP_CALL ndpc_call + +#endif /* __NDPC_H__ */ diff --git a/iscsiuio/src/uip/ipv6_pkt.h b/iscsiuio/src/uip/ipv6_pkt.h new file mode 100644 index 0000000..b42f1aa --- /dev/null +++ b/iscsiuio/src/uip/ipv6_pkt.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Eddie Wai (eddie.wai@broadcom.com) + * Based on Kevin Tran's iSCSI boot code + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ipv6_packet.h - IPv6 routine include file + * + */ +#ifndef __IPV6_PKT_H__ +#define __IPV6_PKT_H__ + +u16_t ipv6_process_rx(struct ipv6_hdr *ipv6); +void ipv6_rx_packet(struct ipv6_context *context, u16_t len); +void ipv6_setup_hdrs(struct ipv6_context *context, struct eth_hdr *eth, + struct ipv6_hdr *ipv6, u16_t packet_len); +int ipv6_send(struct ipv6_context *context, u16_t packet_len); +void ipv6_send_udp_packet(struct ipv6_context *context, u16_t packet_len); + +#endif /* __IPV6_PKT_H__ */ diff --git a/iscsiuio/src/uip/lc-addrlabels.h b/iscsiuio/src/uip/lc-addrlabels.h new file mode 100644 index 0000000..c394b22 --- /dev/null +++ b/iscsiuio/src/uip/lc-addrlabels.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2004-2005, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack + * + * Author: Adam Dunkels + * + */ + +/** + * \addtogroup lc + * @{ + */ + +/** + * \file + * Implementation of local continuations based on the "Labels as + * values" feature of gcc + * \author + * Adam Dunkels + * + * This implementation of local continuations is based on a special + * feature of the GCC C compiler called "labels as values". This + * feature allows assigning pointers with the address of the code + * corresponding to a particular C label. + * + * For more information, see the GCC documentation: + * http://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html + * + * Thanks to dividuum for finding the nice local scope label + * implementation. + */ + +#ifndef __LC_ADDRLABELS_H__ +#define __LC_ADDRLABELS_H__ + +/** \hideinitializer */ + +#define LC_INIT(s) (s = NULL) + +#define LC_RESUME(s) \ + do { \ + if (s != NULL) { \ + goto *s; \ + } \ + } while (0) + +#define LC_SET(s) \ + do { ({ __label__ resume; resume: (s) = &&resume; }); } while (0) + +#define LC_END(s) + +#endif /* __LC_ADDRLABELS_H__ */ + +/** @} */ diff --git a/iscsiuio/src/uip/lc-switch.h b/iscsiuio/src/uip/lc-switch.h new file mode 100644 index 0000000..1839b36 --- /dev/null +++ b/iscsiuio/src/uip/lc-switch.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2004-2005, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack + * + * Author: Adam Dunkels + * + */ + +/** + * \addtogroup lc + * @{ + */ + +/** + * \file + * Implementation of local continuations based on switch() statment + * \author Adam Dunkels + * + * This implementation of local continuations uses the C switch() + * statement to resume execution of a function somewhere inside the + * function's body. The implementation is based on the fact that + * switch() statements are able to jump directly into the bodies of + * control structures such as if() or while() statmenets. + * + * This implementation borrows heavily from Simon Tatham's coroutines + * implementation in C: + * http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html + */ + +#ifndef __LC_SWITCH_H__ +#define __LC_SWTICH_H__ + +/* WARNING! lc implementation using switch() does not work if an + LC_SET() is done within another switch() statement! */ + +/** \hideinitializer */ +#define LC_INIT(s) s = 0; + +#define LC_RESUME(s) switch (s) { case 0: + +#define LC_SET(s) s = __LINE__; case __LINE__: + +#define LC_END(s) } + +#endif /* __LC_SWITCH_H__ */ + +/** @} */ diff --git a/iscsiuio/src/uip/lc.h b/iscsiuio/src/uip/lc.h new file mode 100644 index 0000000..2e4a7bb --- /dev/null +++ b/iscsiuio/src/uip/lc.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2004-2005, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack + * + * Author: Adam Dunkels + * + */ + +/** + * \addtogroup pt + * @{ + */ + +/** + * \defgroup lc Local continuations + * @{ + * + * Local continuations form the basis for implementing protothreads. A + * local continuation can be set in a specific function to + * capture the state of the function. After a local continuation has + * been set can be resumed in order to restore the state of the + * function at the point where the local continuation was set. + * + * + */ + +/** + * \file lc.h + * Local continuations + * \author + * Adam Dunkels + * + */ + +#ifdef DOXYGEN +/** + * Initialize a local continuation. + * + * This operation initializes the local continuation, thereby + * unsetting any previously set continuation state. + * + * \hideinitializer + */ +#define LC_INIT(lc) + +/** + * Set a local continuation. + * + * The set operation saves the state of the function at the point + * where the operation is executed. As far as the set operation is + * concerned, the state of the function does not include the + * call-stack or local (automatic) variables, but only the program + * counter and such CPU registers that needs to be saved. + * + * \hideinitializer + */ +#define LC_SET(lc) + +/** + * Resume a local continuation. + * + * The resume operation resumes a previously set local continuation, thus + * restoring the state in which the function was when the local + * continuation was set. If the local continuation has not been + * previously set, the resume operation does nothing. + * + * \hideinitializer + */ +#define LC_RESUME(lc) + +/** + * Mark the end of local continuation usage. + * + * The end operation signifies that local continuations should not be + * used any more in the function. This operation is not needed for + * most implementations of local continuation, but is required by a + * few implementations. + * + * \hideinitializer + */ +#define LC_END(lc) + +/** + * \var typedef lc_t; + * + * The local continuation type. + * + * \hideinitializer + */ +#endif /* DOXYGEN */ + +#ifndef __LC_H__ +#define __LC_H__ + +#ifdef LC_CONF_INCLUDE +#include LC_CONF_INCLUDE +#else +#include "lc-switch.h" +#endif /* LC_CONF_INCLUDE */ + +#endif /* __LC_H__ */ + +/** @} */ +/** @} */ diff --git a/iscsiuio/src/uip/psock.c b/iscsiuio/src/uip/psock.c new file mode 100644 index 0000000..fcffbe7 --- /dev/null +++ b/iscsiuio/src/uip/psock.c @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2004, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack + * + * Author: Adam Dunkels + * + */ + +#include +#include + +#include "uipopt.h" +#include "psock.h" +#include "uip.h" + +#define STATE_NONE 0 +#define STATE_ACKED 1 +#define STATE_READ 2 +#define STATE_BLOCKED_NEWDATA 3 +#define STATE_BLOCKED_CLOSE 4 +#define STATE_BLOCKED_SEND 5 +#define STATE_DATA_SENT 6 + +/* + * Return value of the buffering functions that indicates that a + * buffer was not filled by incoming data. + * + */ +#define BUF_NOT_FULL 0 +#define BUF_NOT_FOUND 0 + +/* + * Return value of the buffering functions that indicates that a + * buffer was completely filled by incoming data. + * + */ +#define BUF_FULL 1 + +/* + * Return value of the buffering functions that indicates that an + * end-marker byte was found. + * + */ +#define BUF_FOUND 2 + +/*---------------------------------------------------------------------------*/ +static void buf_setup(struct psock_buf *buf, u8_t *bufptr, u16_t bufsize) +{ + buf->ptr = bufptr; + buf->left = bufsize; +} + +/*---------------------------------------------------------------------------*/ +static u8_t +buf_bufdata(struct psock_buf *buf, u16_t len, u8_t **dataptr, u16_t *datalen) +{ + if (*datalen < buf->left) { + memcpy(buf->ptr, *dataptr, *datalen); + buf->ptr += *datalen; + buf->left -= *datalen; + *dataptr += *datalen; + *datalen = 0; + return BUF_NOT_FULL; + } else if (*datalen == buf->left) { + memcpy(buf->ptr, *dataptr, *datalen); + buf->ptr += *datalen; + buf->left = 0; + *dataptr += *datalen; + *datalen = 0; + return BUF_FULL; + } else { + memcpy(buf->ptr, *dataptr, buf->left); + buf->ptr += buf->left; + *datalen -= buf->left; + *dataptr += buf->left; + buf->left = 0; + return BUF_FULL; + } +} + +/*---------------------------------------------------------------------------*/ +static u8_t +buf_bufto(register struct psock_buf *buf, u8_t endmarker, + register u8_t **dataptr, register u16_t *datalen) +{ + u8_t c; + while (buf->left > 0 && *datalen > 0) { + c = *buf->ptr = **dataptr; + ++*dataptr; + ++buf->ptr; + --*datalen; + --buf->left; + + if (c == endmarker) + return BUF_FOUND; + } + + if (*datalen == 0) + return BUF_NOT_FOUND; + + while (*datalen > 0) { + c = **dataptr; + --*datalen; + ++*dataptr; + + if (c == endmarker) + return BUF_FOUND | BUF_FULL; + } + + return BUF_FULL; +} + +/*---------------------------------------------------------------------------*/ +static char send_data(register struct psock *s) +{ + if (s->state != STATE_DATA_SENT || uip_rexmit(s->ustack)) { + if (s->sendlen > uip_mss(s->ustack)) + uip_appsend(s->ustack, s->sendptr, uip_mss(s->ustack)); + else + uip_appsend(s->ustack, s->sendptr, s->sendlen); + s->state = STATE_DATA_SENT; + return 1; + } + return 0; +} + +/*---------------------------------------------------------------------------*/ +static char data_acked(struct psock *s) +{ + if (s->state == STATE_DATA_SENT && uip_acked(s->ustack)) { + if (s->sendlen > uip_mss(s->ustack)) { + s->sendlen -= uip_mss(s->ustack); + s->sendptr += uip_mss(s->ustack); + } else { + s->sendptr += s->sendlen; + s->sendlen = 0; + } + s->state = STATE_ACKED; + return 1; + } + return 0; +} + +/*---------------------------------------------------------------------------*/ +PT_THREAD(psock_send(struct uip_stack *ustack, + register struct psock *s, const u8_t *buf, + unsigned int len)) +{ + PT_BEGIN(&s->psockpt); + + /* If there is no data to send, we exit immediately. */ + if (len == 0) { + PT_EXIT(&s->psockpt); + } + + /* Save the length of and a pointer to the data that is to be + sent. */ + s->sendptr = buf; + s->sendlen = len; + + s->state = STATE_NONE; + + /* We loop here until all data is sent. The s->sendlen variable is + updated by the data_sent() function. */ + while (s->sendlen > 0) { + + /* + * The condition for this PT_WAIT_UNTIL is a little tricky: the + * protothread will wait here until all data has been acked + * (data_acked() returns true) and until all data has been sent + * (send_data() returns true). The two functions data_acked() + * and send_data() must be called in succession to ensure that + * all data is sent. Therefore the & operator is used instead of + * the && operator, which would cause only the data_acked() + * function to be called when it returns false. + */ + PT_WAIT_UNTIL(&s->psockpt, data_acked(s) & send_data(s)); + } + + s->state = STATE_NONE; + + PT_END(&s->psockpt); +} + +/*---------------------------------------------------------------------------*/ +PT_THREAD(psock_generator_send(register struct psock *s, + unsigned short (*generate) (void *), void *arg)) +{ + PT_BEGIN(&s->psockpt); + + /* Ensure that there is a generator function to call. */ + if (generate == NULL) { + PT_EXIT(&s->psockpt); + } + + /* Call the generator function to generate the data in the + uip_appdata buffer. */ + s->sendlen = generate(arg); + s->sendptr = s->ustack->uip_appdata; + + s->state = STATE_NONE; + do { + /* Call the generator function again if we are called to perform + a retransmission. */ + if (uip_rexmit(s->ustack)) + generate(arg); + /* Wait until all data is sent and acknowledged. */ + PT_WAIT_UNTIL(&s->psockpt, data_acked(s) & send_data(s)); + } while (s->sendlen > 0); + + s->state = STATE_NONE; + + PT_END(&s->psockpt); +} + +/*---------------------------------------------------------------------------*/ +u16_t psock_datalen(struct psock *psock) +{ + return psock->bufsize - psock->buf.left; +} + +/*---------------------------------------------------------------------------*/ +char psock_newdata(struct psock *s) +{ + if (s->readlen > 0) { + /* There is data in the uip_appdata buffer that has not yet been + read with the PSOCK_READ functions. */ + return 1; + } else if (s->state == STATE_READ) { + /* All data in uip_appdata buffer already consumed. */ + s->state = STATE_BLOCKED_NEWDATA; + return 0; + } else if (uip_newdata(s->ustack)) { + /* There is new data that has not been consumed. */ + return 1; + } else { + /* There is no new data. */ + return 0; + } +} + +/*---------------------------------------------------------------------------*/ +PT_THREAD(psock_readto(register struct psock *psock, u8_t c)) +{ + PT_BEGIN(&psock->psockpt); + + buf_setup(&psock->buf, psock->bufptr, psock->bufsize); + + /* XXX: Should add buf_checkmarker() before do{} loop, if + incoming data has been handled while waiting for a write. */ + + do { + if (psock->readlen == 0) { + PT_WAIT_UNTIL(&psock->psockpt, psock_newdata(psock)); + psock->state = STATE_READ; + psock->readptr = (u8_t *) psock->ustack->uip_appdata; + psock->readlen = uip_datalen(psock->ustack); + } + } while ((buf_bufto(&psock->buf, c, + &psock->readptr, + &psock->readlen) & BUF_FOUND) == 0); + + if (psock_datalen(psock) == 0) { + psock->state = STATE_NONE; + PT_RESTART(&psock->psockpt); + } + PT_END(&psock->psockpt); +} + +/*---------------------------------------------------------------------------*/ +PT_THREAD(psock_readbuf(register struct psock *psock)) +{ + PT_BEGIN(&psock->psockpt); + + buf_setup(&psock->buf, psock->bufptr, psock->bufsize); + + /* XXX: Should add buf_checkmarker() before do{} loop, if + incoming data has been handled while waiting for a write. */ + + do { + if (psock->readlen == 0) { + PT_WAIT_UNTIL(&psock->psockpt, psock_newdata(psock)); + printf("Waited for newdata\n"); + psock->state = STATE_READ; + psock->readptr = (u8_t *) psock->ustack->uip_appdata; + psock->readlen = uip_datalen(psock->ustack); + } + } while (buf_bufdata(&psock->buf, psock->bufsize, + &psock->readptr, &psock->readlen) != BUF_FULL); + + if (psock_datalen(psock) == 0) { + psock->state = STATE_NONE; + PT_RESTART(&psock->psockpt); + } + PT_END(&psock->psockpt); +} + +/*---------------------------------------------------------------------------*/ +void +psock_init(struct uip_stack *ustack, + register struct psock *psock, u8_t *buffer, unsigned int buffersize) +{ + psock->state = STATE_NONE; + psock->readlen = 0; + psock->bufptr = buffer; + psock->bufsize = buffersize; + psock->ustack = ustack; + buf_setup(&psock->buf, buffer, buffersize); + PT_INIT(&psock->pt); + PT_INIT(&psock->psockpt); +} + +/*---------------------------------------------------------------------------*/ diff --git a/iscsiuio/src/uip/psock.h b/iscsiuio/src/uip/psock.h new file mode 100644 index 0000000..ea86ef5 --- /dev/null +++ b/iscsiuio/src/uip/psock.h @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2004, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack + * + * Author: Adam Dunkels + * + */ + +/** + * \defgroup psock Protosockets library + * @{ + * + * The protosocket library provides an interface to the uIP stack that is + * similar to the traditional BSD socket interface. Unlike programs + * written for the ordinary uIP event-driven interface, programs + * written with the protosocket library are executed in a sequential + * fashion and does not have to be implemented as explicit state + * machines. + * + * Protosockets only work with TCP connections. + * + * The protosocket library uses \ref pt protothreads to provide + * sequential control flow. This makes the protosockets lightweight in + * terms of memory, but also means that protosockets inherits the + * functional limitations of protothreads. Each protosocket lives only + * within a single function. Automatic variables (stack variables) are + * not retained across a protosocket library function call. + * + * \note Because the protosocket library uses protothreads, local + * variables will not always be saved across a call to a protosocket + * library function. It is therefore advised that local variables are + * used with extreme care. + * + * The protosocket library provides functions for sending data without + * having to deal with retransmissions and acknowledgements, as well + * as functions for reading data without having to deal with data + * being split across more than one TCP segment. + * + * Because each protosocket runs as a protothread, the protosocket has to be + * started with a call to PSOCK_BEGIN() at the start of the function + * in which the protosocket is used. Similarly, the protosocket protothread can + * be terminated by a call to PSOCK_EXIT(). + * + */ + +/** + * \file + * Protosocket library header file + * \author + * Adam Dunkels + * + */ + +#ifndef __PSOCK_H__ +#define __PSOCK_H__ + +#include "uip.h" +#include "uipopt.h" +#include "pt.h" + + /* + * The structure that holds the state of a buffer. + * + * This structure holds the state of a uIP buffer. The structure has + * no user-visible elements, but is used through the functions + * provided by the library. + * + */ +struct psock_buf { + u8_t *ptr; + unsigned short left; +}; + +/** + * The representation of a protosocket. + * + * The protosocket structrure is an opaque structure with no user-visible + * elements. + */ +struct psock { + struct pt pt, psockpt; /* Protothreads - one that's using the psock + functions, and one that runs inside the + psock functions. */ + const u8_t *sendptr; /* Pointer to the next data to be sent. */ + u8_t *readptr; /* Pointer to the next data to be read. */ + + u8_t *bufptr; /* Pointer to the buffer used for buffering + incoming data. */ + + u16_t sendlen; /* The number of bytes left to be sent. */ + u16_t readlen; /* The number of bytes left to be read. */ + + struct psock_buf buf; /* The structure holding the state of the + input buffer. */ + unsigned int bufsize; /* The size of the input buffer. */ + + unsigned char state; /* The state of the protosocket. */ + + struct uip_stack *ustack; +}; + +void psock_init(struct uip_stack *ustack, + struct psock *psock, u8_t *buffer, unsigned int buffersize); +/** + * Initialize a protosocket. + * + * This macro initializes a protosocket and must be called before the + * protosocket is used. The initialization also specifies the input buffer + * for the protosocket. + * + * \param psock (struct psock *) A pointer to the protosocket to be + * initialized + * + * \param buffer (char *) A pointer to the input buffer for the + * protosocket. + * + * \param buffersize (unsigned int) The size of the input buffer. + * + * \hideinitializer + */ +#define PSOCK_INIT(psock, buffer, buffersize) \ + psock_init(psock, buffer, buffersize) + +/** + * Start the protosocket protothread in a function. + * + * This macro starts the protothread associated with the protosocket and + * must come before other protosocket calls in the function it is used. + * + * \param psock (struct psock *) A pointer to the protosocket to be + * started. + * + * \hideinitializer + */ +#define PSOCK_BEGIN(psock) PT_BEGIN(&((psock)->pt)) + +PT_THREAD(psock_send(struct uip_stack *ustack, + struct psock *psock, const u8_t *buf, unsigned int len)); +/** + * Send data. + * + * This macro sends data over a protosocket. The protosocket protothread blocks + * until all data has been sent and is known to have been received by + * the remote end of the TCP connection. + * + * \param psock (struct psock *) A pointer to the protosocket over which + * data is to be sent. + * + * \param data (char *) A pointer to the data that is to be sent. + * + * \param datalen (unsigned int) The length of the data that is to be + * sent. + * + * \hideinitializer + */ +#define PSOCK_SEND(psock, data, datalen) \ + PT_WAIT_THREAD(&((psock)->pt), psock_send(psock, data, datalen)) + +/** + * \brief Send a null-terminated string. + * \param psock Pointer to the protosocket. + * \param str The string to be sent. + * + * This function sends a null-terminated string over the + * protosocket. + * + * \hideinitializer + */ +#define PSOCK_SEND_STR(psock, str) \ + PT_WAIT_THREAD(&((psock)->pt), psock_send(psock, str, strlen(str))) + +PT_THREAD(psock_generator_send(struct psock *psock, + unsigned short (*f) (void *), void *arg)); + +/** + * \brief Generate data with a function and send it + * \param psock Pointer to the protosocket. + * \param generator Pointer to the generator function + * \param arg Argument to the generator function + * + * This function generates data and sends it over the + * protosocket. This can be used to dynamically generate + * data for a transmission, instead of generating the data + * in a buffer beforehand. This function reduces the need for + * buffer memory. The generator function is implemented by + * the application, and a pointer to the function is given + * as an argument with the call to PSOCK_GENERATOR_SEND(). + * + * The generator function should place the generated data + * directly in the uip_appdata buffer, and return the + * length of the generated data. The generator function is + * called by the protosocket layer when the data first is + * sent, and once for every retransmission that is needed. + * + * \hideinitializer + */ +#define PSOCK_GENERATOR_SEND(psock, generator, arg) \ + PT_WAIT_THREAD(&((psock)->pt), \ + psock_generator_send(psock, generator, arg)) + +/** + * Close a protosocket. + * + * This macro closes a protosocket and can only be called from within the + * protothread in which the protosocket lives. + * + * \param psock (struct psock *) A pointer to the protosocket that is to + * be closed. + * + * \hideinitializer + */ +#define PSOCK_CLOSE(psock) uip_close() + +PT_THREAD(psock_readbuf(struct psock *psock)); +/** + * Read data until the buffer is full. + * + * This macro will block waiting for data and read the data into the + * input buffer specified with the call to PSOCK_INIT(). Data is read + * until the buffer is full.. + * + * \param psock (struct psock *) A pointer to the protosocket from which + * data should be read. + * + * \hideinitializer + */ +#define PSOCK_READBUF(psock) \ + PT_WAIT_THREAD(&((psock)->pt), psock_readbuf(psock)) + +PT_THREAD(psock_readto(struct psock *psock, unsigned char c)); +/** + * Read data up to a specified character. + * + * This macro will block waiting for data and read the data into the + * input buffer specified with the call to PSOCK_INIT(). Data is only + * read until the specifieed character appears in the data stream. + * + * \param psock (struct psock *) A pointer to the protosocket from which + * data should be read. + * + * \param c (char) The character at which to stop reading. + * + * \hideinitializer + */ +#define PSOCK_READTO(psock, c) \ + PT_WAIT_THREAD(&((psock)->pt), psock_readto(psock, c)) + +/** + * The length of the data that was previously read. + * + * This macro returns the length of the data that was previously read + * using PSOCK_READTO() or PSOCK_READ(). + * + * \param psock (struct psock *) A pointer to the protosocket holding the data. + * + * \hideinitializer + */ +#define PSOCK_DATALEN(psock) psock_datalen(psock) + +u16_t psock_datalen(struct psock *psock); + +/** + * Exit the protosocket's protothread. + * + * This macro terminates the protothread of the protosocket and should + * almost always be used in conjunction with PSOCK_CLOSE(). + * + * \sa PSOCK_CLOSE_EXIT() + * + * \param psock (struct psock *) A pointer to the protosocket. + * + * \hideinitializer + */ +#define PSOCK_EXIT(psock) PT_EXIT(&((psock)->pt)) + +/** + * Close a protosocket and exit the protosocket's protothread. + * + * This macro closes a protosocket and exits the protosocket's protothread. + * + * \param psock (struct psock *) A pointer to the protosocket. + * + * \hideinitializer + */ +#define PSOCK_CLOSE_EXIT(psock) \ + do { \ + PSOCK_CLOSE(psock); \ + PSOCK_EXIT(psock); \ + } while (0) + +/** + * Declare the end of a protosocket's protothread. + * + * This macro is used for declaring that the protosocket's protothread + * ends. It must always be used together with a matching PSOCK_BEGIN() + * macro. + * + * \param psock (struct psock *) A pointer to the protosocket. + * + * \hideinitializer + */ +#define PSOCK_END(psock) PT_END(&((psock)->pt)) + +char psock_newdata(struct psock *s); + +/** + * Check if new data has arrived on a protosocket. + * + * This macro is used in conjunction with the PSOCK_WAIT_UNTIL() + * macro to check if data has arrived on a protosocket. + * + * \param psock (struct psock *) A pointer to the protosocket. + * + * \hideinitializer + */ +#define PSOCK_NEWDATA(psock) psock_newdata(psock) + +/** + * Wait until a condition is true. + * + * This macro blocks the protothread until the specified condition is + * true. The macro PSOCK_NEWDATA() can be used to check if new data + * arrives when the protosocket is waiting. + * + * Typically, this macro is used as follows: + * + \code + PT_THREAD(thread(struct psock *s, struct timer *t)) + { + PSOCK_BEGIN(s); + + PSOCK_WAIT_UNTIL(s, PSOCK_NEWADATA(s) || timer_expired(t)); + + if(PSOCK_NEWDATA(s)) { + PSOCK_READTO(s, '\n'); + } else { + handle_timed_out(s); + } + + PSOCK_END(s); + } + \endcode + * + * \param psock (struct psock *) A pointer to the protosocket. + * \param condition The condition to wait for. + * + * \hideinitializer + */ +#define PSOCK_WAIT_UNTIL(psock, condition) \ + PT_WAIT_UNTIL(&((psock)->pt), (condition)); + +#define PSOCK_WAIT_THREAD(psock, condition) \ + PT_WAIT_THREAD(&((psock)->pt), (condition)) + +#endif /* __PSOCK_H__ */ + +/** @} */ diff --git a/iscsiuio/src/uip/pt.h b/iscsiuio/src/uip/pt.h new file mode 100644 index 0000000..ffb1d15 --- /dev/null +++ b/iscsiuio/src/uip/pt.h @@ -0,0 +1,322 @@ +/* + * Copyright (c) 2004-2005, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack + * + * Author: Adam Dunkels + * + */ + +/** + * \addtogroup pt + * @{ + */ + +/** + * \file + * Protothreads implementation. + * \author + * Adam Dunkels + * + */ + +#ifndef __PT_H__ +#define __PT_H__ + +#include "lc.h" + +struct pt { + unsigned short lc; +}; + +#define PT_WAITING 0 +#define PT_EXITED 1 +#define PT_ENDED 2 +#define PT_YIELDED 3 + +/** + * \name Initialization + * @{ + */ + +/** + * Initialize a protothread. + * + * Initializes a protothread. Initialization must be done prior to + * starting to execute the protothread. + * + * \param pt A pointer to the protothread control structure. + * + * \sa PT_SPAWN() + * + * \hideinitializer + */ +#define PT_INIT(pt) LC_INIT((pt)->lc) + +/** @} */ + +/** + * \name Declaration and definition + * @{ + */ + +/** + * Declaration of a protothread. + * + * This macro is used to declare a protothread. All protothreads must + * be declared with this macro. + * + * \param name_args The name and arguments of the C function + * implementing the protothread. + * + * \hideinitializer + */ +#define PT_THREAD(name_args) char name_args + +/** + * Declare the start of a protothread inside the C function + * implementing the protothread. + * + * This macro is used to declare the starting point of a + * protothread. It should be placed at the start of the function in + * which the protothread runs. All C statements above the PT_BEGIN() + * invokation will be executed each time the protothread is scheduled. + * + * \param pt A pointer to the protothread control structure. + * + * \hideinitializer + */ +#define PT_BEGIN(pt) { char PT_YIELD_FLAG __attribute__((__unused__)) = 1; LC_RESUME((pt)->lc) + +/** + * Declare the end of a protothread. + * + * This macro is used for declaring that a protothread ends. It must + * always be used together with a matching PT_BEGIN() macro. + * + * \param pt A pointer to the protothread control structure. + * + * \hideinitializer + */ +#define PT_END(pt) LC_END((pt)->lc); PT_YIELD_FLAG = 0; \ + PT_INIT(pt); return PT_ENDED; } + +/** @} */ + +/** + * \name Blocked wait + * @{ + */ + +/** + * Block and wait until condition is true. + * + * This macro blocks the protothread until the specified condition is + * true. + * + * \param pt A pointer to the protothread control structure. + * \param condition The condition. + * + * \hideinitializer + */ +#define PT_WAIT_UNTIL(pt, condition) \ + do { \ + LC_SET((pt)->lc); \ + if (!(condition)) { \ + return PT_WAITING; \ + } \ + } while (0) + +/** + * Block and wait while condition is true. + * + * This function blocks and waits while condition is true. See + * PT_WAIT_UNTIL(). + * + * \param pt A pointer to the protothread control structure. + * \param cond The condition. + * + * \hideinitializer + */ +#define PT_WAIT_WHILE(pt, cond) PT_WAIT_UNTIL((pt), !(cond)) + +/** @} */ + +/** + * \name Hierarchical protothreads + * @{ + */ + +/** + * Block and wait until a child protothread completes. + * + * This macro schedules a child protothread. The current protothread + * will block until the child protothread completes. + * + * \note The child protothread must be manually initialized with the + * PT_INIT() function before this function is used. + * + * \param pt A pointer to the protothread control structure. + * \param thread The child protothread with arguments + * + * \sa PT_SPAWN() + * + * \hideinitializer + */ +#define PT_WAIT_THREAD(pt, thread) PT_WAIT_WHILE((pt), PT_SCHEDULE(thread)) + +/** + * Spawn a child protothread and wait until it exits. + * + * This macro spawns a child protothread and waits until it exits. The + * macro can only be used within a protothread. + * + * \param pt A pointer to the protothread control structure. + * \param child A pointer to the child protothread's control structure. + * \param thread The child protothread with arguments + * + * \hideinitializer + */ +#define PT_SPAWN(pt, child, thread) \ + do { \ + PT_INIT((child)); \ + PT_WAIT_THREAD((pt), (thread)); \ + } while (0) + +/** @} */ + +/** + * \name Exiting and restarting + * @{ + */ + +/** + * Restart the protothread. + * + * This macro will block and cause the running protothread to restart + * its execution at the place of the PT_BEGIN() call. + * + * \param pt A pointer to the protothread control structure. + * + * \hideinitializer + */ +#define PT_RESTART(pt) \ + do { \ + PT_INIT(pt); \ + return PT_WAITING; \ + } while (0) + +/** + * Exit the protothread. + * + * This macro causes the protothread to exit. If the protothread was + * spawned by another protothread, the parent protothread will become + * unblocked and can continue to run. + * + * \param pt A pointer to the protothread control structure. + * + * \hideinitializer + */ +#define PT_EXIT(pt) \ + do { \ + PT_INIT(pt); \ + return PT_EXITED; \ + } while (0) + +/** @} */ + +/** + * \name Calling a protothread + * @{ + */ + +/** + * Schedule a protothread. + * + * This function shedules a protothread. The return value of the + * function is non-zero if the protothread is running or zero if the + * protothread has exited. + * + * \param f The call to the C function implementing the protothread to + * be scheduled + * + * \hideinitializer + */ +#define PT_SCHEDULE(f) ((f) == PT_WAITING) + +/** @} */ + +/** + * \name Yielding from a protothread + * @{ + */ + +/** + * Yield from the current protothread. + * + * This function will yield the protothread, thereby allowing other + * processing to take place in the system. + * + * \param pt A pointer to the protothread control structure. + * + * \hideinitializer + */ +#define PT_YIELD(pt) \ + do { \ + PT_YIELD_FLAG = 0; \ + LC_SET((pt)->lc); \ + if (PT_YIELD_FLAG == 0) { \ + return PT_YIELDED; \ + } \ + } while (0) + +/** + * \brief Yield from the protothread until a condition occurs. + * \param pt A pointer to the protothread control structure. + * \param cond The condition. + * + * This function will yield the protothread, until the + * specified condition evaluates to true. + * + * + * \hideinitializer + */ +#define PT_YIELD_UNTIL(pt, cond) \ + do { \ + PT_YIELD_FLAG = 0; \ + LC_SET((pt)->lc); \ + if ((PT_YIELD_FLAG == 0) || !(cond)) { \ + return PT_YIELDED; \ + } \ + } while (0) + +/** @} */ + +#endif /* __PT_H__ */ + +/** @} */ diff --git a/iscsiuio/src/uip/timer.c b/iscsiuio/src/uip/timer.c new file mode 100644 index 0000000..da77148 --- /dev/null +++ b/iscsiuio/src/uip/timer.c @@ -0,0 +1,127 @@ +/** + * \addtogroup timer + * @{ + */ + +/** + * \file + * Timer library implementation. + * \author + * Adam Dunkels + */ + +/* + * Copyright (c) 2004, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack + * + * Author: Adam Dunkels + * + */ + +#include "clock.h" +#include "timer.h" + +/*---------------------------------------------------------------------------*/ +/** + * Set a timer. + * + * This function is used to set a timer for a time sometime in the + * future. The function timer_expired() will evaluate to true after + * the timer has expired. + * + * \param t A pointer to the timer + * \param interval The interval before the timer expires. + * + */ +void timer_set(struct timer *t, clock_time_t interval) +{ + t->interval = interval; + t->start = clock_time(); +} + +/*---------------------------------------------------------------------------*/ +/** + * Reset the timer with the same interval. + * + * This function resets the timer with the same interval that was + * given to the timer_set() function. The start point of the interval + * is the exact time that the timer last expired. Therefore, this + * function will cause the timer to be stable over time, unlike the + * timer_rester() function. + * + * \param t A pointer to the timer. + * + * \sa timer_restart() + */ +void timer_reset(struct timer *t) +{ + t->start += t->interval; +} + +/*---------------------------------------------------------------------------*/ +/** + * Restart the timer from the current point in time + * + * This function restarts a timer with the same interval that was + * given to the timer_set() function. The timer will start at the + * current time. + * + * \note A periodic timer will drift if this function is used to reset + * it. For preioric timers, use the timer_reset() function instead. + * + * \param t A pointer to the timer. + * + * \sa timer_reset() + */ +void timer_restart(struct timer *t) +{ + t->start = clock_time(); +} + +/*---------------------------------------------------------------------------*/ +/** + * Check if a timer has expired. + * + * This function tests if a timer has expired and returns true or + * false depending on its status. + * + * \param t A pointer to the timer + * + * \return Non-zero if the timer has expired, zero otherwise. + * + */ +int timer_expired(struct timer *t) +{ + return (clock_time_t) (clock_time() - t->start) >= + (clock_time_t) t->interval; +} + +/*---------------------------------------------------------------------------*/ + +/** @} */ diff --git a/iscsiuio/src/uip/timer.h b/iscsiuio/src/uip/timer.h new file mode 100644 index 0000000..12739fd --- /dev/null +++ b/iscsiuio/src/uip/timer.h @@ -0,0 +1,84 @@ +/** + * \defgroup timer Timer library + * + * The timer library provides functions for setting, resetting and + * restarting timers, and for checking if a timer has expired. An + * application must "manually" check if its timers have expired; this + * is not done automatically. + * + * A timer is declared as a \c struct \c timer and all access to the + * timer is made by a pointer to the declared timer. + * + * \note The timer library uses the \ref clock "Clock library" to + * measure time. Intervals should be specified in the format used by + * the clock library. + * + * @{ + */ + +/** + * \file + * Timer library header file. + * \author + * Adam Dunkels + */ + +/* + * Copyright (c) 2004, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack + * + * Author: Adam Dunkels + * + */ +#ifndef __TIMER_H__ +#define __TIMER_H__ + +#include "clock.h" + +/** + * A timer. + * + * This structure is used for declaring a timer. The timer must be set + * with timer_set() before it can be used. + * + * \hideinitializer + */ +struct timer { + clock_time_t start; + clock_time_t interval; +}; + +void timer_set(struct timer *t, clock_time_t interval); +void timer_reset(struct timer *t); +void timer_restart(struct timer *t); +int timer_expired(struct timer *t); + +#endif /* __TIMER_H__ */ + +/** @} */ diff --git a/iscsiuio/src/uip/uip-neighbor.c b/iscsiuio/src/uip/uip-neighbor.c new file mode 100644 index 0000000..4c80c32 --- /dev/null +++ b/iscsiuio/src/uip/uip-neighbor.c @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack + * + */ + +/** + * \file + * Database of link-local neighbors, used by IPv6 code and + * to be used by a future ARP code rewrite. + * \author + * Adam Dunkels + */ + +#include "logger.h" +#include "uip.h" +#include "uip-neighbor.h" + +#include +#include +#include + +/******************************************************************************* + * Constants + ******************************************************************************/ +#define PFX "uip-neigh " + +#define MAX_TIME 128 + +/*---------------------------------------------------------------------------*/ +void uip_neighbor_init(struct uip_stack *ustack) +{ + int i; + + pthread_mutex_lock(&ustack->lock); + for (i = 0; i < UIP_NEIGHBOR_ENTRIES; ++i) { + memset(&(ustack->neighbor_entries[i].ipaddr), 0, + sizeof(ustack->neighbor_entries[i].ipaddr)); + memset(&(ustack->neighbor_entries[i].mac_addr), 0, + sizeof(ustack->neighbor_entries[i].mac_addr)); + ustack->neighbor_entries[i].time = MAX_TIME; + } + pthread_mutex_unlock(&ustack->lock); +} + +void uip_neighbor_add(struct uip_stack *ustack, + struct in6_addr *addr6, struct uip_eth_addr *addr) +{ + int i, oldest; + u8_t oldest_time; + char buf[INET6_ADDRSTRLEN]; + + inet_ntop(AF_INET6, addr6, buf, sizeof(buf)); + + pthread_mutex_lock(&ustack->lock); + + /* Find the first unused entry or the oldest used entry. */ + oldest_time = 0; + oldest = 0; + for (i = 0; i < UIP_NEIGHBOR_ENTRIES; ++i) { + if (ustack->neighbor_entries[i].time == MAX_TIME) { + oldest = i; + break; + } + if (uip_ip6addr_cmp + (ustack->neighbor_entries[i].ipaddr.s6_addr, addr6)) { + oldest = i; + break; + } + if (ustack->neighbor_entries[i].time > oldest_time) { + oldest = i; + oldest_time = ustack->neighbor_entries[i].time; + } + } + + /* Use the oldest or first free entry (either pointed to by the + "oldest" variable). */ + ustack->neighbor_entries[oldest].time = 0; + uip_ip6addr_copy(ustack->neighbor_entries[oldest].ipaddr.s6_addr, + addr6); + memcpy(&ustack->neighbor_entries[oldest].mac_addr, addr, + sizeof(struct uip_eth_addr)); + + LOG_DEBUG("Adding neighbor %s with " + "mac address %02x:%02x:%02x:%02x:%02x:%02x at %d", + buf, addr->addr[0], addr->addr[1], addr->addr[2], + addr->addr[3], addr->addr[4], addr->addr[5], oldest); + + pthread_mutex_unlock(&ustack->lock); +} + +/*---------------------------------------------------------------------------*/ +static struct neighbor_entry *find_entry(struct uip_stack *ustack, + struct in6_addr *addr6) +{ + int i; + + for (i = 0; i < UIP_NEIGHBOR_ENTRIES; ++i) { + if (uip_ip6addr_cmp + (ustack->neighbor_entries[i].ipaddr.s6_addr, + addr6->s6_addr)) { + return &ustack->neighbor_entries[i]; + } + } + + return NULL; +} + +/*---------------------------------------------------------------------------*/ +void uip_neighbor_update(struct uip_stack *ustack, struct in6_addr *addr6) +{ + struct neighbor_entry *e; + + pthread_mutex_lock(&ustack->lock); + + e = find_entry(ustack, addr6); + if (e != NULL) + e->time = 0; + + pthread_mutex_unlock(&ustack->lock); +} + +/*---------------------------------------------------------------------------*/ +int uip_neighbor_lookup(struct uip_stack *ustack, + struct in6_addr *addr6, uint8_t *mac_addr) +{ + struct neighbor_entry *e; + + pthread_mutex_lock(&ustack->lock); + e = find_entry(ustack, addr6); + if (e != NULL) { + char addr6_str[INET6_ADDRSTRLEN]; + uint8_t *entry_mac_addr; + + addr6_str[0] = '\0'; + inet_ntop(AF_INET6, addr6->s6_addr, addr6_str, + sizeof(addr6_str)); + entry_mac_addr = (uint8_t *)&e->mac_addr.addr; + + LOG_DEBUG(PFX + "Found %s at %02x:%02x:%02x:%02x:%02x:%02x", + addr6_str, + entry_mac_addr[0], entry_mac_addr[1], + entry_mac_addr[2], entry_mac_addr[3], + entry_mac_addr[4], entry_mac_addr[5]); + + memcpy(mac_addr, entry_mac_addr, sizeof(e->mac_addr)); + pthread_mutex_unlock(&ustack->lock); + return 0; + } + + pthread_mutex_unlock(&ustack->lock); + return -ENOENT; +} + +void uip_neighbor_out(struct uip_stack *ustack) +{ + struct neighbor_entry *e; + struct uip_eth_hdr *eth_hdr = + (struct uip_eth_hdr *)ustack->data_link_layer; + struct uip_ipv6_hdr *ipv6_hdr = + (struct uip_ipv6_hdr *)ustack->network_layer; + + pthread_mutex_lock(&ustack->lock); + + /* Find the destination IP address in the neighbor table and construct + the Ethernet header. If the destination IP addres isn't on the + local network, we use the default router's IP address instead. + + If not ARP table entry is found, we overwrite the original IP + packet with an ARP request for the IP address. */ + e = find_entry(ustack, (struct in6_addr *)ipv6_hdr->destipaddr); + if (e == NULL) { + struct uip_eth_addr eth_addr_tmp; + + memcpy(ð_addr_tmp, eth_hdr->src.addr, sizeof(eth_addr_tmp)); + memcpy(eth_hdr->src.addr, ustack->uip_ethaddr.addr, + sizeof(eth_hdr->src.addr)); + memcpy(eth_hdr->dest.addr, ð_addr_tmp, + sizeof(eth_hdr->dest.addr)); + + pthread_mutex_unlock(&ustack->lock); + return; + } + + memcpy(eth_hdr->dest.addr, &e->mac_addr, sizeof(eth_hdr->dest.addr)); + memcpy(eth_hdr->src.addr, ustack->uip_ethaddr.addr, + sizeof(eth_hdr->src.addr)); + + pthread_mutex_unlock(&ustack->lock); +} + +/*---------------------------------------------------------------------------*/ diff --git a/iscsiuio/src/uip/uip-neighbor.h b/iscsiuio/src/uip/uip-neighbor.h new file mode 100644 index 0000000..d10c57b --- /dev/null +++ b/iscsiuio/src/uip/uip-neighbor.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack + * + */ + +/** + * \file + * Header file for database of link-local neighbors, used by + * IPv6 code and to be used by future ARP code. + * \author + * Adam Dunkels + */ + +#ifndef __UIP_NEIGHBOR_H__ +#define __UIP_NEIGHBOR_H__ + +#include "uip.h" +#include "uip_eth.h" + +/* ICMP types */ +/* ICMPv6 error Messages */ +#define ICMPV6_DEST_UNREACH 1 +#define ICMPV6_PKT_TOOBIG 2 +#define ICMPV6_TIME_EXCEED 3 +#define ICMPV6_PARAMPROB 4 + +/* ICMPv6 Informational Messages */ +#define ICMPV6_ECHO_REQUEST 128 +#define ICMPV6_ECHO_REPLY 129 +#define ICMPV6_MGM_QUERY 130 +#define ICMPV6_MGM_REPORT 131 +#define ICMPV6_MGM_REDUCTION 132 + +/* Codes for Destination Unreachable */ +#define ICMPV6_NOROUTE 0 +#define ICMPV6_ADM_PROHIBITED 1 +#define ICMPV6_NOT_NEIGHBOUR 2 +#define ICMPV6_ADDR_UNREACH 3 +#define ICMPV6_PORT_UNREACH 4 + +/* Codes for Time Exceeded */ +#define ICMPV6_EXC_HOPLIMIT 0 +#define ICMPV6_EXC_FRAGTIME 1 + +/* Codes for Parameter Problem */ +#define ICMPV6_HDR_FIELD 0 +#define ICMPV6_UNK_NEXTHDR 1 +#define ICMPV6_UNK_OPTION 2 + +#if 0 +struct __attribute__ ((__packed__)) icmpv6_hdr { + u8_t type; + u8_t code; + u16_t checksum; + union { + struct { + u16_t id; + u16_t sequence; + } echo; + u32_t gateway; + struct { + u16_t unused; + u16_t mtu; + } frag; + } un; +}; +#endif + +void uip_neighbor_init(struct uip_stack *ustack); +void uip_neighbor_add(struct uip_stack *ustack, + struct in6_addr *addr6, struct uip_eth_addr *addr); +void uip_neighbor_update(struct uip_stack *ustack, struct in6_addr *addr6); +int uip_neighbor_lookup(struct uip_stack *ustack, struct in6_addr *ipaddr, + uint8_t *mac_addr); +void uip_neighbor_periodic(void); +void uip_neighbor_out(struct uip_stack *ustack); + +#endif /* __UIP-NEIGHBOR_H__ */ diff --git a/iscsiuio/src/uip/uip.c b/iscsiuio/src/uip/uip.c new file mode 100644 index 0000000..e2ce2cc --- /dev/null +++ b/iscsiuio/src/uip/uip.c @@ -0,0 +1,2413 @@ +#include +#include +#include +#include +#include +#include "uip.h" +#include "dhcpc.h" +#include "ipv6_ndpc.h" +#include "brcm_iscsi.h" +#include "ping.h" + +/** + * \defgroup uip The uIP TCP/IP stack + * @{ + * + * uIP is an implementation of the TCP/IP protocol stack intended for + * small 8-bit and 16-bit microcontrollers. + * + * uIP provides the necessary protocols for Internet communication, + * with a very small code footprint and RAM requirements - the uIP + * code size is on the order of a few kilobytes and RAM usage is on + * the order of a few hundred bytes. + */ + +/** + * \file + * The uIP TCP/IP stack code. + * \author Adam Dunkels + */ + +/* + * Copyright (c) 2001-2003, Adam Dunkels. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack. + * + * + */ + +/* + * uIP is a small implementation of the IP, UDP and TCP protocols (as + * well as some basic ICMP stuff). The implementation couples the IP, + * UDP, TCP and the application layers very tightly. To keep the size + * of the compiled code down, this code frequently uses the goto + * statement. While it would be possible to break the uip_process() + * function into many smaller functions, this would increase the code + * size because of the overhead of parameter passing and the fact that + * the optimier would not be as efficient. + * + * The principle is that we have a small buffer, called the uip_buf, + * in which the device driver puts an incoming packet. The TCP/IP + * stack parses the headers in the packet, and calls the + * application. If the remote host has sent data to the application, + * this data is present in the uip_buf and the application read the + * data from there. It is up to the application to put this data into + * a byte stream if needed. The application will not be fed with data + * that is out of sequence. + * + * If the application whishes to send data to the peer, it should put + * its data into the uip_buf. The uip_appdata pointer points to the + * first available byte. The TCP/IP stack will calculate the + * checksums, and fill in the necessary header fields and finally send + * the packet back to the peer. +*/ + +#include "logger.h" + +#include "uip.h" +#include "uipopt.h" +#include "uip_arch.h" +#include "uip_eth.h" +#include "uip-neighbor.h" + +#include + +/******************************************************************************* + * Constants + ******************************************************************************/ +#define PFX "uip " + +static const uip_ip4addr_t all_ones_addr4 = { 0xffff, 0xffff }; + +const uip_ip6addr_t all_zeroes_addr6 = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 +}; +const uip_ip4addr_t all_zeroes_addr4 = { 0x0000, 0x0000 }; + +const uint8_t mutlicast_ipv6_prefix[16] = { + 0xfc, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +const uint8_t link_local_addres_prefix[16] = { + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +const uint32_t link_local_address_prefix_length = 10; + +/* Structures and definitions. */ +#define TCP_FIN 0x01 +#define TCP_SYN 0x02 +#define TCP_RST 0x04 +#define TCP_PSH 0x08 +#define TCP_ACK 0x10 +#define TCP_URG 0x20 +#define TCP_CTL 0x3f + +#define TCP_OPT_END 0 /* End of TCP options list */ +#define TCP_OPT_NOOP 1 /* "No-operation" TCP option */ +#define TCP_OPT_MSS 2 /* Maximum segment size TCP option */ + +#define TCP_OPT_MSS_LEN 4 /* Length of TCP MSS option. */ + +#define ICMP_ECHO_REPLY 0 +#define ICMP_ECHO 8 + +#define ICMP6_ECHO_REPLY 129 +#define ICMP6_ECHO 128 +#define ICMP6_NEIGHBOR_SOLICITATION 135 +#define ICMP6_NEIGHBOR_ADVERTISEMENT 136 + +#define ICMP6_FLAG_S (1 << 6) + +#define ICMP6_OPTION_SOURCE_LINK_ADDRESS 1 +#define ICMP6_OPTION_TARGET_LINK_ADDRESS 2 + +/* Macros. */ +#define FBUF ((struct uip_tcpip_hdr *)&uip_reassbuf[0]) +#define UDPBUF(ustack) ((struct uip_udpip_hdr *)ustack->network_layer) + +/****************************************************************************** + * Utility Functions + *****************************************************************************/ +static int is_ipv6(struct uip_stack *ustack) +{ + u16_t type; + + type = ETH_BUF(ustack->uip_buf)->type; + type = ntohs(type); + if (type == UIP_ETHTYPE_8021Q) + type = ntohs(VLAN_ETH_BUF(ustack->uip_buf)->type); + else + type = ntohs(ETH_BUF(ustack->uip_buf)->type); + + return (type == UIP_ETHTYPE_IPv6); +} + +int is_ipv6_link_local_address(uip_ip6addr_t *addr) +{ + u8_t *test_adddr = (u8_t *) addr; + u8_t test_remainder; + + if (test_adddr[0] != link_local_addres_prefix[0]) + return 0; + + test_remainder = (test_adddr[1] & 0xC0) >> 6; + if (test_remainder != 2) + return 0; + + return 1; +} + +void uip_sethostaddr4(struct uip_stack *ustack, uip_ip4addr_t *addr) +{ + pthread_mutex_lock(&ustack->lock); + uip_ip4addr_copy(ustack->hostaddr, (addr)); + pthread_mutex_unlock(&ustack->lock); +} + +void uip_setdraddr4(struct uip_stack *ustack, uip_ip4addr_t *addr) +{ + pthread_mutex_lock(&ustack->lock); + uip_ip4addr_copy(ustack->default_route_addr, (addr)); + pthread_mutex_unlock(&ustack->lock); +} + +void uip_setnetmask4(struct uip_stack *ustack, uip_ip4addr_t *addr) +{ + pthread_mutex_lock(&ustack->lock); + uip_ip4addr_copy(ustack->netmask, (addr)); + pthread_mutex_unlock(&ustack->lock); +} + +void uip_setethernetmac(struct uip_stack *ustack, uint8_t *mac) +{ + pthread_mutex_lock(&ustack->lock); + memcpy(ustack->uip_ethaddr.addr, (mac), 6); + pthread_mutex_unlock(&ustack->lock); +} + +void set_uip_stack(struct uip_stack *ustack, + uip_ip4addr_t *ip, + uip_ip4addr_t *netmask, + uip_ip4addr_t *default_route, uint8_t *mac_addr) +{ + if (ip) + uip_sethostaddr4(ustack, ip); + if (netmask) + uip_setnetmask4(ustack, netmask); + if (default_route) + uip_setdraddr4(ustack, default_route); + if (mac_addr) + uip_setethernetmac(ustack, mac_addr); +} + +#if !UIP_ARCH_ADD32 +void uip_add32(u8_t *op32, u16_t op16, u8_t *uip_acc32) +{ + uip_acc32[3] = op32[3] + (op16 & 0xff); + uip_acc32[2] = op32[2] + (op16 >> 8); + uip_acc32[1] = op32[1]; + uip_acc32[0] = op32[0]; + + if (uip_acc32[2] < (op16 >> 8)) { + ++uip_acc32[1]; + if (uip_acc32[1] == 0) + ++uip_acc32[0]; + } + + if (uip_acc32[3] < (op16 & 0xff)) { + ++uip_acc32[2]; + if (uip_acc32[2] == 0) { + ++uip_acc32[1]; + if (uip_acc32[1] == 0) + ++uip_acc32[0]; + } + } +} + +#endif /* UIP_ARCH_ADD32 */ + +#if !UIP_ARCH_CHKSUM +/*---------------------------------------------------------------------------*/ +static u16_t chksum(u16_t sum, const u8_t *data, u16_t len) +{ + u16_t t; + const u8_t *dataptr; + const u8_t *last_byte; + + dataptr = data; + last_byte = data + len - 1; + + while (dataptr < last_byte) { /* At least two more bytes */ + t = (dataptr[0] << 8) + dataptr[1]; + sum += t; + if (sum < t) + sum++; /* carry */ + dataptr += 2; + } + + if (dataptr == last_byte) { + t = (dataptr[0] << 8) + 0; + sum += t; + if (sum < t) + sum++; /* carry */ + } + + /* Return sum in host byte order. */ + return sum; +} + +/*---------------------------------------------------------------------------*/ +u16_t uip_chksum(u16_t *data, u16_t len) +{ + return htons(chksum(0, (u8_t *)data, len)); +} + +/*---------------------------------------------------------------------------*/ +#ifndef UIP_ARCH_IPCHKSUM +u16_t uip_ipchksum(struct uip_stack *ustack) +{ + u16_t sum; + u16_t uip_iph_len; + + if (is_ipv6(ustack)) + uip_iph_len = UIP_IPv6_H_LEN; + else + uip_iph_len = UIP_IPv4_H_LEN; + + sum = chksum(0, ustack->network_layer, uip_iph_len); + return (sum == 0) ? 0xffff : htons(sum); +} +#endif + +/*---------------------------------------------------------------------------*/ +static u16_t upper_layer_chksum_ipv4(struct uip_stack *ustack, u8_t proto) +{ + u16_t upper_layer_len; + u16_t sum; + struct uip_tcp_ipv4_hdr *tcp_ipv4_hdr = NULL; + + tcp_ipv4_hdr = (struct uip_tcp_ipv4_hdr *)ustack->network_layer; + + upper_layer_len = (((u16_t) (tcp_ipv4_hdr->len[0]) << 8) + + tcp_ipv4_hdr->len[1]) - UIP_IPv4_H_LEN; + + /* First sum pseudoheader. */ + /* IP protocol and length fields. This addition cannot carry. */ + sum = upper_layer_len + proto; + + sum = + chksum(sum, (u8_t *)&tcp_ipv4_hdr->srcipaddr[0], + 2 * sizeof(uip_ip4addr_t)); + /* Sum TCP header and data. */ + sum = chksum(sum, ustack->network_layer + UIP_IPv4_H_LEN, + upper_layer_len); + + return (sum == 0) ? 0xffff : htons(sum); +} + +/*---------------------------------------------------------------------------*/ +static uint16_t upper_layer_checksum_ipv6(uint8_t *data, uint8_t proto) +{ + uint16_t upper_layer_len; + uint16_t sum; + struct ip6_hdr *ipv6_hdr; + uint8_t *upper_layer; + uint32_t val; + + ipv6_hdr = (struct ip6_hdr *)data; + + upper_layer_len = ntohs(ipv6_hdr->ip6_plen); + + /* First sum pseudoheader. */ + sum = 0; + sum = chksum(sum, (const u8_t *)ipv6_hdr->ip6_src.s6_addr, + sizeof(ipv6_hdr->ip6_src)); + sum = chksum(sum, (const u8_t *)ipv6_hdr->ip6_dst.s6_addr, + sizeof(ipv6_hdr->ip6_dst)); + + val = htons(upper_layer_len); + sum = chksum(sum, (u8_t *)&val, sizeof(val)); + + val = htons(proto); + sum = chksum(sum, (u8_t *)&val, sizeof(val)); + + upper_layer = (uint8_t *)(ipv6_hdr + 1); + sum = chksum(sum, upper_layer, upper_layer_len); + + return (sum == 0) ? 0xffff : htons(sum); +} + +/*---------------------------------------------------------------------------*/ + +u16_t uip_icmp6chksum(struct uip_stack *ustack) +{ + uint8_t *data = ustack->network_layer; + + return upper_layer_checksum_ipv6(data, UIP_PROTO_ICMP6); +} + +uint16_t icmpv6_checksum(uint8_t *data) +{ + return upper_layer_checksum_ipv6(data, IPPROTO_ICMPV6); +} + +/*---------------------------------------------------------------------------*/ +u16_t uip_tcpchksum(struct uip_stack *ustack) +{ + return upper_layer_chksum_ipv4(ustack, UIP_PROTO_TCP); +} + +/*---------------------------------------------------------------------------*/ +#if UIP_UDP_CHECKSUMS +static u16_t uip_udpchksum_ipv4(struct uip_stack *ustack) +{ + return upper_layer_chksum_ipv4(ustack, UIP_PROTO_UDP); +} + +static u16_t uip_udpchksum_ipv6(struct uip_stack *ustack) +{ + uint8_t *data = ustack->network_layer; + + return upper_layer_checksum_ipv6(data, UIP_PROTO_UDP); +} + +u16_t uip_udpchksum(struct uip_stack *ustack) +{ + if (is_ipv6(ustack)) + return uip_udpchksum_ipv6(ustack); + else + return uip_udpchksum_ipv4(ustack); +} +#endif /* UIP_UDP_CHECKSUMS */ +#endif /* UIP_ARCH_CHKSUM */ +/*---------------------------------------------------------------------------*/ +void uip_init(struct uip_stack *ustack, uint8_t ipv6_enabled) +{ + u8_t c; + + for (c = 0; c < UIP_LISTENPORTS; ++c) + ustack->uip_listenports[c] = 0; + for (c = 0; c < UIP_CONNS; ++c) + ustack->uip_conns[c].tcpstateflags = UIP_CLOSED; +#if UIP_ACTIVE_OPEN + ustack->lastport = 1024; +#endif /* UIP_ACTIVE_OPEN */ + +#if UIP_UDP + for (c = 0; c < UIP_UDP_CONNS; ++c) + ustack->uip_udp_conns[c].lport = 0; +#endif /* UIP_UDP */ + + /* IPv4 initialization. */ +#if UIP_FIXEDADDR == 0 + /* uip_hostaddr[0] = uip_hostaddr[1] = 0; */ +#endif /* UIP_FIXEDADDR */ + + /* zero out the uIP statistics */ + memset(&ustack->stats, 0, sizeof(ustack->stats)); + + /* prepare the uIP lock */ + pthread_mutex_init(&ustack->lock, NULL); + + if (ipv6_enabled) + ustack->enable_IPv6 = UIP_SUPPORT_IPv6_ENABLED; + else + ustack->enable_IPv6 = UIP_SUPPORT_IPv6_DISABLED; + + ustack->dhcpc = NULL; + ustack->ndpc = NULL; + ustack->ping_conf = NULL; +} +void uip_reset(struct uip_stack *ustack) +{ + /* There was an associated DHCP object, this memory needs to be + * freed */ + if (ustack->dhcpc) + free(ustack->dhcpc); + + ndpc_exit(ustack->ndpc); + + memset(ustack, 0, sizeof(*ustack)); +} + +/*---------------------------------------------------------------------------*/ +#if UIP_ACTIVE_OPEN +struct uip_conn *uip_connect(struct uip_stack *ustack, uip_ip4addr_t *ripaddr, + u16_t rport) +{ + u8_t c; + register struct uip_conn *conn, *cconn; + + /* Find an unused local port. */ +again: + ++ustack->lastport; + + if (ustack->lastport >= 32000) + ustack->lastport = 4096; + + /* Check if this port is already in use, and if so try to find + another one. */ + for (c = 0; c < UIP_CONNS; ++c) { + conn = &ustack->uip_conns[c]; + if (conn->tcpstateflags != UIP_CLOSED && + conn->lport == htons(ustack->lastport)) { + goto again; + } + } + + conn = 0; + for (c = 0; c < UIP_CONNS; ++c) { + cconn = &ustack->uip_conns[c]; + if (cconn->tcpstateflags == UIP_CLOSED) { + conn = cconn; + break; + } + if (cconn->tcpstateflags == UIP_TIME_WAIT) { + if (conn == 0 || cconn->timer > conn->timer) + conn = cconn; + } + } + + if (conn == 0) + return 0; + + conn->tcpstateflags = UIP_SYN_SENT; + + conn->snd_nxt[0] = ustack->iss[0]; + conn->snd_nxt[1] = ustack->iss[1]; + conn->snd_nxt[2] = ustack->iss[2]; + conn->snd_nxt[3] = ustack->iss[3]; + + conn->initialmss = conn->mss = UIP_TCP_MSS; + + conn->len = 1; /* TCP length of the SYN is one. */ + conn->nrtx = 0; + conn->timer = 1; /* Send the SYN next time around. */ + conn->rto = UIP_RTO; + conn->sa = 0; + conn->sv = 16; /* Initial value of the RTT variance. */ + conn->lport = htons(ustack->lastport); + conn->rport = rport; + uip_ip4addr_copy(&conn->ripaddr, ripaddr); + + return conn; +} +#endif /* UIP_ACTIVE_OPEN */ +/*---------------------------------------------------------------------------*/ +#if UIP_UDP +struct uip_udp_conn *uip_udp_new(struct uip_stack *ustack, + uip_ip4addr_t *ripaddr, u16_t rport) +{ + u8_t c; + register struct uip_udp_conn *conn; + + /* Find an unused local port. */ +again: + ++ustack->lastport; + + if (ustack->lastport >= 32000) + ustack->lastport = 4096; + + for (c = 0; c < UIP_UDP_CONNS; ++c) { + if (ustack->uip_udp_conns[c].lport == htons(ustack->lastport)) + goto again; + } + + conn = 0; + for (c = 0; c < UIP_UDP_CONNS; ++c) { + if (ustack->uip_udp_conns[c].lport == 0) { + conn = &ustack->uip_udp_conns[c]; + break; + } + } + + if (conn == 0) + return 0; + + conn->lport = htons(ustack->lastport); + conn->rport = rport; + if (ripaddr == NULL) + memset(conn->ripaddr, 0, sizeof(uip_ip4addr_t)); + else + uip_ip4addr_copy(&conn->ripaddr, ripaddr); + conn->ttl = UIP_TTL; + + return conn; +} +#endif /* UIP_UDP */ +/*---------------------------------------------------------------------------*/ +void uip_unlisten(struct uip_stack *ustack, u16_t port) +{ + u8_t c; + + for (c = 0; c < UIP_LISTENPORTS; ++c) { + if (ustack->uip_listenports[c] == port) { + ustack->uip_listenports[c] = 0; + return; + } + } +} + +/*---------------------------------------------------------------------------*/ +void uip_listen(struct uip_stack *ustack, u16_t port) +{ + u8_t c; + + for (c = 0; c < UIP_LISTENPORTS; ++c) { + if (ustack->uip_listenports[c] == 0) { + ustack->uip_listenports[c] = port; + return; + } + } +} + +/** + * Is new incoming data available? + * + * Will reduce to non-zero if there is new data for the application + * present at the uip_appdata pointer. The size of the data is + * avaliable through the uip_len variable. + * + * \hideinitializer + */ +int uip_newdata(struct uip_stack *ustack) +{ + return ustack->uip_flags & UIP_NEWDATA; +} + +/** + * Has previously sent data been acknowledged? + * + * Will reduce to non-zero if the previously sent data has been + * acknowledged by the remote host. This means that the application + * can send new data. + * + * \hideinitializer + */ +#define uip_acked() (uip_flags & UIP_ACKDATA) + +/** + * Has the connection just been connected? + * + * Reduces to non-zero if the current connection has been connected to + * a remote host. This will happen both if the connection has been + * actively opened (with uip_connect()) or passively opened (with + * uip_listen()). + * + * \hideinitializer + */ +int uip_connected(struct uip_stack *ustack) +{ + return ustack->uip_flags & UIP_CONNECTED; +} + +/** + * Has the connection been closed by the other end? + * + * Is non-zero if the connection has been closed by the remote + * host. The application may then do the necessary clean-ups. + * + * \hideinitializer + */ +int uip_closed(struct uip_stack *ustack) +{ + return ustack->uip_flags & UIP_CLOSE; +} + +/** + * Has the connection been aborted by the other end? + * + * Non-zero if the current connection has been aborted (reset) by the + * remote host. + * + * \hideinitializer + */ +int uip_aborted(struct uip_stack *ustack) +{ + return ustack->uip_flags & UIP_ABORT; +} + +/** + * Has the connection timed out? + * + * Non-zero if the current connection has been aborted due to too many + * retransmissions. + * + * \hideinitializer + */ +int uip_timedout(struct uip_stack *ustack) +{ + return ustack->uip_flags & UIP_TIMEDOUT; +} + +/** + * Do we need to retransmit previously data? + * + * Reduces to non-zero if the previously sent data has been lost in + * the network, and the application should retransmit it. The + * application should send the exact same data as it did the last + * time, using the uip_send() function. + * + * \hideinitializer + */ +int uip_rexmit(struct uip_stack *ustack) +{ + return ustack->uip_flags & UIP_REXMIT; +} + +/** + * Is the connection being polled by uIP? + * + * Is non-zero if the reason the application is invoked is that the + * current connection has been idle for a while and should be + * polled. + * + * The polling event can be used for sending data without having to + * wait for the remote host to send data. + * + * \hideinitializer + */ +int uip_poll(struct uip_stack *ustack) +{ + return ustack->uip_flags & UIP_POLL; +} + +int uip_initialmss(struct uip_stack *ustack) +{ + return ustack->uip_conn->initialmss; +} + +int uip_mss(struct uip_stack *ustack) +{ + return ustack->uip_conn->mss; +} + +/*---------------------------------------------------------------------------*/ +/* XXX: IP fragment reassembly: not well-tested. */ + +#if UIP_REASSEMBLY && !UIP_CONF_IPV6 +#define UIP_REASS_BUFSIZE (UIP_BUFSIZE - UIP_LLH_LEN) +static u8_t uip_reassbuf[UIP_REASS_BUFSIZE]; +static u8_t uip_reassbitmap[UIP_REASS_BUFSIZE / (8 * 8)]; +static const u8_t bitmap_bits[8] = { 0xff, 0x7f, 0x3f, 0x1f, + 0x0f, 0x07, 0x03, 0x01 +}; +static u16_t uip_reasslen; +static u8_t uip_reassflags; +#define UIP_REASS_FLAG_LASTFRAG 0x01 +static u8_t uip_reasstmr; + +#define IP_MF 0x20 + +static u8_t uip_reass(void) +{ + u16_t offset, len; + u16_t i; + + /* If ip_reasstmr is zero, no packet is present in the buffer, so we + write the IP header of the fragment into the reassembly + buffer. The timer is updated with the maximum age. */ + if (uip_reasstmr == 0) { + memcpy(uip_reassbuf, &BUF(ustack)->vhl, uip_iph_len); + uip_reasstmr = UIP_REASS_MAXAGE; + uip_reassflags = 0; + /* Clear the bitmap. */ + memset(uip_reassbitmap, 0, sizeof(uip_reassbitmap)); + } + + /* Check if the incoming fragment matches the one currently present + in the reasembly buffer. If so, we proceed with copying the + fragment into the buffer. */ + if (BUF(ustack)->srcipaddr[0] == FBUF(ustack)->srcipaddr[0] && + BUF(ustack)->srcipaddr[1] == FBUF(ustack)->srcipaddr[1] && + BUF(ustack)->destipaddr[0] == FBUF(ustack)->destipaddr[0] && + BUF(ustack)->destipaddr[1] == FBUF(ustack)->destipaddr[1] && + BUF(ustack)->ipid[0] == FBUF(ustack)->ipid[0] && + BUF(ustack)->ipid[1] == FBUF(ustack)->ipid[1]) { + + len = + (BUF(ustack)->len[0] << 8) + BUF(ustack)->len[1] - + (BUF(ustack)->vhl & 0x0f) * 4; + offset = + (((BUF(ustack)->ipoffset[0] & 0x3f) << 8) + + BUF(ustack)->ipoffset[1]) * 8; + + /* If the offset or the offset + fragment length overflows the + reassembly buffer, we discard the entire packet. */ + if (offset > UIP_REASS_BUFSIZE || + offset + len > UIP_REASS_BUFSIZE) { + uip_reasstmr = 0; + goto nullreturn; + } + + /* Copy the fragment into the reassembly buffer, at the right + offset. */ + memcpy(&uip_reassbuf[uip_iph_len + offset], + (char *)BUF + (int)((BUF(ustack)->vhl & 0x0f) * 4), len); + + /* Update the bitmap. */ + if (offset / (8 * 8) == (offset + len) / (8 * 8)) { + /* If the two endpoints are in the same byte, we only + update that byte. */ + + uip_reassbitmap[offset / (8 * 8)] |= + bitmap_bits[(offset / 8) & 7] & + ~bitmap_bits[((offset + len) / 8) & 7]; + } else { + /* If the two endpoints are in different bytes, we + update the bytes in the endpoints and fill the + stuff inbetween with 0xff. */ + uip_reassbitmap[offset / (8 * 8)] |= + bitmap_bits[(offset / 8) & 7]; + for (i = 1 + offset / (8 * 8); + i < (offset + len) / (8 * 8); ++i) { + uip_reassbitmap[i] = 0xff; + } + uip_reassbitmap[(offset + len) / (8 * 8)] |= + ~bitmap_bits[((offset + len) / 8) & 7]; + } + + /* If this fragment has the More Fragments flag set to zero, we + know that this is the last fragment, so we can calculate the + size of the entire packet. We also set the + IP_REASS_FLAG_LASTFRAG flag to indicate that we have received + the final fragment. */ + + if ((BUF(ustack)->ipoffset[0] & IP_MF) == 0) { + uip_reassflags |= UIP_REASS_FLAG_LASTFRAG; + uip_reasslen = offset + len; + } + + /* Finally, we check if we have a full packet in the buffer. + We do this by checking if we have the last fragment and if + all bits in the bitmap are set. */ + if (uip_reassflags & UIP_REASS_FLAG_LASTFRAG) { + /* Check all bytes up to and including all but the last + byte in the bitmap. */ + for (i = 0; i < uip_reasslen / (8 * 8) - 1; ++i) { + if (uip_reassbitmap[i] != 0xff) + goto nullreturn; + } + /* Check the last byte in the bitmap. It should contain + just the right amount of bits. */ + if (uip_reassbitmap[uip_reasslen / (8 * 8)] != + (u8_t) ~bitmap_bits[uip_reasslen / 8 & 7]) + goto nullreturn; + + /* If we have come this far, we have a full packet in + the buffer, so we allocate a pbuf and copy the + packet into it. We also reset the timer. */ + uip_reasstmr = 0; + memcpy(BUF, FBUF, uip_reasslen); + + /* Pretend to be a "normal" (i.e., not fragmented) IP + packet from now on. */ + BUF(ustack)->ipoffset[0] = BUF(ustack)->ipoffset[1] = 0; + BUF(ustack)->len[0] = uip_reasslen >> 8; + BUF(ustack)->len[1] = uip_reasslen & 0xff; + BUF(ustack)->ipchksum = 0; + BUF(ustack)->ipchksum = ~(uip_ipchksum()); + + return uip_reasslen; + } + } + +nullreturn: + return 0; +} +#endif /* UIP_REASSEMBLY */ +/*---------------------------------------------------------------------------*/ +static void uip_add_rcv_nxt(struct uip_stack *ustack, u16_t n) +{ + u8_t uip_acc32[4]; + + uip_add32(ustack->uip_conn->rcv_nxt, n, uip_acc32); + ustack->uip_conn->rcv_nxt[0] = uip_acc32[0]; + ustack->uip_conn->rcv_nxt[1] = uip_acc32[1]; + ustack->uip_conn->rcv_nxt[2] = uip_acc32[2]; + ustack->uip_conn->rcv_nxt[3] = uip_acc32[3]; +} + +/*---------------------------------------------------------------------------*/ + +/** @} */ + +/** + * \defgroup uipdevfunc uIP device driver functions + * @{ + * + * These functions are used by a network device driver for interacting + * with uIP. + */ + +/** + * Process an incoming packet. + * + * This function should be called when the device driver has received + * a packet from the network. The packet from the device driver must + * be present in the uip_buf buffer, and the length of the packet + * should be placed in the uip_len variable. + * + * When the function returns, there may be an outbound packet placed + * in the uip_buf packet buffer. If so, the uip_len variable is set to + * the length of the packet. If no packet is to be sent out, the + * uip_len variable is set to 0. + * + * The usual way of calling the function is presented by the source + * code below. + \code + uip_len = devicedriver_poll(); + if(uip_len > 0) { + uip_input(); + if(uip_len > 0) { + devicedriver_send(); + } + } + \endcode + * + * \note If you are writing a uIP device driver that needs ARP + * (Address Resolution Protocol), e.g., when running uIP over + * Ethernet, you will need to call the uIP ARP code before calling + * this function: + \code + #define BUF ((struct uip_eth_hdr *)&uip_buf[0]) + uip_len = ethernet_devicedrver_poll(); + if(uip_len > 0) { + if (BUF(ustack)->type == HTONS(UIP_ETHTYPE_IP)) { + uip_arp_ipin(); + uip_input(); + if (uip_len > 0) { + uip_arp_out(); + ethernet_devicedriver_send(); + } + } else if (BUF(ustack)->type == HTONS(UIP_ETHTYPE_ARP)) { + uip_arp_arpin(); + if (uip_len > 0) + ethernet_devicedriver_send(); + } + \endcode + * + * \hideinitializer + */ +void uip_input(struct uip_stack *ustack) +{ + uip_process(ustack, UIP_DATA); +} + +/** + * Periodic processing for a connection identified by its number. + * + * This function does the necessary periodic processing (timers, + * polling) for a uIP TCP conneciton, and should be called when the + * periodic uIP timer goes off. It should be called for every + * connection, regardless of whether they are open of closed. + * + * When the function returns, it may have an outbound packet waiting + * for service in the uIP packet buffer, and if so the uip_len + * variable is set to a value larger than zero. The device driver + * should be called to send out the packet. + * + * The ususal way of calling the function is through a for() loop like + * this: + \code + for(i = 0; i < UIP_CONNS; ++i) { + uip_periodic(i); + if(uip_len > 0) { + devicedriver_send(); + } + } + \endcode + * + * \note If you are writing a uIP device driver that needs ARP + * (Address Resolution Protocol), e.g., when running uIP over + * Ethernet, you will need to call the uip_arp_out() function before + * calling the device driver: + \code + for(i = 0; i < UIP_CONNS; ++i) { + uip_periodic(i); + if(uip_len > 0) { + uip_arp_out(); + ethernet_devicedriver_send(); + } + } + \endcode + * + * \param conn The number of the connection which is to be periodically polled. + * + * \hideinitializer + */ +void uip_periodic(struct uip_stack *ustack, int conn) +{ + ustack->uip_conn = &ustack->uip_conns[conn]; + uip_process(ustack, UIP_TIMER); +} + +#if UIP_UDP +/** + * Periodic processing for a UDP connection identified by its number. + * + * This function is essentially the same as uip_periodic(), but for + * UDP connections. It is called in a similar fashion as the + * uip_periodic() function: + \code + for(i = 0; i < UIP_UDP_CONNS; i++) { + uip_udp_periodic(i); + if(uip_len > 0) { + devicedriver_send(); + } + } + \endcode + * + * \note As for the uip_periodic() function, special care has to be + * taken when using uIP together with ARP and Ethernet: + \code + for(i = 0; i < UIP_UDP_CONNS; i++) { + uip_udp_periodic(i); + if(uip_len > 0) { + uip_arp_out(); + ethernet_devicedriver_send(); + } + } + \endcode + * + * \param conn The number of the UDP connection to be processed. + * + * \hideinitializer + */ +void uip_udp_periodic(struct uip_stack *ustack, int conn) +{ + ustack->uip_udp_conn = &ustack->uip_udp_conns[conn]; + uip_process(ustack, UIP_UDP_TIMER); +} +#endif + +void uip_ndp_periodic(struct uip_stack *ustack) +{ + uip_process(ustack, UIP_NDP_TIMER); +} + +void uip_process(struct uip_stack *ustack, u8_t flag) +{ + u8_t c; + u16_t tmp16; + register struct uip_conn *uip_connr = ustack->uip_conn; + + u16_t uip_iph_len = 0; + u16_t uip_ip_udph_len = 0; + u16_t uip_ip_tcph_len = 0; + struct ip6_hdr *ipv6_hdr = NULL; + struct uip_tcp_ipv4_hdr *tcp_ipv4_hdr = NULL; + struct uip_tcp_hdr *tcp_hdr = NULL; + struct uip_icmpv4_hdr *icmpv4_hdr = NULL; + struct uip_icmpv6_hdr *icmpv6_hdr __attribute__((__unused__)) = NULL; + struct uip_udp_hdr *udp_hdr = NULL; + + /* Drop invalid packets */ + if (ustack->uip_buf == NULL) { + LOG_ERR(PFX "ustack->uip_buf == NULL."); + return; + } + + if (is_ipv6(ustack)) { + uint8_t *buf; + uip_iph_len = UIP_IPv6_H_LEN; + uip_ip_udph_len = UIP_IPv6_UDPH_LEN; + uip_ip_tcph_len = UIP_IPv6_TCPH_LEN; + + ipv6_hdr = (struct ip6_hdr *)ustack->network_layer; + + buf = ustack->network_layer; + buf += sizeof(struct uip_ipv6_hdr); + tcp_hdr = (struct uip_tcp_hdr *)buf; + + buf = ustack->network_layer; + buf += sizeof(struct uip_ipv6_hdr); + udp_hdr = (struct uip_udp_hdr *)buf; + + buf = ustack->network_layer; + buf += sizeof(struct uip_ipv6_hdr); + icmpv6_hdr = (struct uip_icmpv6_hdr *)buf; + } else { + uint8_t *buf; + + uip_iph_len = UIP_IPv4_H_LEN; + uip_ip_udph_len = UIP_IPv4_UDPH_LEN; + uip_ip_tcph_len = UIP_IPv4_TCPH_LEN; + + tcp_ipv4_hdr = (struct uip_tcp_ipv4_hdr *)ustack->network_layer; + + buf = ustack->network_layer; + buf += sizeof(struct uip_ipv4_hdr); + tcp_hdr = (struct uip_tcp_hdr *)buf; + + buf = ustack->network_layer; + buf += sizeof(struct uip_ipv4_hdr); + icmpv4_hdr = (struct uip_icmpv4_hdr *)buf; + + buf = ustack->network_layer; + buf += sizeof(struct uip_ipv4_hdr); + udp_hdr = (struct uip_udp_hdr *)buf; + } /* End of ipv6 */ + +#if UIP_UDP + if (flag == UIP_UDP_SEND_CONN) + goto udp_send; +#endif /* UIP_UDP */ + ustack->uip_sappdata = ustack->uip_appdata = ustack->network_layer + + uip_ip_tcph_len; + + /* Check if we were invoked because of a poll request for a + particular connection. */ + if (flag == UIP_POLL_REQUEST) { + if ((uip_connr->tcpstateflags & UIP_TS_MASK) == UIP_ESTABLISHED + && !uip_outstanding(uip_connr)) { + ustack->uip_flags = UIP_POLL; + UIP_APPCALL(ustack); + goto appsend; + } + goto drop; + + /* Check if we were invoked because of the perodic timer + firing. */ + } else if (flag == UIP_TIMER) { +#if UIP_REASSEMBLY + if (uip_reasstmr != 0) + --uip_reasstmr; +#endif /* UIP_REASSEMBLY */ + /* Increase the initial sequence number. */ + if (++ustack->iss[3] == 0) { + if (++ustack->iss[2] == 0) { + if (++ustack->iss[1] == 0) + ++ustack->iss[0]; + } + } + + /* Reset the length variables. */ + ustack->uip_len = 0; + ustack->uip_slen = 0; + + /* Check if the connection is in a state in which we simply wait + for the connection to time out. If so, we increase the + connection's timer and remove the connection if it times + out. */ + if (uip_connr->tcpstateflags == UIP_TIME_WAIT || + uip_connr->tcpstateflags == UIP_FIN_WAIT_2) { + ++(uip_connr->timer); + if (uip_connr->timer == UIP_TIME_WAIT_TIMEOUT) + uip_connr->tcpstateflags = UIP_CLOSED; + } else if (uip_connr->tcpstateflags != UIP_CLOSED) { + /* If the connection has outstanding data, we increase + the connection's timer and see if it has reached the + RTO value in which case we retransmit. */ + if (uip_outstanding(uip_connr)) { + if (uip_connr->timer-- == 0) { + if (uip_connr->nrtx == UIP_MAXRTX || + ((uip_connr->tcpstateflags == + UIP_SYN_SENT + || uip_connr->tcpstateflags == + UIP_SYN_RCVD) + && uip_connr->nrtx == + UIP_MAXSYNRTX)) { + uip_connr->tcpstateflags = + UIP_CLOSED; + + /* We call UIP_APPCALL() with + uip_flags set to UIP_TIMEDOUT + to inform the application + that the connection has timed + out. */ + ustack->uip_flags = + UIP_TIMEDOUT; + UIP_APPCALL(ustack); + + /* We also send a reset packet + to the remote host. */ + tcp_hdr->flags = + TCP_RST | TCP_ACK; + goto tcp_send_nodata; + } + + /* Exponential backoff. */ + uip_connr->timer = + UIP_RTO << (uip_connr->nrtx > + 4 ? 4 : uip_connr-> + nrtx); + ++(uip_connr->nrtx); + + /* Ok, so we need to retransmit. + We do this differently depending on + which state we are in. + In ESTABLISHED, we call upon the + application so that it may prepare + the data for the retransmit. + In SYN_RCVD, we resend the SYNACK + that we sent earlier and in LAST_ACK + we have to retransmit our FINACK. */ + ++ustack->stats.tcp.rexmit; + switch (uip_connr-> + tcpstateflags & UIP_TS_MASK) { + case UIP_SYN_RCVD: + /* In the SYN_RCVD state, we + should retransmit our SYNACK + */ + goto tcp_send_synack; +#if UIP_ACTIVE_OPEN + case UIP_SYN_SENT: + /* In the SYN_SENT state, + we retransmit out SYN. */ + tcp_hdr->flags = 0; + goto tcp_send_syn; +#endif /* UIP_ACTIVE_OPEN */ + + case UIP_ESTABLISHED: + /* In the ESTABLISHED state, + we call upon the application + to do the actual retransmit + after which we jump into + the code for sending out the + packet (the apprexmit + label). */ + ustack->uip_flags = UIP_REXMIT; + UIP_APPCALL(ustack); + goto apprexmit; + + case UIP_FIN_WAIT_1: + case UIP_CLOSING: + case UIP_LAST_ACK: + /* In all these states we should + retransmit a FINACK. */ + goto tcp_send_finack; + + } + } + } else if ((uip_connr->tcpstateflags & UIP_TS_MASK) == + UIP_ESTABLISHED) { + /* If there was no need for a retransmission, + we poll the application for new data. */ + ustack->uip_flags = UIP_POLL; + UIP_APPCALL(ustack); + goto appsend; + } + } + goto drop; + } /* End of UIP_TIMER */ +#if UIP_UDP + if (flag == UIP_UDP_TIMER) { + /* This is for IPv4 DHCP only! */ + if (ustack->uip_udp_conn->lport != 0) { + ustack->uip_conn = NULL; + ustack->uip_sappdata = ustack->uip_appdata = + ustack->network_layer + uip_ip_udph_len; + ustack->uip_len = ustack->uip_slen = 0; + ustack->uip_flags = UIP_POLL; + UIP_UDP_APPCALL(ustack); + goto udp_send; + } else { + goto drop; + } + } +#endif + if (flag == UIP_NDP_TIMER) { + /* This is for IPv6 NDP Only! */ + if (1) { /* If NDP engine active */ + ustack->uip_len = ustack->uip_slen = 0; + ustack->uip_flags = UIP_POLL; + goto ndp_send; + } + } + + /* This is where the input processing starts. */ + ++ustack->stats.ip.recv; + + /* Start of IP input header processing code. */ + + if (is_ipv6(ustack)) { + u8_t version = ((ipv6_hdr->ip6_vfc) & 0xf0) >> 4; + + /* Check validity of the IP header. */ + if (version != 0x6) { /* IP version and header length. */ + ++ustack->stats.ip.drop; + ++ustack->stats.ip.vhlerr; + LOG_DEBUG(PFX "ipv6: invalid version(0x%x).", version); + goto drop; + } + } else { + /* Check validity of the IP header. */ + if (tcp_ipv4_hdr->vhl != 0x45) { + /* IP version and header length. */ + ++ustack->stats.ip.drop; + ++ustack->stats.ip.vhlerr; + LOG_DEBUG(PFX + "ipv4: invalid version or header length: " + "0x%x.", + tcp_ipv4_hdr->vhl); + goto drop; + } + } + + /* Check the size of the packet. If the size reported to us in + uip_len is smaller the size reported in the IP header, we assume + that the packet has been corrupted in transit. If the size of + uip_len is larger than the size reported in the IP packet header, + the packet has been padded and we set uip_len to the correct + value.. */ + + if (is_ipv6(ustack)) { + u16_t len = ntohs(ipv6_hdr->ip6_plen); + if (len > ustack->uip_len) { + LOG_DEBUG(PFX + "ip: packet shorter than reported in IP header" + ":IPv6_BUF(ustack)->len: %d ustack->uip_len: " + "%d", len, ustack->uip_len); + goto drop; + } + } else { + if ((tcp_ipv4_hdr->len[0] << 8) + + tcp_ipv4_hdr->len[1] <= ustack->uip_len) { + ustack->uip_len = (tcp_ipv4_hdr->len[0] << 8) + + tcp_ipv4_hdr->len[1]; + } else { + LOG_DEBUG(PFX + "ip: packet shorter than reported in IP header" + ":tcp_ipv4_hdr->len: %d ustack->uip_len:%d.", + (tcp_ipv4_hdr->len[0] << 8) + + tcp_ipv4_hdr->len[1], ustack->uip_len); + goto drop; + } + } + + if (!is_ipv6(ustack)) { + /* Check the fragment flag. */ + if ((tcp_ipv4_hdr->ipoffset[0] & 0x3f) != 0 || + tcp_ipv4_hdr->ipoffset[1] != 0) { +#if UIP_REASSEMBLY + uip_len = uip_reass(); + if (uip_len == 0) + goto drop; +#else /* UIP_REASSEMBLY */ + ++ustack->stats.ip.drop; + ++ustack->stats.ip.fragerr; + LOG_WARN(PFX "ip: fragment dropped."); + goto drop; +#endif /* UIP_REASSEMBLY */ + } + } + + if (!is_ipv6(ustack)) { + /* ipv4 */ + if (uip_ip4addr_cmp(ustack->hostaddr, all_zeroes_addr4)) { + /* If we are configured to use ping IP address + configuration and hasn't been assigned an IP + address yet, we accept all ICMP packets. */ +#if UIP_PINGADDRCONF && !UIP_CONF_IPV6 + if (tcp_ipv4_hdr->proto == UIP_PROTO_ICMP) { + LOG_WARN(PFX + "ip: possible ping config packet " + "received."); + goto icmp_input; + } else { + LOG_WARN(PFX + "ip: packet dropped since no " + "address assigned."); + goto drop; + } +#endif /* UIP_PINGADDRCONF */ + } else { + int broadcast_addr = 0xFFFFFFFF; + /* If IP broadcast support is configured, we check for + a broadcast UDP packet, which may be destined to us + */ + if ((tcp_ipv4_hdr->proto == UIP_PROTO_UDP) && + (uip_ip4addr_cmp + (tcp_ipv4_hdr->destipaddr, &broadcast_addr)) + /*&& + uip_ipchksum() == 0xffff */ + ) { + goto udp_input; + } + + /* Check if the packet is destined for our IP address + */ + if (!uip_ip4addr_cmp(tcp_ipv4_hdr->destipaddr, + ustack->hostaddr)) { + ++ustack->stats.ip.drop; + goto drop; + } + } + if (uip_ipchksum(ustack) != 0xffff) { + /* Compute and check the IP header checksum. */ + ++ustack->stats.ip.drop; + ++ustack->stats.ip.chkerr; + LOG_ERR(PFX "ip: bad checksum."); + goto drop; + } + } /* End of ipv4 */ + + if (is_ipv6(ustack)) { + if (ipv6_hdr->ip6_nxt == UIP_PROTO_TCP) { + /* Check for TCP packet. If so, proceed with TCP input + processing. */ + goto ndp_newdata; + } +#if UIP_UDP + if (ipv6_hdr->ip6_nxt == UIP_PROTO_UDP) + goto ndp_newdata; +#endif /* UIP_UDP */ + + /* This is IPv6 ICMPv6 processing code. */ + if (ipv6_hdr->ip6_nxt != UIP_PROTO_ICMP6) { + /* We only allow ICMPv6 packets from here. */ + ++ustack->stats.ip.drop; + ++ustack->stats.ip.protoerr; + goto drop; + } + + ++ustack->stats.icmp.recv; + +ndp_newdata: + /* This call is to handle the IPv6 Network Discovery Protocol */ + ustack->uip_flags = UIP_NEWDATA; + ustack->uip_slen = 0; +ndp_send: + UIP_NDP_CALL(ustack); + if (ustack->uip_slen != 0) { + ustack->uip_len = ustack->uip_slen; + goto send; + } else { + goto drop; + } + } else { + /* IPv4 Processing */ + if (tcp_ipv4_hdr->proto == UIP_PROTO_TCP) { + /* Check for TCP packet. If so, proceed with TCP input + processing. */ + goto tcp_input; + } +#if UIP_UDP + if (tcp_ipv4_hdr->proto == UIP_PROTO_UDP) + goto udp_input; +#endif /* UIP_UDP */ + + /* ICMPv4 processing code follows. */ + if (tcp_ipv4_hdr->proto != UIP_PROTO_ICMP) { + /* We only allow ICMP packets from here. */ + ++ustack->stats.ip.drop; + ++ustack->stats.ip.protoerr; + LOG_DEBUG(PFX "ip: neither tcp nor icmp."); + goto drop; + } +#if UIP_PINGADDRCONF +icmp_input: +#endif /* UIP_PINGADDRCONF */ + ++ustack->stats.icmp.recv; + + if (icmpv4_hdr->type == ICMP_ECHO_REPLY) { + if (process_icmp_packet(icmpv4_hdr, ustack) == 0) + goto drop; + } + + /* ICMP echo (i.e., ping) processing. This is simple, we only + change the ICMP type from ECHO to ECHO_REPLY and adjust the + ICMP checksum before we return the packet. */ + if (icmpv4_hdr->type != ICMP_ECHO) { + ++ustack->stats.icmp.drop; + ++ustack->stats.icmp.typeerr; + LOG_DEBUG(PFX "icmp: not icmp echo."); + goto drop; + } + + /* If we are configured to use ping IP address assignment, we + use the destination IP address of this ping packet and assign + it to ourself. */ +#if UIP_PINGADDRCONF + if ((ustack->hostaddr[0] | ustack->hostaddr[1]) == 0) { + ustack->hostaddr[0] = tcp_ipv4_hdr->destipaddr[0]; + ustack->hostaddr[1] = tcp_ipv4_hdr->destipaddr[1]; + } +#endif /* UIP_PINGADDRCONF */ + + icmpv4_hdr->type = ICMP_ECHO_REPLY; + + if (icmpv4_hdr->icmpchksum >= htons(0xffff - + (ICMP_ECHO << 8))) { + icmpv4_hdr->icmpchksum += htons(ICMP_ECHO << 8) + 1; + } else { + icmpv4_hdr->icmpchksum += htons(ICMP_ECHO << 8); + } + + /* Swap IP addresses. */ + uip_ip4addr_copy(tcp_ipv4_hdr->destipaddr, + tcp_ipv4_hdr->srcipaddr); + uip_ip4addr_copy(tcp_ipv4_hdr->srcipaddr, ustack->hostaddr); + + ++ustack->stats.icmp.sent; + goto send; + + /* End of IPv4 input header processing code. */ + } + +#if UIP_UDP + /* UDP input processing. */ +udp_input: + /* UDP processing is really just a hack. We don't do anything to the + UDP/IP headers, but let the UDP application do all the hard + work. If the application sets uip_slen, it has a packet to + send. */ +#if UIP_UDP_CHECKSUMS + ustack->uip_len = ustack->uip_len - uip_ip_udph_len; + ustack->uip_appdata = ustack->network_layer + uip_ip_udph_len; + if (UDPBUF(ustack)->udpchksum != 0 && uip_udpchksum(ustack) != 0xffff) { + ++ustack->stats.udp.drop; + ++ustack->stats.udp.chkerr; + LOG_DEBUG(PFX "udp: bad checksum."); + goto drop; + } +#else /* UIP_UDP_CHECKSUMS */ + uip_len = uip_len - uip_ip_udph_len; +#endif /* UIP_UDP_CHECKSUMS */ + + if (is_ipv6(ustack)) + goto udp_found; + + /* Demultiplex this UDP packet between the UDP "connections". */ + for (ustack->uip_udp_conn = &ustack->uip_udp_conns[0]; + ustack->uip_udp_conn < &ustack->uip_udp_conns[UIP_UDP_CONNS]; + ++ustack->uip_udp_conn) { + /* If the local UDP port is non-zero, the connection is + considered to be used. If so, the local port number is + checked against the destination port number in the + received packet. If the two port + numbers match, the remote port number is checked if the + connection is bound to a remote port. Finally, if the + connection is bound to a remote IP address, the source IP + address of the packet is checked. */ + + if (ustack->uip_udp_conn->lport != 0 && + UDPBUF(ustack)->destport == ustack->uip_udp_conn->lport && + (ustack->uip_udp_conn->rport == 0 || + UDPBUF(ustack)->srcport == ustack->uip_udp_conn->rport) && + (uip_ip4addr_cmp(ustack->uip_udp_conn->ripaddr, + all_zeroes_addr4) || + uip_ip4addr_cmp(ustack->uip_udp_conn->ripaddr, + all_ones_addr4) || + uip_ip4addr_cmp(tcp_ipv4_hdr->srcipaddr, + ustack->uip_udp_conn->ripaddr))) { + goto udp_found; + } + } + LOG_DEBUG(PFX + "udp: no matching connection found: dest port: %d src port: " + "%d", udp_hdr->destport, udp_hdr->srcport); + goto drop; + +udp_found: + ustack->uip_conn = NULL; + ustack->uip_flags = UIP_NEWDATA; + ustack->uip_sappdata = ustack->uip_appdata = ustack->network_layer + + uip_ip_udph_len; + ustack->uip_slen = 0; + if (is_ipv6(ustack)) + UIP_NDP_CALL(ustack); + else + UIP_UDP_APPCALL(ustack); +udp_send: + if (ustack->uip_slen == 0) + goto drop; + + ustack->uip_len = ustack->uip_slen + uip_ip_udph_len; + + if (is_ipv6(ustack)) { + goto ip_send_nolen; + } else { + tcp_ipv4_hdr->len[0] = (ustack->uip_len >> 8); + tcp_ipv4_hdr->len[1] = (ustack->uip_len & 0xff); + tcp_ipv4_hdr->ttl = ustack->uip_udp_conn->ttl; + tcp_ipv4_hdr->proto = UIP_PROTO_UDP; + } + + udp_hdr->udplen = htons(ustack->uip_slen + UIP_UDPH_LEN); + udp_hdr->udpchksum = 0; + + udp_hdr->srcport = ustack->uip_udp_conn->lport; + udp_hdr->destport = ustack->uip_udp_conn->rport; + + uip_ip4addr_copy(tcp_ipv4_hdr->srcipaddr, ustack->hostaddr); + uip_ip4addr_copy(tcp_ipv4_hdr->destipaddr, + ustack->uip_udp_conn->ripaddr); + + ustack->uip_appdata = ustack->network_layer + uip_ip_tcph_len; + + if (ustack->uip_buf == NULL) { + LOG_WARN(PFX "uip_buf == NULL on udp send"); + goto drop; + } +#if UIP_UDP_CHECKSUMS + /* Calculate UDP checksum. */ + udp_hdr->udpchksum = ~(uip_udpchksum(ustack)); + if (udp_hdr->udpchksum == 0) + udp_hdr->udpchksum = 0xffff; +#endif /* UIP_UDP_CHECKSUMS */ + + goto ip_send_nolen; +#endif /* UIP_UDP */ + + /* TCP input processing. */ +tcp_input: + ++ustack->stats.tcp.recv; + + /* Start of TCP input header processing code. */ + + if (uip_tcpchksum(ustack) != 0xffff) { /* Compute and check the TCP + checksum. */ + ++ustack->stats.tcp.drop; + ++ustack->stats.tcp.chkerr; + LOG_WARN(PFX "tcp: bad checksum."); + goto drop; + } + + if (is_ipv6(ustack)) { + /* Demultiplex this segment. */ + /* First check any active connections. */ + for (uip_connr = &ustack->uip_conns[0]; + uip_connr <= &ustack->uip_conns[UIP_CONNS - 1]; + ++uip_connr) { + if (uip_connr->tcpstateflags != UIP_CLOSED && + tcp_hdr->destport == uip_connr->lport && + tcp_hdr->srcport == uip_connr->rport && + uip_ip6addr_cmp(IPv6_BUF(ustack)->srcipaddr, + uip_connr->ripaddr)) { + goto found; + } + } + } else { + /* Demultiplex this segment. */ + /* First check any active connections. */ + for (uip_connr = &ustack->uip_conns[0]; + uip_connr <= &ustack->uip_conns[UIP_CONNS - 1]; + ++uip_connr) { + if (uip_connr->tcpstateflags != UIP_CLOSED && + tcp_hdr->destport == uip_connr->lport && + tcp_hdr->srcport == uip_connr->rport && + uip_ip4addr_cmp(tcp_ipv4_hdr->srcipaddr, + uip_connr->ripaddr)) { + goto found; + } + } + } + + /* If we didn't find and active connection that expected the packet, + either this packet is an old duplicate, or this is a SYN packet + destined for a connection in LISTEN. If the SYN flag isn't set, + it is an old packet and we send a RST. */ + if ((tcp_hdr->flags & TCP_CTL) != TCP_SYN) + goto reset; + + tmp16 = tcp_hdr->destport; + /* Next, check listening connections. */ + for (c = 0; c < UIP_LISTENPORTS; ++c) { + if (tmp16 == ustack->uip_listenports[c]) + goto found_listen; + } + + /* No matching connection found, so we send a RST packet. */ + ++ustack->stats.tcp.synrst; +reset: + + /* We do not send resets in response to resets. */ + if (tcp_hdr->flags & TCP_RST) + goto drop; + + ++ustack->stats.tcp.rst; + + tcp_hdr->flags = TCP_RST | TCP_ACK; + ustack->uip_len = uip_ip_tcph_len; + tcp_hdr->tcpoffset = 5 << 4; + + /* Flip the seqno and ackno fields in the TCP header. */ + c = tcp_hdr->seqno[3]; + tcp_hdr->seqno[3] = tcp_hdr->ackno[3]; + tcp_hdr->ackno[3] = c; + + c = tcp_hdr->seqno[2]; + tcp_hdr->seqno[2] = tcp_hdr->ackno[2]; + tcp_hdr->ackno[2] = c; + + c = tcp_hdr->seqno[1]; + tcp_hdr->seqno[1] = tcp_hdr->ackno[1]; + tcp_hdr->ackno[1] = c; + + c = tcp_hdr->seqno[0]; + tcp_hdr->seqno[0] = tcp_hdr->ackno[0]; + tcp_hdr->ackno[0] = c; + + /* We also have to increase the sequence number we are + acknowledging. If the least significant byte overflowed, we need + to propagate the carry to the other bytes as well. */ + if (++tcp_hdr->ackno[3] == 0) { + if (++tcp_hdr->ackno[2] == 0) { + if (++tcp_hdr->ackno[1] == 0) + ++tcp_hdr->ackno[0]; + } + } + + /* Swap port numbers. */ + tmp16 = tcp_hdr->srcport; + tcp_hdr->srcport = tcp_hdr->destport; + tcp_hdr->destport = tmp16; + + /* Swap IP addresses. */ + if (is_ipv6(ustack)) { + uip_ip6addr_copy(IPv6_BUF(ustack)->destipaddr, + IPv6_BUF(ustack)->srcipaddr); + uip_ip6addr_copy(IPv6_BUF(ustack)->srcipaddr, + ustack->hostaddr6); + } else { + uip_ip4addr_copy(tcp_ipv4_hdr->destipaddr, + tcp_ipv4_hdr->srcipaddr); + uip_ip4addr_copy(tcp_ipv4_hdr->srcipaddr, ustack->hostaddr); + } + + /* And send out the RST packet! */ + goto tcp_send_noconn; + + /* This label will be jumped to if we matched the incoming packet + with a connection in LISTEN. In that case, we should create a new + connection and send a SYNACK in return. */ +found_listen: + /* First we check if there are any connections avaliable. Unused + connections are kept in the same table as used connections, but + unused ones have the tcpstate set to CLOSED. Also, connections in + TIME_WAIT are kept track of and we'll use the oldest one if no + CLOSED connections are found. Thanks to Eddie C. Dost for a very + nice algorithm for the TIME_WAIT search. */ + uip_connr = 0; + for (c = 0; c < UIP_CONNS; ++c) { + if (ustack->uip_conns[c].tcpstateflags == UIP_CLOSED) { + uip_connr = &ustack->uip_conns[c]; + break; + } + if (ustack->uip_conns[c].tcpstateflags == UIP_TIME_WAIT) { + if (uip_connr == 0 || + ustack->uip_conns[c].timer > uip_connr->timer) { + uip_connr = &ustack->uip_conns[c]; + } + } + } + + if (uip_connr == 0) { + /* All connections are used already, we drop packet and hope + that the remote end will retransmit the packet at a time when + we have more spare connections. */ + ++ustack->stats.tcp.syndrop; + LOG_WARN(PFX "tcp: found no unused connections."); + goto drop; + } + ustack->uip_conn = uip_connr; + + /* Fill in the necessary fields for the new connection. */ + uip_connr->rto = uip_connr->timer = UIP_RTO; + uip_connr->sa = 0; + uip_connr->sv = 4; + uip_connr->nrtx = 0; + uip_connr->lport = tcp_hdr->destport; + uip_connr->rport = tcp_hdr->srcport; + if (is_ipv6(ustack)) { + uip_ip6addr_copy(uip_connr->ripaddr, + IPv6_BUF(ustack)->srcipaddr); + } else { + uip_ip4addr_copy(uip_connr->ripaddr, tcp_ipv4_hdr->srcipaddr); + } + uip_connr->tcpstateflags = UIP_SYN_RCVD; + + uip_connr->snd_nxt[0] = ustack->iss[0]; + uip_connr->snd_nxt[1] = ustack->iss[1]; + uip_connr->snd_nxt[2] = ustack->iss[2]; + uip_connr->snd_nxt[3] = ustack->iss[3]; + uip_connr->len = 1; + + /* rcv_nxt should be the seqno from the incoming packet + 1. */ + uip_connr->rcv_nxt[3] = tcp_hdr->seqno[3]; + uip_connr->rcv_nxt[2] = tcp_hdr->seqno[2]; + uip_connr->rcv_nxt[1] = tcp_hdr->seqno[1]; + uip_connr->rcv_nxt[0] = tcp_hdr->seqno[0]; + uip_add_rcv_nxt(ustack, 1); + + /* Parse the TCP MSS option, if present. */ + if ((tcp_hdr->tcpoffset & 0xf0) > 0x50) { + for (c = 0; c < ((tcp_hdr->tcpoffset >> 4) - 5) << 2;) { + ustack->opt = + ustack->uip_buf[uip_ip_tcph_len + UIP_LLH_LEN + c]; + if (ustack->opt == TCP_OPT_END) { + /* End of options. */ + break; + } else if (ustack->opt == TCP_OPT_NOOP) { + ++c; + /* NOP option. */ + } else if (ustack->opt == TCP_OPT_MSS && + ustack->uip_buf[uip_ip_tcph_len + + UIP_LLH_LEN + 1 + c] == + TCP_OPT_MSS_LEN) { + /* An MSS option with the right option length.*/ + tmp16 = + ((u16_t) ustack-> + uip_buf[uip_ip_tcph_len + UIP_LLH_LEN + 2 + + c] << 8) | (u16_t) ustack-> + uip_buf[uip_ip_tcph_len + UIP_LLH_LEN + 3 + + c]; + uip_connr->initialmss = uip_connr->mss = + tmp16 > UIP_TCP_MSS ? UIP_TCP_MSS : tmp16; + + /* And we are done processing options. */ + break; + } else { + /* All other options have a length field, so + that we easily can skip past them. */ + if (ustack-> + uip_buf[uip_ip_tcph_len + UIP_LLH_LEN + 1 + + c] == 0) { + /* If the length field is zero, the + options are malformed + and we don't process them further. */ + break; + } + c += ustack->uip_buf[uip_ip_tcph_len + + UIP_LLH_LEN + 1 + c]; + } + } + } + + /* Our response will be a SYNACK. */ +#if UIP_ACTIVE_OPEN +tcp_send_synack: + tcp_hdr->flags = TCP_ACK; + +tcp_send_syn: + tcp_hdr->flags |= TCP_SYN; +#else /* UIP_ACTIVE_OPEN */ +tcp_send_synack: + tcp_hdr->flags = TCP_SYN | TCP_ACK; +#endif /* UIP_ACTIVE_OPEN */ + + /* We send out the TCP Maximum Segment Size option with our + SYNACK. */ + tcp_hdr->optdata[0] = TCP_OPT_MSS; + tcp_hdr->optdata[1] = TCP_OPT_MSS_LEN; + tcp_hdr->optdata[2] = (UIP_TCP_MSS) / 256; + tcp_hdr->optdata[3] = (UIP_TCP_MSS) & 255; + ustack->uip_len = uip_ip_tcph_len + TCP_OPT_MSS_LEN; + tcp_hdr->tcpoffset = ((UIP_TCPH_LEN + TCP_OPT_MSS_LEN) / 4) << 4; + goto tcp_send; + + /* This label will be jumped to if we found an active connection. */ +found: + ustack->uip_conn = uip_connr; + ustack->uip_flags = 0; + /* We do a very naive form of TCP reset processing; we just accept + any RST and kill our connection. We should in fact check if the + sequence number of this reset is wihtin our advertised window + before we accept the reset. */ + if (tcp_hdr->flags & TCP_RST) { + uip_connr->tcpstateflags = UIP_CLOSED; + LOG_WARN(PFX "tcp: got reset, aborting connection."); + ustack->uip_flags = UIP_ABORT; + UIP_APPCALL(ustack); + goto drop; + } + /* Calculated the length of the data, if the application has sent + any data to us. */ + c = (tcp_hdr->tcpoffset >> 4) << 2; + /* uip_len will contain the length of the actual TCP data. This is + calculated by subtracing the length of the TCP header (in + c) and the length of the IP header (20 bytes). */ + ustack->uip_len = ustack->uip_len - c - uip_iph_len; + + /* First, check if the sequence number of the incoming packet is + what we're expecting next. If not, we send out an ACK with the + correct numbers in. */ + if (!(((uip_connr->tcpstateflags & UIP_TS_MASK) == UIP_SYN_SENT) && + ((tcp_hdr->flags & TCP_CTL) == (TCP_SYN | TCP_ACK)))) { + if ((ustack->uip_len > 0 + || ((tcp_hdr->flags & (TCP_SYN | TCP_FIN)) != 0)) + && (tcp_hdr->seqno[0] != uip_connr->rcv_nxt[0] + || tcp_hdr->seqno[1] != uip_connr->rcv_nxt[1] + || tcp_hdr->seqno[2] != uip_connr->rcv_nxt[2] + || tcp_hdr->seqno[3] != uip_connr->rcv_nxt[3])) { + goto tcp_send_ack; + } + } + + { + u8_t uip_acc32[4]; + + /* Next, check if the incoming segment acks any outstanding + data. If so, we update the sequence number, reset the len of + the outstanding data, calc RTT estimations, and reset the + retransmission timer. */ + if ((tcp_hdr->flags & TCP_ACK) && uip_outstanding(uip_connr)) { + uip_add32(uip_connr->snd_nxt, uip_connr->len, + uip_acc32); + + if (tcp_hdr->ackno[0] == uip_acc32[0] && + tcp_hdr->ackno[1] == uip_acc32[1] && + tcp_hdr->ackno[2] == uip_acc32[2] && + tcp_hdr->ackno[3] == uip_acc32[3]) { + /* Update sequence number. */ + uip_connr->snd_nxt[0] = uip_acc32[0]; + uip_connr->snd_nxt[1] = uip_acc32[1]; + uip_connr->snd_nxt[2] = uip_acc32[2]; + uip_connr->snd_nxt[3] = uip_acc32[3]; + + /* Do RTT estimation, unless we have done + retransmissions. */ + if (uip_connr->nrtx == 0) { + signed char m; + m = uip_connr->rto - uip_connr->timer; + /* This is taken directly from VJs + original code in his paper */ + m = m - (uip_connr->sa >> 3); + uip_connr->sa += m; + if (m < 0) + m = -m; + m = m - (uip_connr->sv >> 2); + uip_connr->sv += m; + uip_connr->rto = + (uip_connr->sa >> 3) + + uip_connr->sv; + + } + /* Set the acknowledged flag. */ + ustack->uip_flags = UIP_ACKDATA; + /* Reset the retransmission timer. */ + uip_connr->timer = uip_connr->rto; + + /* Reset length of outstanding data. */ + uip_connr->len = 0; + } + + } + + } + + /* Do different things depending on in what state the connection is. */ + switch (uip_connr->tcpstateflags & UIP_TS_MASK) { + /* CLOSED and LISTEN are not handled here. CLOSE_WAIT is not + implemented, since we force the application to close when the + peer sends a FIN (hence the application goes directly from + ESTABLISHED to LAST_ACK). */ + case UIP_SYN_RCVD: + /* In SYN_RCVD we have sent out a SYNACK in response to a SYN, + and we are waiting for an ACK that acknowledges the data we + sent out the last time. Therefore, we want to have the + UIP_ACKDATA flag set. + If so, we enter the ESTABLISHED state. */ + if (ustack->uip_flags & UIP_ACKDATA) { + uip_connr->tcpstateflags = UIP_ESTABLISHED; + ustack->uip_flags = UIP_CONNECTED; + uip_connr->len = 0; + if (ustack->uip_len > 0) { + ustack->uip_flags |= UIP_NEWDATA; + uip_add_rcv_nxt(ustack, ustack->uip_len); + } + ustack->uip_slen = 0; + UIP_APPCALL(ustack); + goto appsend; + } + goto drop; +#if UIP_ACTIVE_OPEN + case UIP_SYN_SENT: + /* In SYN_SENT, we wait for a SYNACK that is sent in response to + our SYN. The rcv_nxt is set to sequence number in the SYNACK + plus one, and we send an ACK. We move into the ESTABLISHED + state. */ + if ((ustack->uip_flags & UIP_ACKDATA) && + (tcp_hdr->flags & TCP_CTL) == (TCP_SYN | TCP_ACK)) { + + /* Parse the TCP MSS option, if present. */ + if ((tcp_hdr->tcpoffset & 0xf0) > 0x50) { + for (c = 0; + c < + ((tcp_hdr->tcpoffset >> 4) - 5) << 2;) { + ustack->opt = + ustack->uip_buf[uip_ip_tcph_len + + UIP_LLH_LEN + c]; + if (ustack->opt == TCP_OPT_END) { + /* End of options. */ + break; + } else if (ustack->opt == + TCP_OPT_NOOP) { + ++c; + /* NOP option. */ + } else if (ustack->opt == TCP_OPT_MSS && + ustack-> + uip_buf[uip_ip_tcph_len + + UIP_LLH_LEN + 1 + + c] == + TCP_OPT_MSS_LEN) { + /* An MSS option with the right + option length. */ + tmp16 = + (ustack-> + uip_buf[uip_ip_tcph_len + + UIP_LLH_LEN + 2 + + c] << 8) | ustack-> + uip_buf[uip_ip_tcph_len + + UIP_LLH_LEN + 3 + + c]; + uip_connr->initialmss = + uip_connr->mss = + tmp16 > + UIP_TCP_MSS ? UIP_TCP_MSS : + tmp16; + + /* And we are done processing + options. */ + break; + } else { + /* All other options have a + length field, so that we + easily can skip past them */ + if (ustack-> + uip_buf[uip_ip_tcph_len + + UIP_LLH_LEN + 1 + + c] == 0) { + /* If the length field + is zero, the options + are malformed and we + don't process them + further. */ + break; + } + c += ustack-> + uip_buf[uip_ip_tcph_len + + UIP_LLH_LEN + 1 + + c]; + } + } + } + uip_connr->tcpstateflags = UIP_ESTABLISHED; + uip_connr->rcv_nxt[0] = tcp_hdr->seqno[0]; + uip_connr->rcv_nxt[1] = tcp_hdr->seqno[1]; + uip_connr->rcv_nxt[2] = tcp_hdr->seqno[2]; + uip_connr->rcv_nxt[3] = tcp_hdr->seqno[3]; + uip_add_rcv_nxt(ustack, 1); + ustack->uip_flags = UIP_CONNECTED | UIP_NEWDATA; + uip_connr->len = 0; + ustack->uip_len = 0; + ustack->uip_slen = 0; + UIP_APPCALL(ustack); + goto appsend; + } + /* Inform the application that the connection failed */ + ustack->uip_flags = UIP_ABORT; + UIP_APPCALL(ustack); + /* The connection is closed after we send the RST */ + ustack->uip_conn->tcpstateflags = UIP_CLOSED; + goto reset; +#endif /* UIP_ACTIVE_OPEN */ + + case UIP_ESTABLISHED: + /* In the ESTABLISHED state, we call upon the application to + feed data into the uip_buf. If the UIP_ACKDATA flag is set, + the application should put new data into the buffer, + otherwise we are retransmitting an old segment, and the + application should put that data into the buffer. + + If the incoming packet is a FIN, we should close the + connection on this side as well, and we send out a FIN and + enter the LAST_ACK state. We require that there is no + outstanding data; otherwise the sequence numbers will be + screwed up. */ + + if (tcp_hdr->flags & TCP_FIN + && !(uip_connr->tcpstateflags & UIP_STOPPED)) { + if (uip_outstanding(uip_connr)) + goto drop; + uip_add_rcv_nxt(ustack, 1 + ustack->uip_len); + ustack->uip_flags |= UIP_CLOSE; + if (ustack->uip_len > 0) + ustack->uip_flags |= UIP_NEWDATA; + UIP_APPCALL(ustack); + uip_connr->len = 1; + uip_connr->tcpstateflags = UIP_LAST_ACK; + uip_connr->nrtx = 0; +tcp_send_finack: + tcp_hdr->flags = TCP_FIN | TCP_ACK; + goto tcp_send_nodata; + } + + /* Check the URG flag. If this is set, the segment carries + urgent data that we must pass to the application. */ + if ((tcp_hdr->flags & TCP_URG) != 0) { +#if UIP_URGDATA > 0 + uip_urglen = (tcp_hdr->urgp[0] << 8) | tcp_hdr->urgp[1]; + if (uip_urglen > uip_len) { + /* There is more urgent data in the next segment + to come. */ + uip_urglen = uip_len; + } + uip_add_rcv_nxt(uip_urglen); + uip_len -= uip_urglen; + uip_urgdata = uip_appdata; + uip_appdata += uip_urglen; + } else { + uip_urglen = 0; +#else /* UIP_URGDATA > 0 */ + ustack->uip_appdata = + ((char *)ustack->uip_appdata) + + ((tcp_hdr->urgp[0] << 8) | tcp_hdr->urgp[1]); + ustack->uip_len -= + (tcp_hdr->urgp[0] << 8) | tcp_hdr->urgp[1]; +#endif /* UIP_URGDATA > 0 */ + } + + /* If uip_len > 0 we have TCP data in the packet, and we flag + this by setting the UIP_NEWDATA flag and update the sequence + number we acknowledge. If the application has stopped the + dataflow using uip_stop(), we must not accept any data + packets from the remote host. */ + if (ustack->uip_len > 0 + && !(uip_connr->tcpstateflags & UIP_STOPPED)) { + ustack->uip_flags |= UIP_NEWDATA; + uip_add_rcv_nxt(ustack, ustack->uip_len); + } + + /* Check if the available buffer space advertised by the other + end is smaller than the initial MSS for this connection. + If so, we set the current MSS to the window size to ensure + that the application does not send more data than the other + end can handle. + + If the remote host advertises a zero window, we set the MSS + to the initial MSS so that the application will send an + entire MSS of data. This data will not be acknowledged by + the receiver, and the application will retransmit it. + This is called the "persistent timer" and uses the + retransmission mechanim. + */ + tmp16 = + ((u16_t) tcp_hdr->wnd[0] << 8) + (u16_t) tcp_hdr->wnd[1]; + if (tmp16 > uip_connr->initialmss || tmp16 == 0) + tmp16 = uip_connr->initialmss; + uip_connr->mss = tmp16; + + /* If this packet constitutes an ACK for outstanding data + (flagged by the UIP_ACKDATA flag, we should call the + application since it might want to send more data. + If the incoming packet had data from the peer + (as flagged by the UIP_NEWDATA flag), the application + must also be notified. + + When the application is called, the global variable uip_len + contains the length of the incoming data. The application can + access the incoming data through the global pointer + uip_appdata, which usually points uip_ip_tcph_len + + UIP_LLH_LEN bytes into the uip_buf array. + + If the application wishes to send any data, this data should + be put into the uip_appdata and the length of the data should + be put into uip_len. If the application don't have any data + to send, uip_len must be set to 0. */ + if (ustack->uip_flags & (UIP_NEWDATA | UIP_ACKDATA)) { + ustack->uip_slen = 0; + UIP_APPCALL(ustack); + +appsend: + + if (ustack->uip_flags & UIP_ABORT) { + ustack->uip_slen = 0; + uip_connr->tcpstateflags = UIP_CLOSED; + tcp_hdr->flags = TCP_RST | TCP_ACK; + goto tcp_send_nodata; + } + + if (ustack->uip_flags & UIP_CLOSE) { + ustack->uip_slen = 0; + uip_connr->len = 1; + uip_connr->tcpstateflags = UIP_FIN_WAIT_1; + uip_connr->nrtx = 0; + tcp_hdr->flags = TCP_FIN | TCP_ACK; + goto tcp_send_nodata; + } + + /* If uip_slen > 0, the application has data to be sent + */ + if (ustack->uip_slen > 0) { + + /* If the connection has acknowledged data, the + contents of the ->len variable should be + discarded. */ + if ((ustack->uip_flags & UIP_ACKDATA) != 0) + uip_connr->len = 0; + + /* If the ->len variable is non-zero the + connection has already data in transit and + cannot send anymore right now. */ + if (uip_connr->len == 0) { + + /* The application cannot send more than + what is allowed by the mss (the + minumum of the MSS and the available + window). */ + if (ustack->uip_slen > uip_connr->mss) { + ustack->uip_slen = + uip_connr->mss; + } + + /* Remember how much data we send out + now so that we know when everything + has been acknowledged. */ + uip_connr->len = ustack->uip_slen; + } else { + + /* If the application already had + unacknowledged data, we make sure + that the application does not send + (i.e., retransmit) out more than it + previously sent out. */ + ustack->uip_slen = uip_connr->len; + } + } + uip_connr->nrtx = 0; +apprexmit: + ustack->uip_appdata = ustack->uip_sappdata; + + /* If the application has data to be sent, or if the + incoming packet had new data in it, we must send + out a packet. */ + if (ustack->uip_slen > 0 && uip_connr->len > 0) { + /* Add the length of the IP and TCP headers. */ + ustack->uip_len = + uip_connr->len + uip_ip_tcph_len; + /* We always set the ACK flag in response + packets. */ + tcp_hdr->flags = TCP_ACK | TCP_PSH; + /* Send the packet. */ + goto tcp_send_noopts; + } + /* If there is no data to send, just send out a pure ACK + if there is newdata. */ + if (ustack->uip_flags & UIP_NEWDATA) { + ustack->uip_len = uip_ip_tcph_len; + tcp_hdr->flags = TCP_ACK; + goto tcp_send_noopts; + } + } + goto drop; + case UIP_LAST_ACK: + /* We can close this connection if the peer has acknowledged our + FIN. This is indicated by the UIP_ACKDATA flag. */ + if (ustack->uip_flags & UIP_ACKDATA) { + uip_connr->tcpstateflags = UIP_CLOSED; + ustack->uip_flags = UIP_CLOSE; + UIP_APPCALL(ustack); + } + break; + + case UIP_FIN_WAIT_1: + /* The application has closed the connection, but the remote + host hasn't closed its end yet. Thus we do nothing but wait + for a FIN from the other side. */ + if (ustack->uip_len > 0) + uip_add_rcv_nxt(ustack, ustack->uip_len); + if (tcp_hdr->flags & TCP_FIN) { + if (ustack->uip_flags & UIP_ACKDATA) { + uip_connr->tcpstateflags = UIP_TIME_WAIT; + uip_connr->timer = 0; + uip_connr->len = 0; + } else { + uip_connr->tcpstateflags = UIP_CLOSING; + } + uip_add_rcv_nxt(ustack, 1); + ustack->uip_flags = UIP_CLOSE; + UIP_APPCALL(ustack); + goto tcp_send_ack; + } else if (ustack->uip_flags & UIP_ACKDATA) { + uip_connr->tcpstateflags = UIP_FIN_WAIT_2; + uip_connr->len = 0; + goto drop; + } + if (ustack->uip_len > 0) + goto tcp_send_ack; + goto drop; + + case UIP_FIN_WAIT_2: + if (ustack->uip_len > 0) + uip_add_rcv_nxt(ustack, ustack->uip_len); + if (tcp_hdr->flags & TCP_FIN) { + uip_connr->tcpstateflags = UIP_TIME_WAIT; + uip_connr->timer = 0; + uip_add_rcv_nxt(ustack, 1); + ustack->uip_flags = UIP_CLOSE; + UIP_APPCALL(ustack); + goto tcp_send_ack; + } + if (ustack->uip_len > 0) + goto tcp_send_ack; + goto drop; + + case UIP_TIME_WAIT: + goto tcp_send_ack; + + case UIP_CLOSING: + if (ustack->uip_flags & UIP_ACKDATA) { + uip_connr->tcpstateflags = UIP_TIME_WAIT; + uip_connr->timer = 0; + } + } + goto drop; + + /* We jump here when we are ready to send the packet, and just want + to set the appropriate TCP sequence numbers in the TCP header. */ +tcp_send_ack: + tcp_hdr->flags = TCP_ACK; +tcp_send_nodata: + ustack->uip_len = uip_ip_tcph_len; +tcp_send_noopts: + tcp_hdr->tcpoffset = (UIP_TCPH_LEN / 4) << 4; +tcp_send: + /* We're done with the input processing. We are now ready to send a + reply. Our job is to fill in all the fields of the TCP and IP + headers before calculating the checksum and finally send the + packet. */ + tcp_hdr->ackno[0] = uip_connr->rcv_nxt[0]; + tcp_hdr->ackno[1] = uip_connr->rcv_nxt[1]; + tcp_hdr->ackno[2] = uip_connr->rcv_nxt[2]; + tcp_hdr->ackno[3] = uip_connr->rcv_nxt[3]; + + tcp_hdr->seqno[0] = uip_connr->snd_nxt[0]; + tcp_hdr->seqno[1] = uip_connr->snd_nxt[1]; + tcp_hdr->seqno[2] = uip_connr->snd_nxt[2]; + tcp_hdr->seqno[3] = uip_connr->snd_nxt[3]; + + if (is_ipv6(ustack)) { + IPv6_BUF(ustack)->proto = UIP_PROTO_TCP; + uip_ip6addr_copy(IPv6_BUF(ustack)->srcipaddr, + ustack->hostaddr6); + uip_ip6addr_copy(IPv6_BUF(ustack)->destipaddr, + uip_connr->ripaddr6); + } else { + tcp_ipv4_hdr->proto = UIP_PROTO_TCP; + uip_ip4addr_copy(tcp_ipv4_hdr->srcipaddr, ustack->hostaddr); + uip_ip4addr_copy(tcp_ipv4_hdr->destipaddr, uip_connr->ripaddr); + } + + tcp_hdr->srcport = uip_connr->lport; + tcp_hdr->destport = uip_connr->rport; + + if (uip_connr->tcpstateflags & UIP_STOPPED) { + /* If the connection has issued uip_stop(), we advertise a zero + window so that the remote host will stop sending data. */ + tcp_hdr->wnd[0] = tcp_hdr->wnd[1] = 0; + } else { + tcp_hdr->wnd[0] = ((UIP_RECEIVE_WINDOW) >> 8); + tcp_hdr->wnd[1] = ((UIP_RECEIVE_WINDOW) & 0xff); + } + +tcp_send_noconn: + if (is_ipv6(ustack)) { + IPv6_BUF(ustack)->ttl = UIP_TTL; + + /* For IPv6, the IP length field does not include the IPv6 IP + header length. */ + IPv6_BUF(ustack)->len[0] = + ((ustack->uip_len - uip_iph_len) >> 8); + IPv6_BUF(ustack)->len[1] = + ((ustack->uip_len - uip_iph_len) & 0xff); + } else { + tcp_ipv4_hdr->ttl = UIP_TTL; + tcp_ipv4_hdr->len[0] = (ustack->uip_len >> 8); + tcp_ipv4_hdr->len[1] = (ustack->uip_len & 0xff); + } + + tcp_hdr->urgp[0] = tcp_hdr->urgp[1] = 0; + + /* Calculate TCP checksum. */ + tcp_hdr->tcpchksum = 0; + tcp_hdr->tcpchksum = ~(uip_tcpchksum(ustack)); + +ip_send_nolen: + + if (!is_ipv6(ustack)) { + tcp_ipv4_hdr->vhl = 0x45; + tcp_ipv4_hdr->tos = 0; + tcp_ipv4_hdr->ipoffset[0] = tcp_ipv4_hdr->ipoffset[1] = 0; + ++ustack->ipid; + tcp_ipv4_hdr->ipid[0] = ustack->ipid >> 8; + tcp_ipv4_hdr->ipid[1] = ustack->ipid & 0xff; + /* Calculate IP checksum. */ + tcp_ipv4_hdr->ipchksum = 0; + tcp_ipv4_hdr->ipchksum = ~(uip_ipchksum(ustack)); + } + + ++ustack->stats.tcp.sent; +send: + if (is_ipv6(ustack)) { + LOG_DEBUG(PFX "Sending packet with length %d (%d)", + ustack->uip_len, ipv6_hdr ? ipv6_hdr->ip6_plen : 0); + } else { + LOG_DEBUG(PFX "Sending packet with length %d (%d)", + ustack->uip_len, + (tcp_ipv4_hdr->len[0] << 8) | tcp_ipv4_hdr->len[1]); + } + ++ustack->stats.ip.sent; + /* Return and let the caller do the actual transmission. */ + ustack->uip_flags = 0; + return; +drop: + ustack->uip_len = 0; + ustack->uip_flags = 0; + return; +} + +/*---------------------------------------------------------------------------*/ +void uip_send(struct uip_stack *ustack, const void *data, int len) +{ + if (len > 0) { + ustack->uip_slen = len; + if (data != ustack->uip_buf) + memcpy(ustack->uip_buf, (data), ustack->uip_slen); + } +} + +void uip_appsend(struct uip_stack *ustack, const void *data, int len) +{ + if (len > 0) { + ustack->uip_slen = len; + if (data != ustack->uip_sappdata) + memcpy(ustack->uip_sappdata, (data), ustack->uip_slen); + } +} + +u16_t uip_datalen(struct uip_stack *ustack) +{ + return ustack->uip_len; +} + +/** @} */ diff --git a/iscsiuio/src/uip/uip.h b/iscsiuio/src/uip/uip.h new file mode 100644 index 0000000..1180ab5 --- /dev/null +++ b/iscsiuio/src/uip/uip.h @@ -0,0 +1,1574 @@ + +/** + * \addtogroup uip + * @{ + */ + +/** + * \file + * Header file for the uIP TCP/IP stack. + * \author Adam Dunkels + * + * The uIP TCP/IP stack header file contains definitions for a number + * of C macros that are used by uIP programs as well as internal uIP + * structures, TCP/IP header structures and function declarations. + * + */ + +/* + * Copyright (c) 2001-2003, Adam Dunkels. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack. + * + * + */ + +#ifndef __UIP_H__ +#define __UIP_H__ + +#include +#include + +#include "uipopt.h" + +#include "debug.h" + +#include "uip_eth.h" + +/* Forware declaration */ +struct uip_stack; + +/** + * Repressentation of an IP address. + * + */ +typedef u16_t uip_ip4addr_t[2]; +typedef u16_t uip_ip6addr_t[8]; + +const uip_ip6addr_t all_zeroes_addr6; +const uip_ip4addr_t all_zeroes_addr4; + +#define ETH_BUF(buf) ((struct uip_eth_hdr *)buf) +#define VLAN_ETH_BUF(buf) ((struct uip_vlan_eth_hdr *)buf) +#define IPv4_BUF(buf) ((struct uip_tcp_ipv4_hdr *)buf) +#define IPv6_BUF(buf) ((struct uip_tcp_ipv6_hdr *)buf) + +/*---------------------------------------------------------------------------*/ +/* First, the functions that should be called from the + * system. Initialization, the periodic timer and incoming packets are + * handled by the following three functions. + */ + +/** + * Set the IP address of this host. + * + * The IP address is represented as a 4-byte array where the first + * octet of the IP address is put in the first member of the 4-byte + * array. + * + * Example: + \code + + uip_ipaddr_t addr; + + uip_ipaddr(&addr, 192,168,1,2); + uip_sethostaddr(&addr); + + \endcode + * \param addr A pointer to an IP address of type uip_ipaddr_t; + * + * \sa uip_ipaddr() + * + * \hideinitializer + */ +void uip_sethostaddr4(struct uip_stack *ustack, uip_ip4addr_t *addr); + +/** + * Set the default router's IP address. + * + * \param addr A pointer to a uip_ipaddr_t variable containing the IP + * address of the default router. + * + * \sa uip_ipaddr() + * + * \hideinitializer + */ +void uip_setdraddr4(struct uip_stack *ustack, uip_ip4addr_t *addr); + +/** + * Set the netmask. + * + * \param addr A pointer to a uip_ipaddr_t variable containing the IP + * address of the netmask. + * + * \sa uip_ipaddr() + * + * \hideinitializer + */ +void uip_setnetmask4(struct uip_stack *ustack, uip_ip4addr_t *addr); + +/** + * Set the ethernet MAC address. + * + * \param addr A pointer to a uip_ipaddr_t variable containing the IP + * address of the netmask. + * + * \sa uip_ipaddr() + * + * \hideinitializer + */ +void uip_setethernetmac(struct uip_stack *ustack, uint8_t *mac); + +/** + * Get the default router's IP address. + * + * \param addr A pointer to a uip_ipaddr_t variable that will be + * filled in with the IP address of the default router. + * + * \hideinitializer + */ +#define uip_getdraddr(addr) uip_ipaddr_copy((addr), uip_draddr) + +/** + * Get the netmask. + * + * \param addr A pointer to a uip_ipaddr_t variable that will be + * filled in with the value of the netmask. + * + * \hideinitializer + */ +#define uip_getnetmask(addr) uip_ipaddr_copy((addr), uip_netmask) + +void set_uip_stack(struct uip_stack *ustack, + uip_ip4addr_t *ip, + uip_ip4addr_t *netmask, + uip_ip4addr_t *default_route, uint8_t *mac_addr); + +/** @} */ + +/** + * \defgroup uipinit uIP initialization functions + * @{ + * + * The uIP initialization functions are used for booting uIP. + */ + +/** + * uIP initialization function. + * + * This function should be called at boot up to initilize the uIP + * TCP/IP stack. + */ +void uip_init(struct uip_stack *ustack, uint8_t enable_ipv6); + +/** + * uIP reset function. + * + * This function should be called at to reset the uIP TCP/IP stack. + */ +void uip_reset(struct uip_stack *ustack); + +/** + * uIP initialization function. + * + * This function may be used at boot time to set the initial ip_id. + */ +void uip_setipid(u16_t id); + +/** + * + * + */ +#define uip_conn_active(conn) (uip_conns[conn].tcpstateflags != UIP_CLOSED) + +#if UIP_UDP +void uip_udp_periodic(struct uip_stack *ustack, int conn); +#endif /* UIP_UDP */ + +void uip_ndp_periodic(struct uip_stack *ustack); + +/** + * The uIP packet buffer. + * + * The uip_buf array is used to hold incoming and outgoing + * packets. The device driver should place incoming data into this + * buffer. When sending data, the device driver should read the link + * level headers and the TCP/IP headers from this buffer. The size of + * the link level headers is configured by the UIP_LLH_LEN define. + * + * \note The application data need not be placed in this buffer, so + * the device driver must read it from the place pointed to by the + * uip_appdata pointer as illustrated by the following example: + \code + void + devicedriver_send(void) + { + hwsend(&uip_buf[0], UIP_LLH_LEN); + if(uip_len <= UIP_LLH_LEN + UIP_TCPIP_HLEN) { + hwsend(&uip_buf[UIP_LLH_LEN], uip_len - UIP_LLH_LEN); + } else { + hwsend(&uip_buf[UIP_LLH_LEN], UIP_TCPIP_HLEN); + hwsend(uip_appdata, uip_len - UIP_TCPIP_HLEN - UIP_LLH_LEN); + } + } + \endcode + */ +/*extern u8_t uip_buf[UIP_BUFSIZE+2]; */ + +/** @} */ + +/*---------------------------------------------------------------------------*/ +/* Functions that are used by the uIP application program. Opening and + * closing connections, sending and receiving data, etc. is all + * handled by the functions below. +*/ +/** + * \defgroup uipappfunc uIP application functions + * @{ + * + * Functions used by an application running of top of uIP. + */ + +/** + * Start listening to the specified port. + * + * \note Since this function expects the port number in network byte + * order, a conversion using HTONS() or htons() is necessary. + * + \code + uip_listen(HTONS(80)); + \endcode + * + * \param port A 16-bit port number in network byte order. + */ +void uip_listen(struct uip_stack *ustack, u16_t port); + +/** + * Stop listening to the specified port. + * + * \note Since this function expects the port number in network byte + * order, a conversion using HTONS() or htons() is necessary. + * + \code + uip_unlisten(HTONS(80)); + \endcode + * + * \param port A 16-bit port number in network byte order. + */ +void uip_unlisten(struct uip_stack *ustack, u16_t port); + +/** + * Connect to a remote host using TCP. + * + * This function is used to start a new connection to the specified + * port on the specied host. It allocates a new connection identifier, + * sets the connection to the SYN_SENT state and sets the + * retransmission timer to 0. This will cause a TCP SYN segment to be + * sent out the next time this connection is periodically processed, + * which usually is done within 0.5 seconds after the call to + * uip_connect(). + * + * \note This function is avaliable only if support for active open + * has been configured by defining UIP_ACTIVE_OPEN to 1 in uipopt.h. + * + * \note Since this function requires the port number to be in network + * byte order, a conversion using HTONS() or htons() is necessary. + * + \code + uip_ipaddr_t ipaddr; + + uip_ipaddr(&ipaddr, 192,168,1,2); + uip_connect(&ipaddr, HTONS(80)); + \endcode + * + * \param ripaddr The IP address of the remote hot. + * + * \param port A 16-bit port number in network byte order. + * + * \return A pointer to the uIP connection identifier for the new connection, + * or NULL if no connection could be allocated. + * + */ +struct uip_conn *uip_connect(struct uip_stack *ustack, + uip_ip4addr_t *ripaddr, u16_t port); + +/** + * \internal + * + * Check if a connection has outstanding (i.e., unacknowledged) data. + * + * \param conn A pointer to the uip_conn structure for the connection. + * + * \hideinitializer + */ +#define uip_outstanding(conn) ((conn)->len) + +/** + * Send data on the current connection. + * + * This function is used to send out a single segment of TCP + * data. Only applications that have been invoked by uIP for event + * processing can send data. + * + * The amount of data that actually is sent out after a call to this + * funcion is determined by the maximum amount of data TCP allows. uIP + * will automatically crop the data so that only the appropriate + * amount of data is sent. The function uip_mss() can be used to query + * uIP for the amount of data that actually will be sent. + * + * \note This function does not guarantee that the sent data will + * arrive at the destination. If the data is lost in the network, the + * application will be invoked with the uip_rexmit() event being + * set. The application will then have to resend the data using this + * function. + * + * \param data A pointer to the data which is to be sent. + * + * \param len The maximum amount of data bytes to be sent. + * + * \hideinitializer + */ +void uip_send(struct uip_stack *ustack, const void *data, int len); +void uip_appsend(struct uip_stack *ustack, const void *data, int len); + +/** + * The length of any incoming data that is currently avaliable (if avaliable) + * in the uip_appdata buffer. + * + * The test function uip_data() must first be used to check if there + * is any data available at all. + * + * \hideinitializer + */ +/*void uip_datalen(void);*/ +u16_t uip_datalen(struct uip_stack *ustack); + +/** + * The length of any out-of-band data (urgent data) that has arrived + * on the connection. + * + * \note The configuration parameter UIP_URGDATA must be set for this + * function to be enabled. + * + * \hideinitializer + */ +#define uip_urgdatalen() uip_urglen + +/** + * Close the current connection. + * + * This function will close the current connection in a nice way. + * + * \hideinitializer + */ +#define uip_close() (uip_flags = UIP_CLOSE) + +/** + * Abort the current connection. + * + * This function will abort (reset) the current connection, and is + * usually used when an error has occured that prevents using the + * uip_close() function. + * + * \hideinitializer + */ +#define uip_abort() (uip_flags = UIP_ABORT) + +/** + * Tell the sending host to stop sending data. + * + * This function will close our receiver's window so that we stop + * receiving data for the current connection. + * + * \hideinitializer + */ +#define uip_stop() (uip_conn->tcpstateflags |= UIP_STOPPED) + +/** + * Find out if the current connection has been previously stopped with + * uip_stop(). + * + * \hideinitializer + */ +#define uip_stopped(conn) ((conn)->tcpstateflags & UIP_STOPPED) + +/** + * Restart the current connection, if is has previously been stopped + * with uip_stop(). + * + * This function will open the receiver's window again so that we + * start receiving data for the current connection. + * + * \hideinitializer + */ +#define uip_restart() do { uip_flags |= UIP_NEWDATA; \ + uip_conn->tcpstateflags &= ~UIP_STOPPED; \ + } while (0) + +/* uIP tests that can be made to determine in what state the current + connection is, and what the application function should do. */ + +/** + * Is the current connection a UDP connection? + * + * This function checks whether the current connection is a UDP connection. + * + * \hideinitializer + * + */ +#define uip_udpconnection() (uip_conn == NULL) + +/** + * Function declarations for hte uip_flags + */ +/** + * Is new incoming data available? + * + * Will reduce to non-zero if there is new data for the application + * present at the uip_appdata pointer. The size of the data is + * avaliable through the uip_len variable. + * + * \hideinitializer + */ +int uip_newdata(struct uip_stack *ustack); + +/** + * Has previously sent data been acknowledged? + * + * Will reduce to non-zero if the previously sent data has been + * acknowledged by the remote host. This means that the application + * can send new data. + * + * \hideinitializer + */ +int uip_acked(struct uip_stack *ustack); + +/** + * Has the connection just been connected? + * + * Reduces to non-zero if the current connection has been connected to + * a remote host. This will happen both if the connection has been + * actively opened (with uip_connect()) or passively opened (with + * uip_listen()). + * + * \hideinitializer + */ +int uip_connected(struct uip_stack *ustack); + +/** + * Has the connection been closed by the other end? + * + * Is non-zero if the connection has been closed by the remote + * host. The application may then do the necessary clean-ups. + * + * \hideinitializer + */ +int uip_closed(struct uip_stack *ustack); + +/** + * Has the connection been aborted by the other end? + * + * Non-zero if the current connection has been aborted (reset) by the + * remote host. + * + * \hideinitializer + */ +int uip_aborted(struct uip_stack *ustack); + +/** + * Has the connection timed out? + * + * Non-zero if the current connection has been aborted due to too many + * retransmissions. + * + * \hideinitializer + */ +int uip_timedout(struct uip_stack *ustack); + +/** + * Do we need to retransmit previously data? + * + * Reduces to non-zero if the previously sent data has been lost in + * the network, and the application should retransmit it. The + * application should send the exact same data as it did the last + * time, using the uip_send() function. + * + * \hideinitializer + */ +int uip_rexmit(struct uip_stack *ustack); + +/** + * Is the connection being polled by uIP? + * + * Is non-zero if the reason the application is invoked is that the + * current connection has been idle for a while and should be + * polled. + * + * The polling event can be used for sending data without having to + * wait for the remote host to send data. + * + * \hideinitializer + */ +int uip_poll(struct uip_stack *ustack); + +/** + * Get the initial maxium segment size (MSS) of the current + * connection. + * + * \hideinitializer + */ +int uip_initialmss(struct uip_stack *ustack); + +/** + * Get the current maxium segment size that can be sent on the current + * connection. + * + * The current maxiumum segment size that can be sent on the + * connection is computed from the receiver's window and the MSS of + * the connection (which also is available by calling + * uip_initialmss()). + * + * \hideinitializer + */ +int uip_mss(struct uip_stack *ustack); + +/** + * Set up a new UDP connection. + * + * This function sets up a new UDP connection. The function will + * automatically allocate an unused local port for the new + * connection. However, another port can be chosen by using the + * uip_udp_bind() call, after the uip_udp_new() function has been + * called. + * + * Example: + \code + uip_ipaddr_t addr; + struct uip_udp_conn *c; + + uip_ipaddr(&addr, 192,168,2,1); + c = uip_udp_new(&addr, HTONS(12345)); + if(c != NULL) { + uip_udp_bind(c, HTONS(12344)); + } + \endcode + * \param ripaddr The IP address of the remote host. + * + * \param rport The remote port number in network byte order. + * + * \return The uip_udp_conn structure for the new connection or NULL + * if no connection could be allocated. + */ +struct uip_udp_conn *uip_udp_new(struct uip_stack *ustack, + uip_ip4addr_t *ripaddr, u16_t rport); + +/** + * Removed a UDP connection. + * + * \param conn A pointer to the uip_udp_conn structure for the connection. + * + * \hideinitializer + */ +#define uip_udp_remove(conn) ((conn)->lport = 0) + +/** + * Bind a UDP connection to a local port. + * + * \param conn A pointer to the uip_udp_conn structure for the + * connection. + * + * \param port The local port number, in network byte order. + * + * \hideinitializer + */ +#define uip_udp_bind(conn, port) ((conn)->lport = port) + +/** + * Send a UDP datagram of length len on the current connection. + * + * This function can only be called in response to a UDP event (poll + * or newdata). The data must be present in the uip_buf buffer, at the + * place pointed to by the uip_appdata pointer. + * + * \param len The length of the data in the uip_buf buffer. + * + * \hideinitializer + */ +#define uip_udp_send(len) uip_appsend((char *)uip_appdata, len) + +/** @} */ + +/* uIP convenience and converting functions. */ + +/** + * \defgroup uipconvfunc uIP conversion functions + * @{ + * + * These functions can be used for converting between different data + * formats used by uIP. + */ + +/** + * Construct an IP address from four bytes. + * + * This function constructs an IP address of the type that uIP handles + * internally from four bytes. The function is handy for specifying IP + * addresses to use with e.g. the uip_connect() function. + * + * Example: + \code + uip_ipaddr_t ipaddr; + struct uip_conn *c; + + uip_ipaddr(&ipaddr, 192,168,1,2); + c = uip_connect(&ipaddr, HTONS(80)); + \endcode + * + * \param addr A pointer to a uip_ipaddr_t variable that will be + * filled in with the IP address. + * + * \param addr0 The first octet of the IP address. + * \param addr1 The second octet of the IP address. + * \param addr2 The third octet of the IP address. + * \param addr3 The forth octet of the IP address. + * + * \hideinitializer + */ +#define uip_ipaddr(addr, addr0, addr1, addr2, addr3) do { \ + ((u16_t *)(addr))[0] = const_htons(((addr0) << 8) | (addr1)); \ + ((u16_t *)(addr))[1] = const_htons(((addr2) << 8) | (addr3)); \ + } while (0) + +/** + * Construct an IPv6 address from eight 16-bit words. + * + * This function constructs an IPv6 address. + * + * \hideinitializer + */ +#define uip_ip6addr(addr, addr0, addr1, addr2, addr3, addr4, addr5, addr6, \ + addr7) \ + do { \ + ((u16_t *)(addr))[0] = HTONS((addr0)); \ + ((u16_t *)(addr))[1] = HTONS((addr1)); \ + ((u16_t *)(addr))[2] = HTONS((addr2)); \ + ((u16_t *)(addr))[3] = HTONS((addr3)); \ + ((u16_t *)(addr))[4] = HTONS((addr4)); \ + ((u16_t *)(addr))[5] = HTONS((addr5)); \ + ((u16_t *)(addr))[6] = HTONS((addr6)); \ + ((u16_t *)(addr))[7] = HTONS((addr7)); \ + } while (0) + +/** + * Copy an IP address to another IP address. + * + * Copies an IP address from one place to another. + * + * Example: + \code + uip_ipaddr_t ipaddr1, ipaddr2; + + uip_ipaddr(&ipaddr1, 192,16,1,2); + uip_ipaddr_copy(&ipaddr2, &ipaddr1); + \endcode + * + * \param dest The destination for the copy. + * \param src The source from where to copy. + * + * \hideinitializer + */ +#define uip_ip4addr_copy(dest, src) memcpy(dest, src, sizeof(uip_ip4addr_t)) +#define uip_ip6addr_copy(dest, src) memcpy(dest, src, sizeof(uip_ip6addr_t)) + +/** + * Compare two IP addresses + * + * Compares two IP addresses. + * + * Example: + \code + uip_ipaddr_t ipaddr1, ipaddr2; + + uip_ipaddr(&ipaddr1, 192,16,1,2); + if(uip_ipaddr_cmp(&ipaddr2, &ipaddr1)) { + printf("They are the same"); + } + \endcode + * + * \param addr1 The first IP address. + * \param addr2 The second IP address. + * + * \hideinitializer + */ +#define uip_ip4addr_cmp(addr1, addr2) (memcmp(addr1, addr2, \ + sizeof(uip_ip4addr_t)) == 0) +#define uip_ip6addr_cmp(addr1, addr2) (memcmp(addr1, addr2, \ + sizeof(uip_ip6addr_t)) == 0) + +/** + * Compare two IP addresses with netmasks + * + * Compares two IP addresses with netmasks. The masks are used to mask + * out the bits that are to be compared. + * + * Example: + \code + uip_ipaddr_t ipaddr1, ipaddr2, mask; + + uip_ipaddr(&mask, 255,255,255,0); + uip_ipaddr(&ipaddr1, 192,16,1,2); + uip_ipaddr(&ipaddr2, 192,16,1,3); + if(uip_ipaddr_maskcmp(&ipaddr1, &ipaddr2, &mask)) { + printf("They are the same"); + } + \endcode + * + * \param addr1 The first IP address. + * \param addr2 The second IP address. + * \param mask The netmask. + * + * \hideinitializer + */ +#define uip_ip4addr_maskcmp(addr1, addr2, mask) \ + (((((u16_t *)addr1)[0] & ((u16_t *)mask)[0]) == \ + (((u16_t *)addr2)[0] & ((u16_t *)mask)[0])) && \ + ((((u16_t *)addr1)[1] & ((u16_t *)mask)[1]) == \ + (((u16_t *)addr2)[1] & ((u16_t *)mask)[1]))) + +/** + * Mask out the network part of an IP address. + * + * Masks out the network part of an IP address, given the address and + * the netmask. + * + * Example: + \code + uip_ipaddr_t ipaddr1, ipaddr2, netmask; + + uip_ipaddr(&ipaddr1, 192,16,1,2); + uip_ipaddr(&netmask, 255,255,255,0); + uip_ipaddr_mask(&ipaddr2, &ipaddr1, &netmask); + \endcode + * + * In the example above, the variable "ipaddr2" will contain the IP + * address 192.168.1.0. + * + * \param dest Where the result is to be placed. + * \param src The IP address. + * \param mask The netmask. + * + * \hideinitializer + */ +#define uip_ip4addr_mask(dest, src, mask) do { \ + ((u16_t *)dest)[0] = ((u16_t *)src)[0] & ((u16_t *)mask)[0]; \ + ((u16_t *)dest)[1] = ((u16_t *)src)[1] & ((u16_t *)mask)[1]; \ + } while (0) + +/** + * Pick the first octet of an IP address. + * + * Picks out the first octet of an IP address. + * + * Example: + \code + uip_ipaddr_t ipaddr; + u8_t octet; + + uip_ipaddr(&ipaddr, 1,2,3,4); + octet = uip_ipaddr1(&ipaddr); + \endcode + * + * In the example above, the variable "octet" will contain the value 1. + * + * \hideinitializer + */ +#define uip_ipaddr1(addr) (htons(((u16_t *)(addr))[0]) >> 8) + +/** + * Pick the second octet of an IP address. + * + * Picks out the second octet of an IP address. + * + * Example: + \code + uip_ipaddr_t ipaddr; + u8_t octet; + + uip_ipaddr(&ipaddr, 1,2,3,4); + octet = uip_ipaddr2(&ipaddr); + \endcode + * + * In the example above, the variable "octet" will contain the value 2. + * + * \hideinitializer + */ +#define uip_ipaddr2(addr) (htons(((u16_t *)(addr))[0]) & 0xff) + +/** + * Pick the third octet of an IP address. + * + * Picks out the third octet of an IP address. + * + * Example: + \code + uip_ipaddr_t ipaddr; + u8_t octet; + + uip_ipaddr(&ipaddr, 1,2,3,4); + octet = uip_ipaddr3(&ipaddr); + \endcode + * + * In the example above, the variable "octet" will contain the value 3. + * + * \hideinitializer + */ +#define uip_ipaddr3(addr) (htons(((u16_t *)(addr))[1]) >> 8) + +/** + * Pick the fourth octet of an IP address. + * + * Picks out the fourth octet of an IP address. + * + * Example: + \code + uip_ipaddr_t ipaddr; + u8_t octet; + + uip_ipaddr(&ipaddr, 1,2,3,4); + octet = uip_ipaddr4(&ipaddr); + \endcode + * + * In the example above, the variable "octet" will contain the value 4. + * + * \hideinitializer + */ +#define uip_ipaddr4(addr) (htons(((u16_t *)(addr))[1]) & 0xff) + +/** + * Convert 16-bit quantity from host byte order to network byte order. + * + * This macro is primarily used for converting constants from host + * byte order to network byte order. For converting variables to + * network byte order, use the htons() function instead. + * + * \hideinitializer + */ +#if 0 +#ifndef HTONS +# if UIP_BYTE_ORDER == UIP_BIG_ENDIAN +# define HTONS(n) (n) +# else /* UIP_BYTE_ORDER == UIP_BIG_ENDIAN */ +# define HTONS(n) (u16_t)((((u16_t) (n)) << 8) | (((u16_t) (n)) >> 8)) +# endif /* UIP_BYTE_ORDER == UIP_BIG_ENDIAN */ +#else +#error "HTONS already defined!" +#endif /* HTONS */ +#endif + +#if UIP_BYTE_ORDER == UIP_BIG_ENDIAN +# error "Should not be here" +# define const_htons(n) (n) +# else /* UIP_BYTE_ORDER == UIP_BIG_ENDIAN */ +# define const_htons(n) (u16_t)((((u16_t) (n)) << 8) | (((u16_t) (n)) >> 8)) +# endif /* UIP_BYTE_ORDER == UIP_BIG_ENDIAN */ + +/* BWL */ +#if 0 +/** + * Convert 16-bit quantity from host byte order to network byte order. + * + * This function is primarily used for converting variables from host + * byte order to network byte order. For converting constants to + * network byte order, use the HTONS() macro instead. + */ +#ifndef htons +u16_t htons(u16_t val); +#endif /* htons */ +#ifndef ntohs +#define ntohs htons +#endif +#endif + +/** @} */ + +/** + * Pointer to the application data in the packet buffer. + * + * This pointer points to the application data when the application is + * called. If the application wishes to send data, the application may + * use this space to write the data into before calling uip_send(). + */ +/* extern void *uip_appdata; */ + +#if UIP_URGDATA > 0 +/* u8_t *uip_urgdata: + * + * This pointer points to any urgent data that has been received. Only + * present if compiled with support for urgent data (UIP_URGDATA). + */ +extern void *uip_urgdata; +#endif /* UIP_URGDATA > 0 */ + +/** + * \defgroup uipdrivervars Variables used in uIP device drivers + * @{ + * + * uIP has a few global variables that are used in device drivers for + * uIP. + */ + +/** + * The length of the packet in the uip_buf buffer. + * + * The global variable uip_len holds the length of the packet in the + * uip_buf buffer. + * + * When the network device driver calls the uIP input function, + * uip_len should be set to the length of the packet in the uip_buf + * buffer. + * + * When sending packets, the device driver should use the contents of + * the uip_len variable to determine the length of the outgoing + * packet. + * + */ +/* extern u16_t uip_len; */ + +/** @} */ + +#if UIP_URGDATA > 0 +extern u16_t uip_urglen, uip_surglen; +#endif /* UIP_URGDATA > 0 */ + +/** + * Representation of a uIP TCP connection. + * + * The uip_conn structure is used for identifying a connection. All + * but one field in the structure are to be considered read-only by an + * application. The only exception is the appstate field whos purpose + * is to let the application store application-specific state (e.g., + * file pointers) for the connection. The type of this field is + * configured in the "uipopt.h" header file. + */ +struct __attribute__ ((__packed__)) uip_conn { + uip_ip4addr_t ripaddr; + uip_ip6addr_t ripaddr6; + /**< The IP address of the remote host. */ + + u16_t lport; /**< The local TCP port, in network byte order. */ + u16_t rport; /**< The local remote TCP port, in network byte + order. */ + + u8_t rcv_nxt[4]; + /**< The sequence number that we expect to + receive next. */ + u8_t snd_nxt[4]; + /**< The sequence number that was last sent by + us. */ + u16_t len; /**< Length of the data that was previously sent. */ + u16_t mss; /**< Current maximum segment size for the + connection. */ + u16_t initialmss; + /**< Initial maximum segment size for the + connection. */ + u8_t sa; /**< Retransmission time-out calculation state + variable. */ + u8_t sv; /**< Retransmission time-out calculation state + variable. */ + u8_t rto; /**< Retransmission time-out. */ + u8_t tcpstateflags; + /**< TCP state and flags. */ + u8_t timer; /**< The retransmission timer. */ + u8_t nrtx; /**< The number of retransmissions for the last + segment sent. */ +}; + +/** + * \addtogroup uiparch + * @{ + */ + +/** + * 4-byte array used for the 32-bit sequence number calculations. + */ +extern u8_t uip_acc32[4]; + +/** @} */ + +#if UIP_UDP +/** + * Representation of a uIP UDP connection. + */ +struct uip_udp_conn { + uip_ip4addr_t ripaddr; + /**< The IP address of the remote peer. */ + u16_t lport; /**< The local port number in network byte order. */ + u16_t rport; /**< The remote port number in network byte order. */ + u8_t ttl; /**< Default time-to-live. */ + + /** The application state. */ +/* uip_udp_appstate_t appstate; */ +}; + +#endif /* UIP_UDP */ + +/** + * The structure holding the TCP/IP statistics that are gathered if + * UIP_STATISTICS is set to 1. + * + */ +struct uip_stats { + struct { + uip_stats_t drop; + /**< Number of dropped packets at the IP + layer. */ + uip_stats_t recv; + /**< Number of received packets at the IP + layer. */ + uip_stats_t sent; + /**< Number of sent packets at the IP + layer. */ + uip_stats_t vhlerr; + /**< Number of packets dropped due to wrong + IP version or header length. */ + uip_stats_t hblenerr; + /**< Number of packets dropped due to wrong + IP length, high byte. */ + uip_stats_t lblenerr; + /**< Number of packets dropped due to wrong + IP length, low byte. */ + uip_stats_t fragerr; + /**< Number of packets dropped since they + were IP fragments. */ + uip_stats_t chkerr; + /**< Number of packets dropped due to IP + checksum errors. */ + uip_stats_t protoerr; + /**< Number of packets dropped since they + were neither ICMP, UDP nor TCP. */ + } ip; /**< IP statistics. */ + struct { + uip_stats_t drop; + /**< Number of dropped ICMP packets. */ + uip_stats_t recv; + /**< Number of received ICMP packets. */ + uip_stats_t sent; + /**< Number of sent ICMP packets. */ + uip_stats_t typeerr; + /**< Number of ICMP packets with a wrong + type. */ + } icmp; /**< ICMP statistics. */ + struct { + uip_stats_t drop; + /**< Number of dropped TCP segments. */ + uip_stats_t recv; + /**< Number of recived TCP segments. */ + uip_stats_t sent; + /**< Number of sent TCP segments. */ + uip_stats_t chkerr; + /**< Number of TCP segments with a bad + checksum. */ + uip_stats_t ackerr; + /**< Number of TCP segments with a bad ACK + number. */ + uip_stats_t rst; + /**< Number of recevied TCP RST (reset) segments. */ + uip_stats_t rexmit; + /**< Number of retransmitted TCP segments. */ + uip_stats_t syndrop; + /**< Number of dropped SYNs due to too few + connections was avaliable. */ + uip_stats_t synrst; + /**< Number of SYNs for closed ports, + triggering a RST. */ + } tcp; /**< TCP statistics. */ +#if UIP_UDP + struct { + uip_stats_t drop; + /**< Number of dropped UDP segments. */ + uip_stats_t recv; + /**< Number of recived UDP segments. */ + uip_stats_t sent; + /**< Number of sent UDP segments. */ + uip_stats_t chkerr; + /**< Number of UDP segments with a bad + checksum. */ + } udp; /**< UDP statistics. */ +#endif /* UIP_UDP */ +}; + +/*---------------------------------------------------------------------------*/ +/* All the stuff below this point is internal to uIP and should not be + * used directly by an application or by a device driver. + */ +/*---------------------------------------------------------------------------*/ +/* u8_t uip_flags: + * + * When the application is called, uip_flags will contain the flags + * that are defined in this file. Please read below for more + * infomation. + */ +/* extern u8_t uip_flags; */ + +/* The following flags may be set in the global variable uip_flags + before calling the application callback. The UIP_ACKDATA, + UIP_NEWDATA, and UIP_CLOSE flags may both be set at the same time, + whereas the others are mutualy exclusive. Note that these flags + should *NOT* be accessed directly, but only through the uIP + functions/macros. */ + +#define UIP_ACKDATA 1 /* Signifies that the outstanding data was + acked and the application should send + out new data instead of retransmitting + the last data. */ +#define UIP_NEWDATA 2 /* Flags the fact that the peer has sent + us new data. */ +#define UIP_REXMIT 4 /* Tells the application to retransmit the + data that was last sent. */ +#define UIP_POLL 8 /* Used for polling the application, to + check if the application has data that + it wants to send. */ +#define UIP_CLOSE 16 /* The remote host has closed the + connection, thus the connection has + gone away. Or the application signals + that it wants to close the + connection. */ +#define UIP_ABORT 32 /* The remote host has aborted the + connection, thus the connection has + gone away. Or the application signals + that it wants to abort the + connection. */ +#define UIP_CONNECTED 64 /* We have got a connection from a remote + host and have set up a new connection + for it, or an active connection has + been successfully established. */ + +#define UIP_TIMEDOUT 128 /* The connection has been aborted due to + too many retransmissions. */ + +void uip_input(struct uip_stack *ustack); +void uip_periodic(struct uip_stack *ustack, int conn); + +/* uip_process(flag): + * + * The actual uIP function which does all the work. + */ +void uip_process(struct uip_stack *ustack, u8_t flag); + +/* The following flags are passed as an argument to the uip_process() + function. They are used to distinguish between the two cases where + uip_process() is called. It can be called either because we have + incoming data that should be processed, or because the periodic + timer has fired. These values are never used directly, but only in + the macrose defined in this file. */ + +#define UIP_DATA 1 /* Tells uIP that there is incoming + data in the uip_buf buffer. The + length of the data is stored in the + global variable uip_len. */ +#define UIP_TIMER 2 /* Tells uIP that the periodic timer + has fired. */ +#define UIP_POLL_REQUEST 3 /* Tells uIP that a connection should + be polled. */ +#define UIP_UDP_SEND_CONN 4 /* Tells uIP that a UDP datagram + should be constructed in the + uip_buf buffer. */ +#if UIP_UDP +#define UIP_UDP_TIMER 5 +#endif /* UIP_UDP */ + +#define UIP_NDP_TIMER 6 + +/* The TCP states used in the uip_conn->tcpstateflags. */ +#define UIP_CLOSED 0 +#define UIP_SYN_RCVD 1 +#define UIP_SYN_SENT 2 +#define UIP_ESTABLISHED 3 +#define UIP_FIN_WAIT_1 4 +#define UIP_FIN_WAIT_2 5 +#define UIP_CLOSING 6 +#define UIP_TIME_WAIT 7 +#define UIP_LAST_ACK 8 +#define UIP_TS_MASK 15 + +#define UIP_STOPPED 16 + +struct __attribute__ ((__packed__)) uip_tcp_hdr { + /* TCP header. */ + u16_t srcport, destport; + u8_t seqno[4], ackno[4], tcpoffset, flags, wnd[2]; + u16_t tcpchksum; + u8_t urgp[2]; + u8_t optdata[4]; +}; + +struct __attribute__ ((__packed__)) uip_ipv4_hdr { + /* IPv4 header. */ + u8_t vhl, tos, len[2], ipid[2], ipoffset[2], ttl, proto; + u16_t ipchksum; + u16_t srcipaddr[2], destipaddr[2]; +}; + +struct __attribute__ ((__packed__)) uip_ipv6_hdr { + /* IPv6 header. */ + u8_t vtc, tcflow; + u16_t flow; + u16_t len; + u8_t proto, ttl; + uip_ip6addr_t srcipaddr, destipaddr; +}; + +/* The TCP and IPv4 headers. */ +struct __attribute__ ((__packed__)) uip_tcp_ipv4_hdr { + /* IPv4 header. */ + u8_t vhl, tos, len[2], ipid[2], ipoffset[2], ttl, proto; + u16_t ipchksum; + u16_t srcipaddr[2], destipaddr[2]; + + /* TCP header. */ + u16_t srcport, destport; + u8_t seqno[4], ackno[4], tcpoffset, flags, wnd[2]; + u16_t tcpchksum; + u8_t urgp[2]; + u8_t optdata[4]; +}; + +/* The TCP and IP headers. */ +struct __attribute__ ((__packed__)) uip_tcp_ipv6_hdr { + /* IPv6 header. */ + u8_t vtc, tcflow; + u16_t flow; + u8_t len[2]; + u8_t proto, ttl; + uip_ip6addr_t srcipaddr, destipaddr; + + /* TCP header. */ + u16_t srcport, destport; + u8_t seqno[4], ackno[4], tcpoffset, flags, wnd[2]; + u16_t tcpchksum; + u8_t urgp[2]; + u8_t optdata[4]; +}; + +/* The ICMPv4 */ +struct __attribute__ ((__packed__)) uip_icmpv4_hdr { + /* ICMP (echo) header. */ + u8_t type, icode; + u16_t icmpchksum; + u16_t id, seqno; +}; + +typedef struct uip_icmpv4_hdr uip_icmp_echo_hdr_t; + +/* The ICMPv6 */ +struct __attribute__ ((__packed__)) uip_icmpv6_hdr { + /* ICMP (echo) header. */ + u8_t type, icode; + u16_t icmpchksum; + u8_t flags, reserved1, reserved2, reserved3; + u8_t icmp6data[16]; + u8_t options[1]; +}; + +/* The ICMP and IP headers. */ +struct __attribute__ ((__packed__)) uip_icmpip_hdr { +#if UIP_CONF_IPV6 + /* IPv6 header. */ + u8_t vtc, tcf; + u16_t flow; + u8_t len[2]; + u8_t proto, ttl; + uip_ip6addr_t srcipaddr, destipaddr; +#else /* UIP_CONF_IPV6 */ + /* IPv4 header. */ + u8_t vhl, tos, len[2], ipid[2], ipoffset[2], ttl, proto; + u16_t ipchksum; + u16_t srcipaddr[2], destipaddr[2]; +#endif /* UIP_CONF_IPV6 */ + + /* ICMP (echo) header. */ + u8_t type, icode; + u16_t icmpchksum; +#if !UIP_CONF_IPV6 + u16_t id, seqno; +#else /* !UIP_CONF_IPV6 */ + u8_t flags, reserved1, reserved2, reserved3; + u8_t icmp6data[16]; + u8_t options[1]; +#endif /* !UIP_CONF_IPV6 */ +}; + +/* The UDP */ +struct __attribute__ ((__packed__)) uip_udp_hdr { + /* UDP header. */ + u16_t srcport, destport; + u16_t udplen; + u16_t udpchksum; +}; + +/* The UDP and IP headers. */ +struct __attribute__ ((__packed__)) uip_udpip_hdr { +#if UIP_CONF_IPV6 + /* IPv6 header. */ + u8_t vtc, tcf; + u16_t flow; + u8_t len[2]; + u8_t proto, ttl; + uip_ip6addr_t srcipaddr, destipaddr; +#else /* UIP_CONF_IPV6 */ + /* IP header. */ + u8_t vhl, tos, len[2], ipid[2], ipoffset[2], ttl, proto; + u16_t ipchksum; + u16_t srcipaddr[2], destipaddr[2]; +#endif /* UIP_CONF_IPV6 */ + + /* UDP header. */ + u16_t srcport, destport; + u16_t udplen; + u16_t udpchksum; +}; + +/** + * The buffer size available for user data in the \ref uip_buf buffer. + * + * This macro holds the available size for user data in the \ref + * uip_buf buffer. The macro is intended to be used for checking + * bounds of available user data. + * + * Example: + \code + snprintf(uip_appdata, UIP_APPDATA_SIZE, "%u\n", i); + \endcode + * + * \hideinitializer + */ +#define UIP_APPDATA_SIZE (UIP_BUFSIZE - UIP_LLH_LEN - UIP_TCPIP_HLEN) + +#define UIP_PROTO_ICMP 1 +#define UIP_PROTO_TCP 6 +#define UIP_PROTO_UDP 17 +#define UIP_PROTO_ICMP6 58 + +/* Header sizes. */ +#define UIP_IPv6_H_LEN 40 /* Size of IPv6 header */ +#define UIP_IPv4_H_LEN 20 /* Size of IPv4 header */ + +#define UIP_UDPH_LEN 8 /* Size of UDP header */ +#define UIP_TCPH_LEN 20 /* Size of TCP header */ + +#define UIP_IPv4_UDPH_LEN (UIP_UDPH_LEN + UIP_IPv4_H_LEN) /* Size of IPv4 + + UDP + header */ +#define UIP_IPv4_TCPH_LEN (UIP_TCPH_LEN + UIP_IPv4_H_LEN) /* Size of IPv4 + + TCP + header */ +#define UIP_TCP_IPv4_HLEN UIP_IPv4_TCPH_LEN + +#define UIP_IPv6_UDPH_LEN (UIP_UDPH_LEN + UIP_IPv6_H_LEN) /* Size of IPv6 + + UDP + header */ +#define UIP_IPv6_TCPH_LEN (UIP_TCPH_LEN + UIP_IPv6_H_LEN) /* Size of IPv6 + + TCP + header */ +#define UIP_TCP_IPv6_HLEN UIP_IPv6_TCPH_LEN + +/** + * Calculate the Internet checksum over a buffer. + * + * The Internet checksum is the one's complement of the one's + * complement sum of all 16-bit words in the buffer. + * + * See RFC1071. + * + * \param buf A pointer to the buffer over which the checksum is to be + * computed. + * + * \param len The length of the buffer over which the checksum is to + * be computed. + * + * \return The Internet checksum of the buffer. + */ +u16_t uip_chksum(u16_t *buf, u16_t len); + +/** + * Calculate the IP header checksum of the packet header in uip_buf. + * + * The IP header checksum is the Internet checksum of the 20 bytes of + * the IP header. + * + * \return The IP header checksum of the IP header in the uip_buf + * buffer. + */ +u16_t uip_ipchksum(struct uip_stack *ustack); + +/** + * Calculate the TCP checksum of the packet in uip_buf and uip_appdata. + * + * The TCP checksum is the Internet checksum of data contents of the + * TCP segment, and a pseudo-header as defined in RFC793. + * + * \return The TCP checksum of the TCP segment in uip_buf and pointed + * to by uip_appdata. + */ +u16_t uip_tcpchksum(struct uip_stack *ustack); + +/** + * Calculate the UDP checksum of the packet in uip_buf and uip_appdata. + * + * The UDP checksum is the Internet checksum of data contents of the + * UDP segment, and a pseudo-header as defined in RFC768. + * + * \return The UDP checksum of the UDP segment in uip_buf and pointed + * to by uip_appdata. + */ +u16_t uip_udpchksum(struct uip_stack *ustack); + +/* IPv6 checksum */ +uint16_t icmpv6_checksum(uint8_t *data); + +struct neighbor_entry { + struct in6_addr ipaddr; + struct uip_eth_addr mac_addr; + u8_t time; +}; + +struct uip_stack { + struct uip_eth_addr uip_ethaddr; + + u8_t *uip_buf; + + uint8_t *data_link_layer; /* Pointer to the data link layer */ + uint8_t *network_layer; /* Pointer to the network layer */ + void *uip_appdata; /* The uip_appdata pointer points to + application data. */ + void *uip_sappdata; /* The uip_appdata pointer points to + the application data which is to + be sent. */ +#if UIP_URGDATA > 0 + void *uip_urgdata; /* The uip_urgdata pointer points to + urgent data (out-of-band data), if + present. */ + u16_t uip_urglen, uip_surglen; +#endif /* UIP_URGDATA > 0 */ + + u16_t uip_len, uip_slen; /* The uip_len is either 8 or 16 bits, + depending on the maximum packet + size. */ + u8_t uip_flags; /* The uip_flags variable is used for + communication between the TCP/IP stack + and the application program. */ + struct uip_conn *uip_conn; /* uip_conn always points to the current + connection. */ + + struct uip_conn uip_conns[UIP_CONNS]; + /* The uip_conns array holds all TCP + connections. */ + u16_t uip_listenports[UIP_LISTENPORTS]; + /* The uip_listenports list all currently + listning ports. */ +#if UIP_UDP + struct uip_udp_conn *uip_udp_conn; + struct uip_udp_conn uip_udp_conns[UIP_UDP_CONNS]; +#endif /* UIP_UDP */ + + u16_t ipid; /* This ipid variable is an increasing + number that is used for the IP ID + field. */ + + u8_t iss[4]; /* The iss variable is used for the TCP + initial sequence number. */ + +#if UIP_ACTIVE_OPEN + u16_t lastport; /* Keeps track of the last port used for + a new connection. */ +#endif /* UIP_ACTIVE_OPEN */ + +#define IP_CONFIG_OFF 0x00 +#define IPV4_CONFIG_OFF 0x01 +#define IPV4_CONFIG_STATIC 0x02 +#define IPV4_CONFIG_DHCP 0x04 +#define IPV6_CONFIG_OFF 0x10 +#define IPV6_CONFIG_STATIC 0x20 +#define IPV6_CONFIG_DHCP 0x40 + u8_t ip_config; + + uip_ip4addr_t hostaddr, netmask, default_route_addr; + uip_ip6addr_t hostaddr6, netmask6, default_route_addr6, + linklocal6; + int prefix_len; + u8_t ipv6_autocfg; +#define IPV6_AUTOCFG_DHCPV6 (1<<0) +#define IPV6_AUTOCFG_ND (1<<1) +#define IPV6_AUTOCFG_NOTSPEC (1<<6) +#define IPV6_AUTOCFG_NOTUSED (1<<7) + u8_t linklocal_autocfg; +#define IPV6_LL_AUTOCFG_ON (1<<0) +#define IPV6_LL_AUTOCFG_OFF (1<<1) +#define IPV6_LL_AUTOCFG_NOTSPEC (1<<6) +#define IPV6_LL_AUTOCFG_NOTUSED (1<<7) + u8_t router_autocfg; +#define IPV6_RTR_AUTOCFG_ON (1<<0) +#define IPV6_RTR_AUTOCFG_OFF (1<<1) +#define IPV6_RTR_AUTOCFG_NOTSPEC (1<<6) +#define IPV6_RTR_AUTOCFG_NOTUSED (1<<7) + +#define UIP_NEIGHBOR_ENTRIES 8 + struct neighbor_entry neighbor_entries[UIP_NEIGHBOR_ENTRIES]; + + struct uip_stats stats; + + u8_t opt; + + pthread_mutex_t lock; + + /* IPv6 support */ +#define UIP_SUPPORT_IPv6_ENABLED 0x01 +#define UIP_SUPPORT_IPv6_DISABLED 0x02 + u8_t enable_IPv6; + + /* DHCPC client attached */ + void *dhcpc; + + /* NDP client */ + void *ndpc; + + void *ping_conf; +}; + +/******************************************************************************* + * IPv6 Support + ******************************************************************************/ +int set_ipv6_link_local_address(struct uip_stack *ustack); +int is_ipv6_link_local_address(uip_ip6addr_t *addr); + +void dump_uip_packet(struct uip_stack *ustack); +u16_t uip_icmp6chksum(struct uip_stack *ustack); + +#endif /* __UIP_H__ */ + +/** @} */ diff --git a/iscsiuio/src/uip/uip_arch.h b/iscsiuio/src/uip/uip_arch.h new file mode 100644 index 0000000..3ddacec --- /dev/null +++ b/iscsiuio/src/uip/uip_arch.h @@ -0,0 +1,137 @@ +/** + * \addtogroup uip + * {@ + */ + +/** + * \defgroup uiparch Architecture specific uIP functions + * @{ + * + * The functions in the architecture specific module implement the IP + * check sum and 32-bit additions. + * + * The IP checksum calculation is the most computationally expensive + * operation in the TCP/IP stack and it therefore pays off to + * implement this in efficient assembler. The purpose of the uip-arch + * module is to let the checksum functions to be implemented in + * architecture specific assembler. + * + */ + +/** + * \file + * Declarations of architecture specific functions. + * \author Adam Dunkels + */ + +/* + * Copyright (c) 2001, Adam Dunkels. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack. + * + * + */ + +#ifndef __UIP_ARCH_H__ +#define __UIP_ARCH_H__ + +#include "uip.h" + +/** + * Carry out a 32-bit addition. + * + * Because not all architectures for which uIP is intended has native + * 32-bit arithmetic, uIP uses an external C function for doing the + * required 32-bit additions in the TCP protocol processing. This + * function should add the two arguments and place the result in the + * global variable uip_acc32. + * + * \note The 32-bit integer pointed to by the op32 parameter and the + * result in the uip_acc32 variable are in network byte order (big + * endian). + * + * \param op32 A pointer to a 4-byte array representing a 32-bit + * integer in network byte order (big endian). + * + * \param op16 A 16-bit integer in host byte order. + */ +void uip_add32(u8_t *op32, u16_t op16, u8_t *uip_add32); + +/** + * Calculate the Internet checksum over a buffer. + * + * The Internet checksum is the one's complement of the one's + * complement sum of all 16-bit words in the buffer. + * + * See RFC1071. + * + * \note This function is not called in the current version of uIP, + * but future versions might make use of it. + * + * \param buf A pointer to the buffer over which the checksum is to be + * computed. + * + * \param len The length of the buffer over which the checksum is to + * be computed. + * + * \return The Internet checksum of the buffer. + */ +u16_t uip_chksum(u16_t *buf, u16_t len); + +/** + * Calculate the IP header checksum of the packet header in uip_buf. + * + * The IP header checksum is the Internet checksum of the 20 bytes of + * the IP header. + * + * \return The IP header checksum of the IP header in the uip_buf + * buffer. + */ +u16_t uip_ipchksum(struct uip_stack *ustack); + +/** + * Calculate the TCP checksum of the packet in uip_buf and uip_appdata. + * + * The TCP checksum is the Internet checksum of data contents of the + * TCP segment, and a pseudo-header as defined in RFC793. + * + * \note The uip_appdata pointer that points to the packet data may + * point anywhere in memory, so it is not possible to simply calculate + * the Internet checksum of the contents of the uip_buf buffer. + * + * \return The TCP checksum of the TCP segment in uip_buf and pointed + * to by uip_appdata. + */ +u16_t uip_tcpchksum(struct uip_stack *ustack); + +u16_t uip_udpchksum(struct uip_stack *ustack); + +/** @} */ +/** @} */ + +#endif /* __UIP_ARCH_H__ */ diff --git a/iscsiuio/src/uip/uip_arp.c b/iscsiuio/src/uip/uip_arp.c new file mode 100644 index 0000000..1b3761f --- /dev/null +++ b/iscsiuio/src/uip/uip_arp.c @@ -0,0 +1,479 @@ +#include +#include +#include + +#include "logger.h" +#include "packet.h" + +/** + * \addtogroup uip + * @{ + */ + +/** + * \defgroup uiparp uIP Address Resolution Protocol + * @{ + * + * The Address Resolution Protocol ARP is used for mapping between IP + * addresses and link level addresses such as the Ethernet MAC + * addresses. ARP uses broadcast queries to ask for the link level + * address of a known IP address and the host which is configured with + * the IP address for which the query was meant, will respond with its + * link level address. + * + * \note This ARP implementation only supports Ethernet. + */ + +/** + * \file + * Implementation of the ARP Address Resolution Protocol. + * \author Adam Dunkels + * + */ + +/* + * Copyright (c) 2001-2003, Adam Dunkels. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack. + * + * + */ + +#include "uip_arp.h" +#include "uip_eth.h" + +#include +#include + +static const struct uip_eth_addr broadcast_ethaddr = { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff} }; +static const u16_t broadcast_ipaddr[2] = { 0xffff, 0xffff }; + +pthread_mutex_t arp_table_mutex = PTHREAD_MUTEX_INITIALIZER; +static struct arp_entry arp_table[UIP_ARPTAB_SIZE]; + +static u8_t arptime; + +/** + * Initialize the ARP module. + * + */ +/*----------------------------------------------------------------------------*/ +void uip_arp_init(void) +{ + u8_t i; + for (i = 0; i < UIP_ARPTAB_SIZE; ++i) + memset(&arp_table[i], 0, sizeof(arp_table[i])); + + pthread_mutex_init(&arp_table_mutex, NULL); +} + +/*----------------------------------------------------------------------------*/ +/** + * Periodic ARP processing function. + * + * This function performs periodic timer processing in the ARP module + * and should be called at regular intervals. The recommended interval + * is 10 seconds between the calls. + * + */ +/*----------------------------------------------------------------------------*/ +void uip_arp_timer(void) +{ + u8_t i; + struct arp_entry *tabptr; + + ++arptime; + for (i = 0; i < UIP_ARPTAB_SIZE; ++i) { + tabptr = &arp_table[i]; + if ((tabptr->ipaddr[0] | tabptr->ipaddr[1]) != 0 && + (u8_t)(arptime - tabptr->time) >= UIP_ARP_MAXAGE) + memset(tabptr->ipaddr, 0, 4); + } + +} + +/*----------------------------------------------------------------------------*/ +static void uip_arp_update(u16_t *ipaddr, struct uip_eth_addr *ethaddr) +{ + u8_t i; + struct arp_entry *tabptr; + + pthread_mutex_lock(&arp_table_mutex); + /* Walk through the ARP mapping table and try to find an entry to + update. If none is found, the IP -> MAC address mapping is + inserted in the ARP table. */ + for (i = 0; i < UIP_ARPTAB_SIZE; ++i) { + + tabptr = &arp_table[i]; + /* Only check those entries that are actually in use. */ + if (tabptr->ipaddr[0] != 0 && tabptr->ipaddr[1] != 0) { + + /* Check if the source IP address of the incoming packet + matches the IP address in this ARP table entry. */ + if (ipaddr[0] == tabptr->ipaddr[0] && + ipaddr[1] == tabptr->ipaddr[1]) { + + tabptr->time = arptime; + + pthread_mutex_unlock(&arp_table_mutex); + return; + } + } + } + + /* If we get here, no existing ARP table entry was found, so we + create one. */ + + /* First, we try to find an unused entry in the ARP table. */ + for (i = 0; i < UIP_ARPTAB_SIZE; ++i) { + tabptr = &arp_table[i]; + if (tabptr->ipaddr[0] == 0 && tabptr->ipaddr[1] == 0) + break; + } + + /* If no unused entry is found, we try to find the oldest entry and + throw it away. */ + if (i == UIP_ARPTAB_SIZE) { + u8_t c; + u8_t tmpage = 0; + c = 0; + for (i = 0; i < UIP_ARPTAB_SIZE; ++i) { + tabptr = &arp_table[i]; + if ((u8_t)(arptime - tabptr->time) > tmpage) { + tmpage = (u8_t)(arptime - tabptr->time); + c = i; + } + } + i = c; + tabptr = &arp_table[i]; + } + + /* Now, i is the ARP table entry which we will fill with the new + information. */ + memcpy(tabptr->ipaddr, ipaddr, 4); + memcpy(tabptr->ethaddr.addr, ethaddr->addr, 6); + tabptr->time = arptime; + + pthread_mutex_unlock(&arp_table_mutex); +} + +/** + * ARP processing for incoming ARP packets. + * + * This function should be called by the device driver when an ARP + * packet has been received. The function will act differently + * depending on the ARP packet type: if it is a reply for a request + * that we previously sent out, the ARP cache will be filled in with + * the values from the ARP reply. If the incoming ARP packet is an ARP + * request for our IP address, an ARP reply packet is created and put + * into the uip_buf[] buffer. + * + * When the function returns, the value of the global variable uip_len + * indicates whether the device driver should send out a packet or + * not. If uip_len is zero, no packet should be sent. If uip_len is + * non-zero, it contains the length of the outbound packet that is + * present in the uip_buf[] buffer. + * + * This function expects an ARP packet with a prepended Ethernet + * header in the uip_buf[] buffer, and the length of the packet in the + * global variable uip_len. + */ +void uip_arp_ipin(struct uip_stack *ustack, packet_t *pkt) +{ + struct ip_hdr *ip; + struct uip_eth_hdr *eth; + + eth = (struct uip_eth_hdr *)pkt->data_link_layer; + ip = (struct ip_hdr *)pkt->network_layer; + + if (uip_ip4addr_cmp(ip->destipaddr, ustack->hostaddr)) { + /* First, we register the one who made the request in our ARP + table, since it is likely that we will do more communication + with this host in the future. */ + uip_arp_update(ip->srcipaddr, ð->src); + } +} + +void +uip_arp_arpin(nic_interface_t *nic_iface, + struct uip_stack *ustack, packet_t *pkt) +{ + struct arp_hdr *arp; + struct uip_eth_hdr *eth; + + if (pkt->buf_size < sizeof(struct arp_hdr)) { + pkt->buf_size = 0; + return; + } + pkt->buf_size = 0; + + eth = (struct uip_eth_hdr *)pkt->data_link_layer; + arp = (struct arp_hdr *)pkt->network_layer; + + switch (arp->opcode) { + case const_htons(ARP_REQUEST): + /* ARP request. If it asked for our address, we send out a + reply. */ + if (uip_ip4addr_cmp(arp->dipaddr, ustack->hostaddr)) { + /* First, we register the one who made the request in + our ARP table, since it is likely that we will do + more communication with this host in the future. */ + uip_arp_update(arp->sipaddr, &arp->shwaddr); + + /* The reply opcode is 2. */ + arp->opcode = htons(2); + + memcpy(arp->dhwaddr.addr, arp->shwaddr.addr, 6); + memcpy(arp->shwaddr.addr, ustack->uip_ethaddr.addr, 6); + memcpy(eth->src.addr, ustack->uip_ethaddr.addr, 6); + memcpy(eth->dest.addr, arp->dhwaddr.addr, 6); + + arp->dipaddr[0] = arp->sipaddr[0]; + arp->dipaddr[1] = arp->sipaddr[1]; + arp->sipaddr[0] = ustack->hostaddr[0]; + arp->sipaddr[1] = ustack->hostaddr[1]; + + if (nic_iface->vlan_id == 0) { + eth->type = htons(UIP_ETHTYPE_ARP); + pkt->buf_size = sizeof(*arp) + sizeof(*eth); + } else { + eth->type = htons(UIP_ETHTYPE_8021Q); + pkt->buf_size = sizeof(*arp) + + sizeof(struct uip_vlan_eth_hdr); + } + } + break; + case const_htons(ARP_REPLY): + uip_arp_update(arp->sipaddr, &arp->shwaddr); + break; + default: + LOG_WARN("Unknown ARP opcode: %d", ntohs(arp->opcode)); + break; + } + + return; +} + +/** + * Prepend Ethernet header to an outbound IP packet and see if we need + * to send out an ARP request. + * + * This function should be called before sending out an IP packet. The + * function checks the destination IP address of the IP packet to see + * what Ethernet MAC address that should be used as a destination MAC + * address on the Ethernet. + * + * If the destination IP address is in the local network (determined + * by logical ANDing of netmask and our IP address), the function + * checks the ARP cache to see if an entry for the destination IP + * address is found. If so, an Ethernet header is prepended and the + * function returns. If no ARP cache entry is found for the + * destination IP address, the packet in the uip_buf[] is replaced by + * an ARP request packet for the IP address. The IP packet is dropped + * and it is assumed that they higher level protocols (e.g., TCP) + * eventually will retransmit the dropped packet. + * + * If the destination IP address is not on the local network, the IP + * address of the default router is used instead. + * + * When the function returns, a packet is present in the uip_buf[] + * buffer, and the length of the packet is in the global variable + * uip_len. + */ + +dest_ipv4_addr_t +uip_determine_dest_ipv4_addr(struct uip_stack *ustack, u16_t *ipaddr) +{ + struct uip_eth_hdr *eth; + struct ip_hdr *ip_buf; + + eth = (struct uip_eth_hdr *)ustack->data_link_layer; + ip_buf = (struct ip_hdr *)ustack->network_layer; + + /* Find the destination IP address in the ARP table and construct + the Ethernet header. If the destination IP addres isn't on the + local network, we use the default router's IP address instead. + + If not ARP table entry is found, we overwrite the original IP + packet with an ARP request for the IP address. */ + + /* First check if destination is a local broadcast. */ + if (uip_ip4addr_cmp(ip_buf->destipaddr, broadcast_ipaddr)) { + memcpy(ð->dest, broadcast_ethaddr.addr, 6); + + return LOCAL_BROADCAST; + } else { + /* Check if the destination address is on the local network. */ + if (!uip_ip4addr_maskcmp(ip_buf->destipaddr, + ustack->hostaddr, ustack->netmask)) { + /* Destination address was not on the local network, + so we need to use the default router's IP address + instead of the destination address when determining + the MAC address. */ + uip_ip4addr_copy(ipaddr, ustack->default_route_addr); + } else { + /* Else, we use the destination IP address. */ + uip_ip4addr_copy(ipaddr, ip_buf->destipaddr); + } + + return NONLOCAL_BROADCAST; + } +} + +arp_out_t is_in_arp_table(u16_t *ipaddr, struct arp_entry **tabptr) +{ + u8_t i; + + pthread_mutex_lock(&arp_table_mutex); + + for (i = 0; i < UIP_ARPTAB_SIZE; ++i) { + if (uip_ip4addr_cmp(ipaddr, arp_table[i].ipaddr)) { + *tabptr = &arp_table[i]; + break; + } + } + + pthread_mutex_unlock(&arp_table_mutex); + + if (i == UIP_ARPTAB_SIZE) + return NOT_IN_ARP_TABLE; + else + return IS_IN_ARP_TABLE; +} + +void uip_build_arp_request(struct uip_stack *ustack, u16_t *ipaddr) +{ + struct arp_hdr *arp; + struct uip_eth_hdr *eth; + + arp = (struct arp_hdr *)ustack->network_layer; + eth = (struct uip_eth_hdr *)ustack->data_link_layer; + + /* The destination address was not in our ARP table, so we + overwrite the IP packet with an ARP request. */ + + memset(eth->dest.addr, 0xff, 6); + memset(arp->dhwaddr.addr, 0x00, 6); + memcpy(eth->src.addr, ustack->uip_ethaddr.addr, 6); + memcpy(arp->shwaddr.addr, ustack->uip_ethaddr.addr, 6); + + uip_ip4addr_copy(arp->dipaddr, ipaddr); + uip_ip4addr_copy(arp->sipaddr, ustack->hostaddr); + arp->opcode = const_htons(ARP_REQUEST); /* ARP request. */ + arp->hwtype = const_htons(ARP_HWTYPE_ETH); + arp->protocol = const_htons(UIP_ETHTYPE_IPv4); + arp->hwlen = 6; + arp->protolen = 4; + eth->type = const_htons(UIP_ETHTYPE_ARP); + + ustack->uip_appdata = &ustack->uip_buf[UIP_TCP_IPv4_HLEN + UIP_LLH_LEN]; + + ustack->uip_len = sizeof(*arp) + sizeof(*eth); +} + +void +uip_build_eth_header(struct uip_stack *ustack, + u16_t *ipaddr, + struct arp_entry *tabptr, + struct packet *pkt, u16_t vlan_id) +{ + struct uip_ipv4_hdr *ip_buf; + struct uip_eth_hdr *eth; + struct uip_vlan_eth_hdr *eth_vlan; + + ip_buf = (struct uip_ipv4_hdr *)ustack->network_layer; + eth = (struct uip_eth_hdr *)ustack->data_link_layer; + eth_vlan = (struct uip_vlan_eth_hdr *)ustack->data_link_layer; + + /* First check if destination is a local broadcast. */ + if (uip_ip4addr_cmp(ip_buf->destipaddr, broadcast_ipaddr)) { + memcpy(eth->dest.addr, broadcast_ethaddr.addr, 6); + } else { + /* Build an ethernet header. */ + memcpy(eth->dest.addr, tabptr->ethaddr.addr, 6); + } + memcpy(eth->src.addr, ustack->uip_ethaddr.addr, 6); + + if (vlan_id == 0) { + eth->type = htons(UIP_ETHTYPE_IPv4); + + ustack->uip_len += sizeof(struct uip_eth_hdr); + pkt->buf_size += sizeof(struct uip_eth_hdr); + } else { + eth_vlan->tpid = htons(UIP_ETHTYPE_8021Q); + eth_vlan->vid = htons(vlan_id); + eth_vlan->type = htons(UIP_ETHTYPE_IPv4); + + ustack->uip_len += sizeof(struct uip_vlan_eth_hdr); + pkt->buf_size += sizeof(struct uip_vlan_eth_hdr); + } +} + +static struct arp_entry *uip_get_arp_entry(int index) +{ + return &arp_table[index]; +} + +int uip_lookup_arp_entry(uint32_t ip_addr, uint8_t *mac_addr) +{ + int i; + int rc = -EINVAL; + + pthread_mutex_lock(&arp_table_mutex); + + for (i = 0; i < UIP_ARPTAB_SIZE; ++i) { + struct arp_entry *entry = uip_get_arp_entry(i); + + if (((entry->ipaddr[1] << 16) == (ip_addr & 0xffff0000)) && + ((entry->ipaddr[0]) == (ip_addr & 0x0000ffff))) { + struct in_addr addr; + char *addr_str; + + addr.s_addr = ip_addr; + addr_str = inet_ntoa(addr); + + memcpy(mac_addr, entry->ethaddr.addr, 6); + + LOG_INFO("Found %s at %02x:%02x:%02x:%02x:%02x:%02x", + addr_str, + mac_addr[0], mac_addr[1], mac_addr[2], + mac_addr[3], mac_addr[4], mac_addr[5]); + rc = 0; + break; + } + } + + pthread_mutex_unlock(&arp_table_mutex); + return rc; +} + +/*----------------------------------------------------------------------------*/ + +/** @} */ +/** @} */ diff --git a/iscsiuio/src/uip/uip_arp.h b/iscsiuio/src/uip/uip_arp.h new file mode 100644 index 0000000..339d57d --- /dev/null +++ b/iscsiuio/src/uip/uip_arp.h @@ -0,0 +1,197 @@ +/** + * \addtogroup uip + * @{ + */ + +/** + * \addtogroup uiparp + * @{ + */ + +/** + * \file + * Macros and definitions for the ARP module. + * \author Adam Dunkels + */ + +/* + * Copyright (c) 2001-2003, Adam Dunkels. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack. + * + * + */ + +#ifndef __UIP_ARP_H__ +#define __UIP_ARP_H__ + +#include "packet.h" +#include "uip.h" +#include "uip_eth.h" + +#define ARP_REQUEST 1 +#define ARP_REPLY 2 + +#define ARP_HWTYPE_ETH 1 + +struct __attribute__ ((__packed__)) arp_hdr { + u16_t hwtype; + u16_t protocol; + u8_t hwlen; + u8_t protolen; + u16_t opcode; + struct uip_eth_addr shwaddr; + u16_t sipaddr[2]; + struct uip_eth_addr dhwaddr; + u16_t dipaddr[2]; +}; + +struct __attribute__ ((__packed__)) ip_hdr { + /* IP header. */ + u8_t vhl, tos, len[2], ipid[2], ipoffset[2], ttl, proto; + u16_t ipchksum; + u16_t srcipaddr[2], destipaddr[2]; +}; + +struct __attribute__ ((__packed__)) ethip_hdr { + struct uip_eth_hdr ethhdr; + /* IP header. */ + u8_t vhl, tos, len[2], ipid[2], ipoffset[2], ttl, proto; + u16_t ipchksum; + u16_t srcipaddr[2], destipaddr[2]; +}; + +struct arp_entry { + u16_t ipaddr[2]; + struct uip_eth_addr ethaddr; + u8_t time; +}; + +/* The uip_arp_init() function must be called before any of the other + ARP functions. */ +void uip_arp_init(void); + +/* The uip_arp_ipin() function should be called whenever an IP packet + arrives from the Ethernet. This function refreshes the ARP table or + inserts a new mapping if none exists. The function assumes that an + IP packet with an Ethernet header is present in the uip_buf buffer + and that the length of the packet is in the uip_len variable. */ +/*void uip_arp_ipin(void);*/ +/* #define uip_arp_ipin() */ +void uip_arp_ipin(struct uip_stack *ustack, struct packet *pkt); + +/* The uip_arp_arpin() should be called when an ARP packet is received + by the Ethernet driver. This function also assumes that the + Ethernet frame is present in the uip_buf buffer. When the + uip_arp_arpin() function returns, the contents of the uip_buf + buffer should be sent out on the Ethernet if the uip_len variable + is > 0. */ +void uip_arp_arpin(nic_interface_t *nic_iface, + struct uip_stack *ustack, struct packet *pkt); + +typedef enum { + ARP_SENT = 1, + ETH_HEADER_APPEDEND = 2, +} arp_out_t; + +typedef enum { + LOCAL_BROADCAST = 1, + NONLOCAL_BROADCAST = 2, +} dest_ipv4_addr_t; + +typedef enum { + IS_IN_ARP_TABLE = 1, + NOT_IN_ARP_TABLE = 2, +} arp_table_query_t; + +dest_ipv4_addr_t +uip_determine_dest_ipv4_addr(struct uip_stack *ustack, u16_t *ipaddr); +arp_out_t is_in_arp_table(u16_t *ipaddr, struct arp_entry **tabptr); + +void uip_build_arp_request(struct uip_stack *ustack, u16_t *ipaddr); + +void +uip_build_eth_header(struct uip_stack *ustack, + u16_t *ipaddr, + struct arp_entry *tabptr, + struct packet *pkt, u16_t vlan_id); + +/* The uip_arp_out() function should be called when an IP packet + should be sent out on the Ethernet. This function creates an + Ethernet header before the IP header in the uip_buf buffer. The + Ethernet header will have the correct Ethernet MAC destination + address filled in if an ARP table entry for the destination IP + address (or the IP address of the default router) is present. If no + such table entry is found, the IP packet is overwritten with an ARP + request and we rely on TCP to retransmit the packet that was + overwritten. In any case, the uip_len variable holds the length of + the Ethernet frame that should be transmitted. */ +arp_out_t uip_arp_out(struct uip_stack *ustack); + +/* The uip_arp_timer() function should be called every ten seconds. It + is responsible for flushing old entries in the ARP table. */ +void uip_arp_timer(void); + +int uip_lookup_arp_entry(uint32_t ip_addr, uint8_t *mac_addr); + +/** @} */ + +/** + * \addtogroup uipconffunc + * @{ + */ + +/** + * Specifiy the Ethernet MAC address. + * + * The ARP code needs to know the MAC address of the Ethernet card in + * order to be able to respond to ARP queries and to generate working + * Ethernet headers. + * + * \note This macro only specifies the Ethernet MAC address to the ARP + * code. It cannot be used to change the MAC address of the Ethernet + * card. + * + * \param eaddr A pointer to a struct uip_eth_addr containing the + * Ethernet MAC address of the Ethernet card. + * + * \hideinitializer + */ +#define uip_setethaddr(eaddr) do { \ + uip_ethaddr.addr[0] = eaddr.addr[0]; \ + uip_ethaddr.addr[1] = eaddr.addr[1]; \ + uip_ethaddr.addr[2] = eaddr.addr[2]; \ + uip_ethaddr.addr[3] = eaddr.addr[3]; \ + uip_ethaddr.addr[4] = eaddr.addr[4]; \ + uip_ethaddr.addr[5] = eaddr.addr[5]; \ + } while (0) + +/** @} */ +/** @} */ + +#endif /* __UIP_ARP_H__ */ diff --git a/iscsiuio/src/uip/uip_eth.c b/iscsiuio/src/uip/uip_eth.c new file mode 100644 index 0000000..9e1ea81 --- /dev/null +++ b/iscsiuio/src/uip/uip_eth.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * uip_eth.c - CNIC UIO uIP user space stack + * + */ + +#include "uip.h" +#include "uip_eth.h" + +int is_vlan_packet(struct uip_vlan_eth_hdr *hdr) +{ + /* The TPID field in a 802.1Q Header must be 0x8100 */ + if (hdr->tpid == const_htons(UIP_ETHTYPE_8021Q)) + return 1; + + return 0; +} diff --git a/iscsiuio/src/uip/uip_eth.h b/iscsiuio/src/uip/uip_eth.h new file mode 100644 index 0000000..830c04c --- /dev/null +++ b/iscsiuio/src/uip/uip_eth.h @@ -0,0 +1,43 @@ +#ifndef __UIP_ETH_H__ +#define __UIP_ETH_H__ + +#include "uipopt.h" + +/******************************************************************************* + * Ether types + ******************************************************************************/ +#define UIP_ETHTYPE_ARP 0x0806 +#define UIP_ETHTYPE_IPv4 0x0800 +#define UIP_ETHTYPE_8021Q 0x8100 +#define UIP_ETHTYPE_IPv6 0x86dd + +/** + * Representation of a 48-bit Ethernet address. + */ +struct uip_eth_addr { + u8_t addr[6]; +}; + +/** + * The Ethernet header. + */ +struct __attribute__ ((__packed__)) uip_eth_hdr { + struct uip_eth_addr dest; + struct uip_eth_addr src; + u16_t type; +}; + +/** + * The 802.1Q Ethernet header (VLAN). + */ +struct __attribute__ ((__packed__)) uip_vlan_eth_hdr { + struct uip_eth_addr dest; + struct uip_eth_addr src; + u16_t tpid; + u16_t vid; + u16_t type; +}; + +int is_vlan_packet(struct uip_vlan_eth_hdr *hdr); + +#endif /* __UIP_ETH_H__ */ diff --git a/iscsiuio/src/uip/uipopt.h b/iscsiuio/src/uip/uipopt.h new file mode 100644 index 0000000..bcc8949 --- /dev/null +++ b/iscsiuio/src/uip/uipopt.h @@ -0,0 +1,536 @@ +/** + * \defgroup uipopt Configuration options for uIP + * @{ + * + * uIP is configured using the per-project configuration file + * uipopt.h. This file contains all compile-time options for uIP and + * should be tweaked to match each specific project. The uIP + * distribution contains a documented example "uipopt.h" that can be + * copied and modified for each project. + * + * \note Most of the configuration options in the uipopt.h should not + * be changed, but rather the per-project uip-conf.h file. + */ + +/** + * \file + * Configuration options for uIP. + * \author Adam Dunkels + * + * This file is used for tweaking various configuration options for + * uIP. You should make a copy of this file into one of your project's + * directories instead of editing this example "uipopt.h" file that + * comes with the uIP distribution. + */ + +/* + * Copyright (c) 2001-2003, Adam Dunkels. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack. + * + * + */ + +#ifndef __UIPOPT_H__ +#define __UIPOPT_H__ + +#ifndef UIP_LITTLE_ENDIAN +#define UIP_LITTLE_ENDIAN 3412 +#endif /* UIP_LITTLE_ENDIAN */ +#ifndef UIP_BIG_ENDIAN +#define UIP_BIG_ENDIAN 1234 +#endif /* UIP_BIG_ENDIAN */ + +#include "uip-conf.h" + +/*----------------------------------------------------------------------------*/ + +/** + * \name Static configuration options + * @{ + * + * These configuration options can be used for setting the IP address + * settings statically, but only if UIP_FIXEDADDR is set to 1. The + * configuration options for a specific node includes IP address, + * netmask and default router as well as the Ethernet address. The + * netmask, default router and Ethernet address are appliciable only + * if uIP should be run over Ethernet. + * + * All of these should be changed to suit your project. +*/ + +/** + * Determines if uIP should use a fixed IP address or not. + * + * If uIP should use a fixed IP address, the settings are set in the + * uipopt.h file. If not, the macros uip_sethostaddr(), + * uip_setdraddr() and uip_setnetmask() should be used instead. + * + * \hideinitializer + */ +#define UIP_FIXEDADDR 0 + +/** + * Ping IP address asignment. + * + * uIP uses a "ping" packets for setting its own IP address if this + * option is set. If so, uIP will start with an empty IP address and + * the destination IP address of the first incoming "ping" (ICMP echo) + * packet will be used for setting the hosts IP address. + * + * \note This works only if UIP_FIXEDADDR is 0. + * + * \hideinitializer + */ +#ifdef UIP_CONF_PINGADDRCONF +#define UIP_PINGADDRCONF UIP_CONF_PINGADDRCONF +#else /* UIP_CONF_PINGADDRCONF */ +#define UIP_PINGADDRCONF 0 +#endif /* UIP_CONF_PINGADDRCONF */ + +/** + * Specifies if the uIP ARP module should be compiled with a fixed + * Ethernet MAC address or not. + * + * If this configuration option is 0, the macro uip_setethaddr() can + * be used to specify the Ethernet address at run-time. + * + * \hideinitializer + */ +#define UIP_FIXEDETHADDR 0 + +/** @} */ +/*------------------------------------------------------------------------------*/ +/** + * \name IP configuration options + * @{ + * + */ +/** + * The IP TTL (time to live) of IP packets sent by uIP. + * + * This should normally not be changed. + */ +#define UIP_TTL 64 + +/** + * Turn on support for IP packet reassembly. + * + * uIP supports reassembly of fragmented IP packets. This features + * requires an additonal amount of RAM to hold the reassembly buffer + * and the reassembly code size is approximately 700 bytes. The + * reassembly buffer is of the same size as the uip_buf buffer + * (configured by UIP_BUFSIZE). + * + * \note IP packet reassembly is not heavily tested. + * + * \hideinitializer + */ +#define UIP_REASSEMBLY 0 + +/** + * The maximum time an IP fragment should wait in the reassembly + * buffer before it is dropped. + * + */ +#define UIP_REASS_MAXAGE 40 + +/** @} */ + +/*----------------------------------------------------------------------------*/ +/** + * \name UDP configuration options + * @{ + */ + +/** + * Toggles wether UDP support should be compiled in or not. + * + * \hideinitializer + */ +#ifdef UIP_CONF_UDP +#define UIP_UDP UIP_CONF_UDP +#else /* UIP_CONF_UDP */ +#define UIP_UDP 0 +#endif /* UIP_CONF_UDP */ + +/** + * Toggles if UDP checksums should be used or not. + * + * \note Support for UDP checksums is currently not included in uIP, + * so this option has no function. + * + * \hideinitializer + */ +#ifdef UIP_CONF_UDP_CHECKSUMS +#define UIP_UDP_CHECKSUMS UIP_CONF_UDP_CHECKSUMS +#else +#define UIP_UDP_CHECKSUMS 0 +#endif + +/** + * The maximum amount of concurrent UDP connections. + * + * \hideinitializer + */ +#ifdef UIP_CONF_UDP_CONNS +#define UIP_UDP_CONNS UIP_CONF_UDP_CONNS +#else /* UIP_CONF_UDP_CONNS */ +#define UIP_UDP_CONNS 10 +#endif /* UIP_CONF_UDP_CONNS */ + +/** + * The name of the function that should be called when UDP datagrams arrive. + * + * \hideinitializer + */ + +/** @} */ +/*------------------------------------------------------------------------------*/ +/** + * \name TCP configuration options + * @{ + */ + +/** + * Determines if support for opening connections from uIP should be + * compiled in. + * + * If the applications that are running on top of uIP for this project + * do not need to open outgoing TCP connections, this configration + * option can be turned off to reduce the code size of uIP. + * + * \hideinitializer + */ +#define UIP_ACTIVE_OPEN 1 + +/** + * The maximum number of simultaneously open TCP connections. + * + * Since the TCP connections are statically allocated, turning this + * configuration knob down results in less RAM used. Each TCP + * connection requires approximatly 30 bytes of memory. + * + * \hideinitializer + */ +#ifndef UIP_CONF_MAX_CONNECTIONS +#define UIP_CONNS 10 +#else /* UIP_CONF_MAX_CONNECTIONS */ +#define UIP_CONNS UIP_CONF_MAX_CONNECTIONS +#endif /* UIP_CONF_MAX_CONNECTIONS */ + +/** + * The maximum number of simultaneously listening TCP ports. + * + * Each listening TCP port requires 2 bytes of memory. + * + * \hideinitializer + */ +#ifndef UIP_CONF_MAX_LISTENPORTS +#define UIP_LISTENPORTS 20 +#else /* UIP_CONF_MAX_LISTENPORTS */ +#define UIP_LISTENPORTS UIP_CONF_MAX_LISTENPORTS +#endif /* UIP_CONF_MAX_LISTENPORTS */ + +/** + * Determines if support for TCP urgent data notification should be + * compiled in. + * + * Urgent data (out-of-band data) is a rarely used TCP feature that + * very seldom would be required. + * + * \hideinitializer + */ +#define UIP_URGDATA 0 + +/** + * The initial retransmission timeout counted in timer pulses. + * + * This should not be changed. + */ +#define UIP_RTO 3 + +/** + * The maximum number of times a segment should be retransmitted + * before the connection should be aborted. + * + * This should not be changed. + */ +#define UIP_MAXRTX 8 + +/** + * The maximum number of times a SYN segment should be retransmitted + * before a connection request should be deemed to have been + * unsuccessful. + * + * This should not need to be changed. + */ +#define UIP_MAXSYNRTX 5 + +/** + * The TCP maximum segment size. + * + * This is should not be to set to more than + * UIP_BUFSIZE - UIP_LLH_LEN - UIP_TCPIP_HLEN. + */ +#define UIP_TCP_MSS (UIP_BUFSIZE - UIP_LLH_LEN - UIP_TCP_IPv4_HLEN) + +/** + * The size of the advertised receiver's window. + * + * Should be set low (i.e., to the size of the uip_buf buffer) is the + * application is slow to process incoming data, or high (32768 bytes) + * if the application processes data quickly. + * + * \hideinitializer + */ +#ifndef UIP_CONF_RECEIVE_WINDOW +#define UIP_RECEIVE_WINDOW UIP_TCP_MSS +#else +#define UIP_RECEIVE_WINDOW UIP_CONF_RECEIVE_WINDOW +#endif + +/** + * How long a connection should stay in the TIME_WAIT state. + * + * This configiration option has no real implication, and it should be + * left untouched. + */ +#define UIP_TIME_WAIT_TIMEOUT 120 + +/** @} */ +/*------------------------------------------------------------------------------*/ +/** + * \name ARP configuration options + * @{ + */ + +/** + * The size of the ARP table. + * + * This option should be set to a larger value if this uIP node will + * have many connections from the local network. + * + * \hideinitializer + */ +#ifdef UIP_CONF_ARPTAB_SIZE +#define UIP_ARPTAB_SIZE UIP_CONF_ARPTAB_SIZE +#else +#define UIP_ARPTAB_SIZE 16 +#endif + +/** + * The maxium age of ARP table entries measured in 10ths of seconds. + * + * An UIP_ARP_MAXAGE of 120 corresponds to 20 minutes (BSD + * default). + * Changed the default to 30 which corresponds to 5 minutes (Linux default) + */ +#define UIP_ARP_MAXAGE 30 + +/** @} */ + +/*----------------------------------------------------------------------------*/ + +/** + * \name General configuration options + * @{ + */ + +/** + * The size of the uIP packet buffer. + * + * The uIP packet buffer should not be smaller than 60 bytes, and does + * not need to be larger than 1500 bytes. Lower size results in lower + * TCP throughput, larger size results in higher TCP throughput. + * + * \hideinitializer + */ +#ifndef UIP_CONF_BUFFER_SIZE +#define UIP_BUFSIZE 400 +#else /* UIP_CONF_BUFFER_SIZE */ +#define UIP_BUFSIZE UIP_CONF_BUFFER_SIZE +#endif /* UIP_CONF_BUFFER_SIZE */ + +/** + * Determines if statistics support should be compiled in. + * + * The statistics is useful for debugging and to show the user. + * + * \hideinitializer + */ +#ifndef UIP_CONF_STATISTICS +#define UIP_STATISTICS 0 +#else /* UIP_CONF_STATISTICS */ +#define UIP_STATISTICS UIP_CONF_STATISTICS +#endif /* UIP_CONF_STATISTICS */ + +/** + * Determines if logging of certain events should be compiled in. + * + * This is useful mostly for debugging. The function uip_log() + * must be implemented to suit the architecture of the project, if + * logging is turned on. + * + * \hideinitializer + */ +#ifndef UIP_CONF_LOGGING +#define UIP_LOGGING 0 +#else /* UIP_CONF_LOGGING */ +#define UIP_LOGGING UIP_CONF_LOGGING +#endif /* UIP_CONF_LOGGING */ + +/** + * Broadcast support. + * + * This flag configures IP broadcast support. This is useful only + * together with UDP. + * + * \hideinitializer + * + */ +#ifndef UIP_CONF_BROADCAST +#define UIP_BROADCAST 0 +#else /* UIP_CONF_BROADCAST */ +#define UIP_BROADCAST UIP_CONF_BROADCAST +#endif /* UIP_CONF_BROADCAST */ + +/** + * Print out a uIP log message. + * + * This function must be implemented by the module that uses uIP, and + * is called by uIP whenever a log message is generated. + */ +void uip_log(char *msg); + +/** + * The link level header length. + * + * This is the offset into the uip_buf where the IP header can be + * found. For Ethernet, this should be set to 14. For SLIP, this + * should be set to 0. + * + * \hideinitializer + */ +#ifdef UIP_CONF_LLH_LEN +#define UIP_LLH_LEN UIP_CONF_LLH_LEN +#else /* UIP_CONF_LLH_LEN */ +#define UIP_LLH_LEN 14 +#endif /* UIP_CONF_LLH_LEN */ + +#if 0 +/** @} */ +/*------------------------------------------------------------------------------*/ +/** + * \name CPU architecture configuration + * @{ + * + * The CPU architecture configuration is where the endianess of the + * CPU on which uIP is to be run is specified. Most CPUs today are + * little endian, and the most notable exception are the Motorolas + * which are big endian. The BYTE_ORDER macro should be changed to + * reflect the CPU architecture on which uIP is to be run. + */ + +/** + * The byte order of the CPU architecture on which uIP is to be run. + * + * This option can be either BIG_ENDIAN (Motorola byte order) or + * LITTLE_ENDIAN (Intel byte order). + * + * \hideinitializer + */ +#ifdef UIP_CONF_BYTE_ORDER +#define UIP_BYTE_ORDER UIP_CONF_BYTE_ORDER +#else /* UIP_CONF_BYTE_ORDER */ +#define UIP_BYTE_ORDER UIP_LITTLE_ENDIAN +#endif /* UIP_CONF_BYTE_ORDER */ +#endif + +/** @} */ +/*------------------------------------------------------------------------------*/ + +/** + * \name Appication specific configurations + * @{ + * + * An uIP application is implemented using a single application + * function that is called by uIP whenever a TCP/IP event occurs. The + * name of this function must be registered with uIP at compile time + * using the UIP_APPCALL definition. + * + * uIP applications can store the application state within the + * uip_conn structure by specifying the type of the application + * structure by typedef:ing the type uip_tcp_appstate_t and uip_udp_appstate_t. + * + * The file containing the definitions must be included in the + * uipopt.h file. + * + * The following example illustrates how this can look. + \code + +void httpd_appcall(void); +#define UIP_APPCALL httpd_appcall + +struct httpd_state { + u8_t state; + u16_t count; + char *dataptr; + char *script; +}; +typedef struct httpd_state uip_tcp_appstate_t + \endcode + */ + +/** + * \var #define UIP_APPCALL + * + * The name of the application function that uIP should call in + * response to TCP/IP events. + * + */ + +/** + * \var typedef uip_tcp_appstate_t + * + * The type of the application state that is to be stored in the + * uip_conn structure. This usually is typedef:ed to a struct holding + * application state information. + */ + +/** + * \var typedef uip_udp_appstate_t + * + * The type of the application state that is to be stored in the + * uip_conn structure. This usually is typedef:ed to a struct holding + * application state information. + */ +/** @} */ +/** @} */ + +#endif /* __UIPOPT_H__ */ diff --git a/iscsiuio/src/unix/.gitignore b/iscsiuio/src/unix/.gitignore new file mode 100644 index 0000000..b3b37db --- /dev/null +++ b/iscsiuio/src/unix/.gitignore @@ -0,0 +1,3 @@ +build_date.c +build_date.h +iscsiuio diff --git a/iscsiuio/src/unix/Makefile.am b/iscsiuio/src/unix/Makefile.am new file mode 100644 index 0000000..a989ef0 --- /dev/null +++ b/iscsiuio/src/unix/Makefile.am @@ -0,0 +1,41 @@ +SUBDIRS= libs + +AM_CFLAGS = -I${top_srcdir}/src/uip \ + -I${top_srcdir}/src/apps/brcm-iscsi \ + -I${top_srcdir}/src/apps/dhcpc \ + -I${top_srcdir}/src/unix/libs \ + -I${top_srcdir}/../include \ + -I${top_srcdir}/../usr + +sbin_PROGRAMS = iscsiuio + +iscsiuio_SOURCES = build_date.c \ + main.c \ + clock-arch.c \ + logger.c \ + nic.c \ + nic_id.c \ + nic_vlan.c \ + nic_nl.c \ + nic_utils.c \ + packet.c \ + iscsid_ipc.c \ + ping.c \ + ${top_srcdir}/../utils/sysdeps/sysdeps.c + +iscsiuio_CFLAGS = $(AM_CFLAGS) \ + $(LIBNL_CFLAGS) \ + -DBYTE_ORDER=@ENDIAN@ + +iscsiuio_LDFLAGS= $(AM_LDADD) \ + -ldl \ + -rdynamic \ + $(LIBNL_LIBS) \ + -lpthread + +iscsiuio_LDADD = ${top_srcdir}/src/uip/lib_iscsi_uip.a \ + ${top_srcdir}/src/apps/dhcpc/lib_apps_dhcpc.a\ + ${top_srcdir}/src/apps/brcm-iscsi/lib_apps_brcm_iscsi.a \ + ${top_srcdir}/src/unix/libs/lib_iscsiuio_hw_cnic.a + +iscsiuio_YFLAGS = -d diff --git a/iscsiuio/src/unix/clock-arch.c b/iscsiuio/src/unix/clock-arch.c new file mode 100644 index 0000000..d853101 --- /dev/null +++ b/iscsiuio/src/unix/clock-arch.c @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack + * + */ + +/** + * \file + * Implementation of architecture-specific clock functionality + * \author + * Adam Dunkels + */ + +#include "clock-arch.h" +#include + +/*---------------------------------------------------------------------------*/ +clock_time_t clock_time(void) +{ + struct timeval tv; + struct timezone tz; + + gettimeofday(&tv, &tz); + + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +} + +/*---------------------------------------------------------------------------*/ diff --git a/iscsiuio/src/unix/clock-arch.h b/iscsiuio/src/unix/clock-arch.h new file mode 100644 index 0000000..888933f --- /dev/null +++ b/iscsiuio/src/unix/clock-arch.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack + * + */ + +#ifndef __CLOCK_ARCH_H__ +#define __CLOCK_ARCH_H__ + +typedef int clock_time_t; +#define CLOCK_CONF_SECOND 1000 + +#endif /* __CLOCK_ARCH_H__ */ diff --git a/iscsiuio/src/unix/iscsid_ipc.c b/iscsiuio/src/unix/iscsid_ipc.c new file mode 100644 index 0000000..2acac48 --- /dev/null +++ b/iscsiuio/src/unix/iscsid_ipc.c @@ -0,0 +1,1258 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * iscsi_ipc.c - Generic NIC management/utility functions + * + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PFX "iscsi_ipc " + +/* TODO fix me */ +#define IFNAMSIZ 15 + +#include "nic.h" +#include "nic_utils.h" +#include "nic_vlan.h" +#include "options.h" +#include "mgmt_ipc.h" +#include "iscsid_ipc.h" +#include "uip.h" +#include "uip_mgmt_ipc.h" +#include "sysdeps.h" + +#include "logger.h" +#include "uip.h" +#include "ping.h" + +/* private iscsid options stucture */ +struct iscsid_options { + int fd; + pthread_t thread; +}; + +struct iface_rec_decode { + /* General */ + int32_t iface_num; + uint32_t ip_type; + + /* IPv4 */ + struct in_addr ipv4_addr; + struct in_addr ipv4_subnet_mask; + struct in_addr ipv4_gateway; + + /* IPv6 */ + struct in6_addr ipv6_addr; + struct in6_addr ipv6_subnet_mask; + uint32_t prefix_len; + struct in6_addr ipv6_linklocal; + struct in6_addr ipv6_router; + + uint8_t ipv6_autocfg; + uint8_t linklocal_autocfg; + uint8_t router_autocfg; + + uint8_t vlan_state; + uint8_t vlan_priority; + uint16_t vlan_id; + +#define MIN_MTU_SUPPORT 46 +#define MAX_MTU_SUPPORT 9000 + uint16_t mtu; +}; + +#define PEERUSER_MAX 64 + +/****************************************************************************** + * Globals + *****************************************************************************/ +static struct iscsid_options iscsid_opts = { + .fd = INVALID_FD, + .thread = INVALID_THREAD, +}; + +/****************************************************************************** + * iscsid Functions + *****************************************************************************/ + +static void *enable_nic_thread(void *data) +{ + nic_t *nic = (nic_t *) data; + + prepare_nic_thread(nic); + LOG_INFO(PFX "%s: started NIC enable thread state: 0x%x", + nic->log_name, nic->state) + + /* Enable the NIC */ + nic_enable(nic); + + nic->enable_thread = INVALID_THREAD; + + pthread_exit(NULL); +} + +static int decode_cidr(char *in_ipaddr_str, struct iface_rec_decode *ird) +{ + int rc = 0, i; + char *tmp, *tok; + char ipaddr_str[NI_MAXHOST]; + char str[INET6_ADDRSTRLEN]; + unsigned long keepbits = 0; + struct in_addr ia; + struct in6_addr ia6; + + strlcpy(ipaddr_str, in_ipaddr_str, NI_MAXHOST); + + /* Find the CIDR if any */ + tmp = strchr(ipaddr_str, '/'); + if (tmp) { + /* CIDR found, now decode, tmpbuf = ip, tmp = netmask */ + tmp = ipaddr_str; + tok = strsep(&tmp, "/"); + LOG_INFO(PFX "in cidr: bitmask '%s' ip '%s'", tmp, tok); + keepbits = strtoull(tmp, NULL, 10); + } + + /* Determine if the IP address passed from the iface file is + * an IPv4 or IPv6 address */ + rc = inet_pton(AF_INET, ipaddr_str, &ird->ipv6_addr); + if (rc == 0) { + /* Test to determine if the addres is an IPv6 address */ + rc = inet_pton(AF_INET6, ipaddr_str, &ird->ipv6_addr); + if (rc == 0) { + LOG_ERR(PFX "Could not parse IP address: '%s'", + ipaddr_str); + goto out; + } + ird->ip_type = AF_INET6; + if (keepbits > 128) { + LOG_ERR(PFX "CIDR netmask > 128 for IPv6: %d(%s)", + keepbits, tmp); + goto out; + } + if (!keepbits) { + /* Default prefix mask to 64 */ + memcpy(&ird->ipv6_subnet_mask.s6_addr, all_zeroes_addr6, + sizeof(struct in6_addr)); + ird->prefix_len = 64; + for (i = 0; i < 2; i++) + ird->ipv6_subnet_mask.s6_addr32[i] = 0xffffffff; + goto out; + } + ird->prefix_len = keepbits; + memcpy(&ia6.s6_addr, all_zeroes_addr6, sizeof(struct in6_addr)); + for (i = 0; i < 4; i++) { + if (keepbits < 32) { + ia6.s6_addr32[i] = keepbits > 0 ? + 0x00 - (1 << (32 - keepbits)) : 0; + ia6.s6_addr32[i] = htonl(ia6.s6_addr32[i]); + break; + } else + ia6.s6_addr32[i] = 0xFFFFFFFF; + keepbits -= 32; + } + ird->ipv6_subnet_mask = ia6; + if (inet_ntop(AF_INET6, &ia6, str, sizeof(str))) + LOG_INFO(PFX "Using netmask: %s", str); + } else { + ird->ip_type = AF_INET; + rc = inet_pton(AF_INET, ipaddr_str, &ird->ipv4_addr); + + if (keepbits > 32) { + LOG_ERR(PFX "CIDR netmask > 32 for IPv4: %d(%s)", + keepbits, tmp); + goto out; + } + ia.s_addr = keepbits > 0 ? 0x00 - (1 << (32 - keepbits)) : 0; + ird->ipv4_subnet_mask.s_addr = htonl(ia.s_addr); + LOG_INFO(PFX "Using netmask: %s", + inet_ntoa(ird->ipv4_subnet_mask)); + } +out: + return rc; +} + +static int decode_iface(struct iface_rec_decode *ird, struct iface_rec *rec) +{ + int rc = 0; + char ipaddr_str[NI_MAXHOST]; + + /* Decodes the rec contents */ + memset(ird, 0, sizeof(struct iface_rec_decode)); + + /* Detect for CIDR notation and strip off the netmask if present */ + rc = decode_cidr(rec->ipaddress, ird); + if (rc && !ird->ip_type) { + LOG_ERR(PFX "cidr decode err: rc=%d, ip_type=%d", + rc, ird->ip_type); + /* Can't decode address, just exit */ + return rc; + } + rc = 0; + ird->iface_num = rec->iface_num; + ird->vlan_id = rec->vlan_id; + if (rec->iface_num != IFACE_NUM_INVALID) { + ird->mtu = rec->mtu; + if (rec->vlan_id && strcmp(rec->vlan_state, "disable")) { + ird->vlan_state = 1; + ird->vlan_priority = rec->vlan_priority; + ird->vlan_id = rec->vlan_id; + } + if (ird->ip_type == AF_INET6) { + if (!strcmp(rec->ipv6_autocfg, "dhcpv6")) + ird->ipv6_autocfg = IPV6_AUTOCFG_DHCPV6; + else if (!strcmp(rec->ipv6_autocfg, "nd")) + ird->ipv6_autocfg = IPV6_AUTOCFG_ND; + else + ird->ipv6_autocfg = IPV6_AUTOCFG_NOTSPEC; + + if (!strcmp(rec->linklocal_autocfg, "auto")) + ird->linklocal_autocfg = IPV6_LL_AUTOCFG_ON; + else if (!strcmp(rec->linklocal_autocfg, "off")) + ird->linklocal_autocfg = IPV6_LL_AUTOCFG_OFF; + else /* default */ + ird->linklocal_autocfg = IPV6_LL_AUTOCFG_ON; + + if (!strcmp(rec->router_autocfg, "auto")) + ird->router_autocfg = IPV6_RTR_AUTOCFG_ON; + else if (!strcmp(rec->router_autocfg, "off")) + ird->router_autocfg = IPV6_RTR_AUTOCFG_OFF; + else /* default */ + ird->router_autocfg = IPV6_RTR_AUTOCFG_ON; + + /* Decode the addresses based on the control flags */ + /* For DHCP, ignore the IPv6 addr in the iface */ + if (ird->ipv6_autocfg == IPV6_AUTOCFG_DHCPV6) + memcpy(&ird->ipv6_addr, all_zeroes_addr6, + sizeof(struct in6_addr)); + /* Subnet mask priority: CIDR, then rec */ + if (!ird->ipv6_subnet_mask.s6_addr) + inet_pton(AF_INET6, rec->subnet_mask, + &ird->ipv6_subnet_mask); + + /* For LL on, ignore the IPv6 addr in the iface */ + if (ird->linklocal_autocfg == IPV6_LL_AUTOCFG_OFF) { + strlcpy(ipaddr_str, rec->ipv6_linklocal, + NI_MAXHOST); + inet_pton(AF_INET6, ipaddr_str, + &ird->ipv6_linklocal); + } + + /* For RTR on, ignore the IPv6 addr in the iface */ + if (ird->router_autocfg == IPV6_RTR_AUTOCFG_OFF) { + strlcpy(ipaddr_str, rec->ipv6_router, + NI_MAXHOST); + inet_pton(AF_INET6, ipaddr_str, + &ird->ipv6_router); + } + } else { + /* Subnet mask priority: CIDR, rec, default */ + if (!ird->ipv4_subnet_mask.s_addr) + inet_pton(AF_INET, rec->subnet_mask, + &ird->ipv4_subnet_mask); + if (!ird->ipv4_subnet_mask.s_addr) + ird->ipv4_subnet_mask.s_addr = + calculate_default_netmask( + ird->ipv4_addr.s_addr); + + strlcpy(ipaddr_str, rec->gateway, NI_MAXHOST); + inet_pton(AF_INET, ipaddr_str, &ird->ipv4_gateway); + } + } else { + ird->ipv6_autocfg = IPV6_AUTOCFG_NOTUSED; + ird->linklocal_autocfg = IPV6_LL_AUTOCFG_NOTUSED; + ird->router_autocfg = IPV6_RTR_AUTOCFG_NOTUSED; + } + return rc; +} + +static void *perform_ping(void *arg) +{ + struct ping_conf *png_c = (struct ping_conf *)arg; + nic_interface_t *nic_iface = png_c->nic_iface; + nic_t *nic = nic_iface->parent; + iscsid_uip_broadcast_t *data; + struct sockaddr_in *addr; + struct sockaddr_in6 *addr6; + uip_ip6addr_t dst_addr; + int rc = 0; + int datalen; + struct timespec ts = {.tv_sec = 5, + .tv_nsec = 0}; + + data = (iscsid_uip_broadcast_t *)png_c->data; + datalen = data->u.ping_rec.datalen; + if ((datalen > STD_MTU_SIZE) || (datalen < 0)) { + LOG_ERR(PFX "Ping datalen invalid: %d", datalen); + rc = -EINVAL; + goto ping_done; + } + + memset(dst_addr, 0, sizeof(uip_ip6addr_t)); + if (nic_iface->protocol == AF_INET) { + /* IPv4 */ + addr = (struct sockaddr_in *)&data->u.ping_rec.ipaddr; + memcpy(dst_addr, &addr->sin_addr.s_addr, sizeof(uip_ip4addr_t)); + } else { + /* IPv6 */ + addr6 = (struct sockaddr_in6 *)&data->u.ping_rec.ipaddr; + memcpy(dst_addr, &addr6->sin6_addr.s6_addr, + sizeof(uip_ip6addr_t)); + } + + /* Ensure that the NIC is RUNNING */ + if ((nic->state != NIC_RUNNING) || !(nic->flags & NIC_ENABLED)) { + pthread_mutex_lock(&nic->nic_mutex); + rc = pthread_cond_timedwait(&nic->enable_done_cond, + &nic->nic_mutex, &ts); + if ((rc == 0) && (nic->state == NIC_RUNNING)) { + LOG_DEBUG(PFX "%s: nic running", nic->log_name); + } else if (rc) { + LOG_DEBUG(PFX "%s: err %d", nic->log_name, rc); + rc = -EAGAIN; + } + pthread_mutex_unlock(&nic->nic_mutex); + } + + if (rc || nic->state != NIC_RUNNING) { + png_c->state = rc; + goto ping_done; + } + + ping_init(png_c, dst_addr, nic_iface->protocol, datalen); + + rc = do_ping_from_nic_iface(png_c); + if (png_c->state == -1) + png_c->state = rc; + +ping_done: + LOG_INFO(PFX "ping thread end"); + nic->ping_thread = INVALID_THREAD; + pthread_exit(NULL); +} + +static int parse_iface(void *arg, int do_ping) +{ + int rc, i; + nic_t *nic = NULL; + nic_interface_t *nic_iface; + char *transport_name; + size_t transport_name_size; + nic_lib_handle_t *handle; + iscsid_uip_broadcast_t *data; + char ipv6_buf_str[INET6_ADDRSTRLEN]; + int request_type = 0; + struct iface_rec *rec; + struct iface_rec_decode ird; + struct in_addr src_match, dst_match; + pthread_attr_t attr; + struct ping_conf *png_c; + + data = (iscsid_uip_broadcast_t *) arg; + if (do_ping) + rec = &data->u.ping_rec.ifrec; + else + rec = &data->u.iface_rec.rec; + + LOG_INFO(PFX "Received request for '%s' to set IP address: '%s' " + "VLAN: '%d'", + rec->netdev, + rec->ipaddress, + rec->vlan_id); + + rc = decode_iface(&ird, rec); + if (ird.vlan_id && valid_vlan(ird.vlan_id) == 0) { + LOG_ERR(PFX "Invalid VLAN tag: %d", ird.vlan_id); + rc = -EIO; + goto early_exit; + } + if (rc && !ird.ip_type) { + LOG_ERR(PFX "iface err: rc=%d, ip_type=%d", rc, ird.ip_type); + goto early_exit; + } + + for (i = 0; i < 10; i++) { + struct timespec sleep_req, sleep_rem; + + if (pthread_mutex_trylock(&nic_list_mutex) == 0) + break; + + sleep_req.tv_sec = 0; + sleep_req.tv_nsec = 100000; + nanosleep(&sleep_req, &sleep_rem); + } + + if (i >= 10) { + LOG_WARN(PFX "Could not acquire nic_list_mutex lock"); + rc = -EIO; + goto early_exit; + } + + /* nic_list_mutex locked */ + + /* Check if we can find the NIC device using the netdev + * name */ + rc = from_netdev_name_find_nic(rec->netdev, &nic); + + if (rc != 0) { + LOG_WARN(PFX "Couldn't find NIC: %s, creating an instance", + rec->netdev); + + nic = nic_init(); + if (nic == NULL) { + LOG_ERR(PFX "Couldn't allocate space for NIC %s", + rec->netdev); + + rc = -ENOMEM; + goto done; + } + + strncpy(nic->eth_device_name, + rec->netdev, + sizeof(nic->eth_device_name)); + nic->config_device_name = nic->eth_device_name; + nic->log_name = nic->eth_device_name; + + if (nic_fill_name(nic) != 0) { + free(nic); + rc = -EIO; + goto done; + } + + nic_add(nic); + } else { + LOG_INFO(PFX " %s, using existing NIC", + rec->netdev); + } + + pthread_mutex_lock(&nic->nic_mutex); + if (nic->flags & NIC_GOING_DOWN) { + pthread_mutex_unlock(&nic->nic_mutex); + rc = -EIO; + LOG_INFO(PFX "nic->flags GOING DOWN"); + goto done; + } + + /* If we retry too many times allow iscsid to timeout */ + if (nic->pending_count > 1000) { + nic->pending_count = 0; + nic->flags &= ~NIC_ENABLED_PENDING; + pthread_mutex_unlock(&nic->nic_mutex); + + LOG_WARN(PFX "%s: pending count exceeded 1000", nic->log_name); + + rc = 0; + goto done; + } + + if (nic->flags & NIC_ENABLED_PENDING) { + struct timespec sleep_req, sleep_rem; + + nic->pending_count++; + pthread_mutex_unlock(&nic->nic_mutex); + + sleep_req.tv_sec = 2; + sleep_req.tv_nsec = 0; + nanosleep(&sleep_req, &sleep_rem); + + pthread_mutex_lock(&nic->nic_mutex); + if (!(nic->flags & NIC_ENABLED) || + nic->state != NIC_RUNNING) { + pthread_mutex_unlock(&nic->nic_mutex); + LOG_INFO(PFX "%s: enabled pending", nic->log_name); + rc = -EAGAIN; + goto done; + } + } + pthread_mutex_unlock(&nic->nic_mutex); + + prepare_library(nic); + + /* Sanity Check to ensure the transport names are the same */ + handle = nic->nic_library; + if (handle != NULL) { + (*handle->ops->lib_ops.get_transport_name) (&transport_name, + &transport_name_size); + + if (strncmp(transport_name, + rec->transport_name, + transport_name_size) != 0) { + LOG_ERR(PFX "%s Transport name is not equal " + "expected: %s got: %s", + nic->log_name, + rec->transport_name, + transport_name); + } + } else { + LOG_ERR(PFX "%s Couldn't find nic library ", nic->log_name); + rc = -EIO; + goto done; + } + + LOG_INFO(PFX "%s library set using transport_name %s", + nic->log_name, transport_name); + + /* Determine how to configure the IP address */ + if (ird.ip_type == AF_INET) { + if (memcmp(&ird.ipv4_addr, + all_zeroes_addr4, sizeof(uip_ip4addr_t)) == 0) { + LOG_INFO(PFX "%s: requesting configuration using DHCP", + nic->log_name); + request_type = IPV4_CONFIG_DHCP; + } else { + LOG_INFO(PFX "%s: requesting configuration using " + "static IP address", nic->log_name); + request_type = IPV4_CONFIG_STATIC; + } + } else if (ird.ip_type == AF_INET6) { + /* For the new 872_22, check ipv6_autocfg for DHCPv6 instead */ + switch (ird.ipv6_autocfg) { + case IPV6_AUTOCFG_DHCPV6: + request_type = IPV6_CONFIG_DHCP; + break; + case IPV6_AUTOCFG_ND: + request_type = IPV6_CONFIG_STATIC; + break; + case IPV6_AUTOCFG_NOTSPEC: + /* Treat NOTSPEC the same as NOTUSED for now */ + case IPV6_AUTOCFG_NOTUSED: + /* For 871 */ + default: + /* Just the IP address to determine */ + if (memcmp(&ird.ipv6_addr, + all_zeroes_addr6, + sizeof(struct in6_addr)) == 0) + request_type = IPV6_CONFIG_DHCP; + else + request_type = IPV6_CONFIG_STATIC; + } + } else { + LOG_ERR(PFX "%s: unknown ip_type to configure: 0x%x", + nic->log_name, ird.ip_type); + + rc = -EIO; + goto done; + } + + pthread_mutex_lock(&nic->nic_mutex); + + nic_iface = nic_find_nic_iface(nic, ird.ip_type, ird.vlan_id, + ird.iface_num, request_type); + + if (nic->flags & NIC_PATHREQ_WAIT) { + if (!nic_iface || + !(nic_iface->flags & NIC_IFACE_PATHREQ_WAIT)) { + int pathreq_wait; + + if (nic_iface && + (nic_iface->flags & NIC_IFACE_PATHREQ_WAIT2)) + pathreq_wait = 12; + else + pathreq_wait = 10; + + if (nic->pathreq_pending_count < pathreq_wait) { + struct timespec sleep_req, sleep_rem; + + pthread_mutex_unlock(&nic->nic_mutex); + + nic->pathreq_pending_count++; + sleep_req.tv_sec = 0; + sleep_req.tv_nsec = 100000; + nanosleep(&sleep_req, &sleep_rem); + /* Somebody else is waiting for PATH_REQ */ + LOG_INFO(PFX "%s: path req pending cnt=%d", + nic->log_name, + nic->pathreq_pending_count); + rc = -EAGAIN; + goto done; + } else { + nic->pathreq_pending_count = 0; + LOG_DEBUG(PFX "%s: path req pending cnt " + "exceeded!", nic->log_name); + /* Allow to fall thru */ + } + } + } + + nic->flags |= NIC_PATHREQ_WAIT; + + /* Create the network interface if it doesn't exist */ + if (nic_iface == NULL) { + LOG_DEBUG(PFX "%s couldn't find interface with " + "ip_type: 0x%x creating it", + nic->log_name, ird.ip_type); + nic_iface = nic_iface_init(); + + if (nic_iface == NULL) { + pthread_mutex_unlock(&nic->nic_mutex); + LOG_ERR(PFX "%s Couldn't allocate " + "interface with ip_type: 0x%x", + nic->log_name, ird.ip_type); + goto done; + } + nic_iface->protocol = ird.ip_type; + nic_iface->vlan_id = ird.vlan_id; + nic_iface->vlan_priority = ird.vlan_priority; + if (ird.mtu >= MIN_MTU_SUPPORT && ird.mtu <= MAX_MTU_SUPPORT) + nic_iface->mtu = ird.mtu; + nic_iface->iface_num = ird.iface_num; + nic_iface->request_type = request_type; + nic_add_nic_iface(nic, nic_iface); + + persist_all_nic_iface(nic); + + LOG_INFO(PFX "%s: created network interface", + nic->log_name); + } else { + /* Move the nic_iface to the front */ + set_nic_iface(nic, nic_iface); + LOG_INFO(PFX "%s: using existing network interface", + nic->log_name); + } + + nic_iface->flags |= NIC_IFACE_PATHREQ_WAIT1; + if (nic->nl_process_thread == INVALID_THREAD) { + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + rc = pthread_create(&nic->nl_process_thread, &attr, + nl_process_handle_thread, nic); + if (rc != 0) { + LOG_ERR(PFX "%s: Could not create NIC NL " + "processing thread [%s]", nic->log_name, + strerror(rc)); + nic->nl_process_thread = INVALID_THREAD; + /* Reset both WAIT flags */ + nic_iface->flags &= ~NIC_IFACE_PATHREQ_WAIT; + nic->flags &= ~NIC_PATHREQ_WAIT; + } + } + + pthread_mutex_unlock(&nic->nic_mutex); + + if (nic_iface->ustack.ip_config == request_type) { + /* Same request_type, check for STATIC address change */ + if (request_type == IPV4_CONFIG_STATIC) { + if (memcmp(nic_iface->ustack.hostaddr, &ird.ipv4_addr, + sizeof(struct in_addr))) + goto reacquire; + } else if (request_type == IPV6_CONFIG_STATIC) { + if (memcmp(nic_iface->ustack.hostaddr6, &ird.ipv6_addr, + sizeof(struct in6_addr))) + goto reacquire; + else + inet_ntop(AF_INET6, &ird.ipv6_addr, + ipv6_buf_str, + sizeof(ipv6_buf_str)); + } + LOG_INFO(PFX "%s: IP configuration didn't change using 0x%x", + nic->log_name, nic_iface->ustack.ip_config); + /* No need to acquire the IP address */ + inet_ntop(AF_INET6, &ird.ipv6_addr, ipv6_buf_str, + sizeof(ipv6_buf_str)); + + goto enable_nic; + } +reacquire: + /* Config needs to re-acquire for this nic_iface */ + pthread_mutex_lock(&nic->nic_mutex); + nic_iface->flags |= NIC_IFACE_ACQUIRE; + pthread_mutex_unlock(&nic->nic_mutex); + + /* Disable the nic loop from further processing, upon returned, + the nic_iface should be cleared */ + nic_disable(nic, 0); + + /* Check to see if this is using DHCP or if this is + * a static IPv4 address. This is done by checking + * if the IP address is equal to 0.0.0.0. If it is + * then the user has specified to use DHCP. If not + * then the user has spcicied to use a static IP address + * an the default netmask will be used */ + switch (request_type) { + case IPV4_CONFIG_DHCP: + memset(nic_iface->ustack.hostaddr, 0, sizeof(struct in_addr)); + LOG_INFO(PFX "%s: configuring using DHCP", nic->log_name); + nic_iface->ustack.ip_config = IPV4_CONFIG_DHCP; + break; + + case IPV4_CONFIG_STATIC: + memcpy(nic_iface->ustack.hostaddr, &ird.ipv4_addr, + sizeof(struct in_addr)); + LOG_INFO(PFX "%s: configuring using static IP " + "IPv4 address :%s ", + nic->log_name, inet_ntoa(ird.ipv4_addr)); + + if (ird.ipv4_subnet_mask.s_addr) + memcpy(nic_iface->ustack.netmask, + &ird.ipv4_subnet_mask, sizeof(struct in_addr)); + LOG_INFO(PFX " netmask: %s", inet_ntoa(ird.ipv4_subnet_mask)); + + /* Default route */ + if (ird.ipv4_gateway.s_addr) { + /* Check for validity */ + src_match.s_addr = ird.ipv4_addr.s_addr & + ird.ipv4_subnet_mask.s_addr; + dst_match.s_addr = ird.ipv4_gateway.s_addr & + ird.ipv4_subnet_mask.s_addr; + if (src_match.s_addr == dst_match.s_addr) + memcpy(nic_iface->ustack.default_route_addr, + &ird.ipv4_gateway, + sizeof(struct in_addr)); + } + nic_iface->ustack.ip_config = IPV4_CONFIG_STATIC; + break; + + case IPV6_CONFIG_DHCP: + memset(nic_iface->ustack.hostaddr6, 0, + sizeof(struct in6_addr)); + nic_iface->ustack.prefix_len = ird.prefix_len; + nic_iface->ustack.ipv6_autocfg = ird.ipv6_autocfg; + nic_iface->ustack.linklocal_autocfg = ird.linklocal_autocfg; + nic_iface->ustack.router_autocfg = ird.router_autocfg; + + if (memcmp(&ird.ipv6_subnet_mask, all_zeroes_addr6, + sizeof(struct in6_addr))) + memcpy(nic_iface->ustack.netmask6, + &ird.ipv6_subnet_mask, sizeof(struct in6_addr)); + if (ird.linklocal_autocfg == IPV6_LL_AUTOCFG_OFF) + memcpy(nic_iface->ustack.linklocal6, + &ird.ipv6_linklocal, sizeof(struct in6_addr)); + if (ird.router_autocfg == IPV6_RTR_AUTOCFG_OFF) + memcpy(nic_iface->ustack.default_route_addr6, + &ird.ipv6_router, sizeof(struct in6_addr)); + inet_ntop(AF_INET6, &ird.ipv6_addr, ipv6_buf_str, + sizeof(ipv6_buf_str)); + LOG_INFO(PFX "%s: configuring using DHCPv6", + nic->log_name); + nic_iface->ustack.ip_config = IPV6_CONFIG_DHCP; + break; + + case IPV6_CONFIG_STATIC: + memcpy(nic_iface->ustack.hostaddr6, &ird.ipv6_addr, + sizeof(struct in6_addr)); + nic_iface->ustack.prefix_len = ird.prefix_len; + nic_iface->ustack.ipv6_autocfg = ird.ipv6_autocfg; + nic_iface->ustack.linklocal_autocfg = ird.linklocal_autocfg; + nic_iface->ustack.router_autocfg = ird.router_autocfg; + + if (memcmp(&ird.ipv6_subnet_mask, all_zeroes_addr6, + sizeof(struct in6_addr))) + memcpy(nic_iface->ustack.netmask6, + &ird.ipv6_subnet_mask, sizeof(struct in6_addr)); + if (ird.linklocal_autocfg == IPV6_LL_AUTOCFG_OFF) + memcpy(nic_iface->ustack.linklocal6, + &ird.ipv6_linklocal, sizeof(struct in6_addr)); + if (ird.router_autocfg == IPV6_RTR_AUTOCFG_OFF) + memcpy(nic_iface->ustack.default_route_addr6, + &ird.ipv6_router, sizeof(struct in6_addr)); + + inet_ntop(AF_INET6, &ird.ipv6_addr, ipv6_buf_str, + sizeof(ipv6_buf_str)); + LOG_INFO(PFX "%s: configuring using static IP " + "IPv6 address: '%s'", nic->log_name, ipv6_buf_str); + + nic_iface->ustack.ip_config = IPV6_CONFIG_STATIC; + break; + + default: + LOG_INFO(PFX "%s: Unknown request type: 0x%x", + nic->log_name, request_type); + + } + +enable_nic: + switch (nic->state) { + case NIC_STOPPED: + /* This thread will be thrown away when completed */ + if (nic->enable_thread != INVALID_THREAD) { + rc = pthread_cancel(nic->enable_thread); + if (rc != 0) { + LOG_INFO(PFX "%s: failed to cancel enable NIC " + "thread\n", nic->log_name); + goto eagain; + } + } + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + rc = pthread_create(&nic->enable_thread, &attr, + enable_nic_thread, (void *)nic); + if (rc != 0) + LOG_WARN(PFX "%s: failed starting enable NIC thread\n", + nic->log_name); +eagain: + rc = -EAGAIN; + break; + + case NIC_RUNNING: + LOG_INFO(PFX "%s: NIC already enabled " + "flags: 0x%x state: 0x%x\n", + nic->log_name, nic->flags, nic->state); + rc = 0; + break; + default: + LOG_INFO(PFX "%s: NIC enable still in progress " + "flags: 0x%x state: 0x%x\n", + nic->log_name, nic->flags, nic->state); + rc = -EAGAIN; + } + + LOG_INFO(PFX "ISCSID_UIP_IPC_GET_IFACE: command: %x " + "name: %s, netdev: %s ipaddr: %s vlan: %d transport_name:%s", + data->header.command, rec->name, rec->netdev, + (ird.ip_type == AF_INET) ? inet_ntoa(ird.ipv4_addr) : + ipv6_buf_str, + ird.vlan_id, rec->transport_name); + + if (do_ping) { + if (nic->ping_thread != INVALID_THREAD) { + rc = pthread_cancel(nic->ping_thread); + if (rc != 0) { + LOG_INFO(PFX "%s: failed to cancel ping thread", + nic->log_name); + rc = -EAGAIN; + goto done; + } + } + + png_c = malloc(sizeof(struct ping_conf)); + if (!png_c) { + LOG_ERR(PFX "Memory alloc failed for ping conf"); + rc = -ENOMEM; + goto done; + } + + memset(png_c, 0, sizeof(struct ping_conf)); + png_c->nic_iface = nic_iface; + png_c->data = arg; + nic_iface->ustack.ping_conf = png_c; + + /* Spawn a thread to perform ping operation. + * This thread will exit when done. + */ + rc = pthread_create(&nic->ping_thread, NULL, + perform_ping, (void *)png_c); + if (rc != 0) { + LOG_WARN(PFX "%s: failed starting ping thread\n", + nic->log_name); + } else { + pthread_join(nic->ping_thread, NULL); + rc = png_c->state; + if (rc == -EAGAIN) + png_c->state = 0; + } + free(png_c); + nic_iface->ustack.ping_conf = NULL; + } + +done: + pthread_mutex_unlock(&nic_list_mutex); + +early_exit: + return rc; +} + +/** + * process_iscsid_broadcast() - This function is used to process the + * broadcast messages from iscsid + * + * s2 is an open file descriptor, which + * must not be left open upon return + */ +int process_iscsid_broadcast(int s2) +{ + int rc = 0; + iscsid_uip_broadcast_t *data; + iscsid_uip_rsp_t rsp; + FILE *fd; + size_t size; + iscsid_uip_cmd_e cmd; + uint32_t payload_len; + + fd = fdopen(s2, "r+"); + if (fd == NULL) { + LOG_ERR(PFX "Couldn't open file descriptor: %d(%s)", + errno, strerror(errno)); + close(s2); + return -EIO; + } + + /* This will be freed by parse_iface_thread() */ + data = (iscsid_uip_broadcast_t *) calloc(1, sizeof(*data)); + if (data == NULL) { + LOG_ERR(PFX "Couldn't allocate memory for iface data"); + rc = -ENOMEM; + goto error; + } + memset(data, 0, sizeof(*data)); + + size = fread(data, sizeof(iscsid_uip_broadcast_header_t), 1, fd); + if (!size) { + LOG_ERR(PFX "Could not read request: %d(%s)", + errno, strerror(errno)); + rc = ferror(fd); + goto error; + } + + cmd = data->header.command; + payload_len = data->header.payload_len; + if (payload_len > sizeof(data->u)) { + LOG_ERR(PFX "Data payload length too large (%d). Corrupt payload?", + payload_len); + rc = -EINVAL; + goto error; + } + + LOG_DEBUG(PFX "recv iscsid request: cmd: %d, payload_len: %d", + cmd, payload_len); + + memset(&rsp, 0, sizeof(rsp)); + + switch (cmd) { + case ISCSID_UIP_IPC_GET_IFACE: + size = fread(&data->u.iface_rec, payload_len, 1, fd); + if (!size) { + LOG_ERR(PFX "Could not read data: %d(%s)", + errno, strerror(errno)); + goto error; + } + + rc = parse_iface(data, 0); + switch (rc) { + case 0: + rsp.command = cmd; + rsp.err = ISCSID_UIP_MGMT_IPC_DEVICE_UP; + break; + case -EAGAIN: + rsp.command = cmd; + rsp.err = ISCSID_UIP_MGMT_IPC_DEVICE_INITIALIZING; + break; + default: + rsp.command = cmd; + rsp.err = ISCSID_UIP_MGMT_IPC_ERR; + } + + break; + case ISCSID_UIP_IPC_PING: + size = fread(&data->u.ping_rec, payload_len, 1, fd); + if (!size) { + LOG_ERR(PFX "Could not read data: %d(%s)", + errno, strerror(errno)); + goto error; + } + + rc = parse_iface(data, 1); + rsp.command = cmd; + rsp.ping_sc = rc; + + switch (rc) { + case 0: + rsp.err = ISCSID_UIP_MGMT_IPC_DEVICE_UP; + break; + case -EAGAIN: + rsp.err = ISCSID_UIP_MGMT_IPC_DEVICE_INITIALIZING; + break; + default: + rsp.err = ISCSID_UIP_MGMT_IPC_ERR; + } + + break; + default: + LOG_WARN(PFX "Unknown iscsid broadcast command: %x", + data->header.command); + + /* Send a response back to iscsid to tell it the + operation succeeded */ + rsp.command = cmd; + rsp.err = ISCSID_UIP_MGMT_IPC_OK; + break; + } + + size = fwrite(&rsp, sizeof(rsp), 1, fd); + if (size == -1) { + LOG_ERR(PFX "Could not send response: %d(%s)", + errno, strerror(errno)); + rc = ferror(fd); + } + +error: + if (data) + free(data); + fclose(fd); + + return rc; +} + +static void iscsid_loop_close(void *arg) +{ + close(iscsid_opts.fd); + + LOG_INFO(PFX "iSCSI daemon socket closed"); +} + +/* + * check that the peer user is privilidged + * + * return 1 if peer is ok else 0 + * + * XXX: this function is copied from iscsid_ipc.c and should be + * moved into a common library + */ +static int +mgmt_peeruser(int sock, char *user) +{ + struct ucred peercred; + socklen_t so_len = sizeof(peercred); + struct passwd *pass; + + errno = 0; + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &peercred, + &so_len) != 0 || so_len != sizeof(peercred)) { + /* We didn't get a valid credentials struct. */ + LOG_ERR(PFX "peeruser_unux: error receiving credentials: %m"); + return 0; + } + + pass = getpwuid(peercred.uid); + if (pass == NULL) { + LOG_ERR(PFX "peeruser_unix: unknown local user with uid %d", + (int) peercred.uid); + return 0; + } + + strlcpy(user, pass->pw_name, PEERUSER_MAX); + return 1; +} + +/** + * iscsid_loop() - This is the function which will process the broadcast + * messages from iscsid + * + */ +static void *iscsid_loop(void *arg) +{ + int rc; + sigset_t set; + char user[PEERUSER_MAX]; + + pthread_cleanup_push(iscsid_loop_close, arg); + + sigfillset(&set); + rc = pthread_sigmask(SIG_BLOCK, &set, NULL); + if (rc != 0) { + LOG_ERR(PFX + "Couldn't set signal mask for the iscisd listening " + "thread"); + } + + LOG_DEBUG(PFX "Started iscsid listening thread"); + + while (1) { + struct sockaddr_un remote; + socklen_t sock_len; + int s2; + + LOG_DEBUG(PFX "Waiting for iscsid command"); + + sock_len = sizeof(remote); + s2 = accept(iscsid_opts.fd, + (struct sockaddr *)&remote, &sock_len); + if (s2 == -1) { + if (errno == EAGAIN) { + LOG_DEBUG("Got EAGAIN from accept"); + sleep(1); + continue; + } else if (errno == EINTR) { + LOG_DEBUG("Got EINTR from accept"); + /* The program is terminating, time to exit */ + break; + } + + LOG_ERR(PFX "Could not accept: %d(%s)", + s2, strerror(errno)); + continue; + } + + if (!mgmt_peeruser(iscsid_opts.fd, user) || strncmp(user, "root", PEERUSER_MAX)) { + close(s2); + LOG_ERR(PFX "Access error: non-administrative connection rejected"); + break; + } + + /* this closes the file descriptor s2 */ + process_iscsid_broadcast(s2); + } + + pthread_cleanup_pop(0); + + LOG_ERR(PFX "exit iscsid listening thread"); + + pthread_exit(NULL); +} + +#define SD_SOCKET_FDS_START 3 + +static int ipc_systemd(void) +{ + char *env; + + env = getenv("LISTEN_PID"); + + if (!env || (strtoul(env, NULL, 10) != getpid())) + return -EINVAL; + + env = getenv("LISTEN_FDS"); + + if (!env) + return -EINVAL; + + if (strtoul(env, NULL, 10) != 1) { + LOG_ERR("Did not receive exactly one IPC socket from systemd"); + return -EINVAL; + } + + return SD_SOCKET_FDS_START; +} + +/****************************************************************************** + * Initialize/Cleanup routines + ******************************************************************************/ +/** + * iscsid_init() - This function will setup the thread used to listen for + * the iscsid broadcast messages + * @return 0 on success, <0 on failure + */ +int iscsid_init() +{ + int rc, addr_len; + struct sockaddr_un addr; + + iscsid_opts.fd = ipc_systemd(); + if (iscsid_opts.fd >= 0) + return 0; + + iscsid_opts.fd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (iscsid_opts.fd < 0) { + LOG_ERR(PFX "Can not create IPC socket"); + return iscsid_opts.fd; + } + + addr_len = offsetof(struct sockaddr_un, sun_path) + strlen(ISCSID_UIP_NAMESPACE) + 1; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_LOCAL; + memcpy((char *)&addr.sun_path + 1, ISCSID_UIP_NAMESPACE, + strlen(ISCSID_UIP_NAMESPACE)); + + rc = bind(iscsid_opts.fd, (struct sockaddr *)&addr, addr_len); + if (rc < 0) { + LOG_ERR(PFX "Can not bind IPC socket: %s", strerror(errno)); + goto error; + } + + rc = listen(iscsid_opts.fd, 32); + if (rc < 0) { + LOG_ERR(PFX "Can not listen IPC socket: %s", strerror(errno)); + goto error; + } + + return 0; +error: + close(iscsid_opts.fd); + iscsid_opts.fd = INVALID_FD; + + return rc; +} + +/** + * iscsid_start() - This function will start the thread used to listen for + * the iscsid broadcast messages + * @return 0 on success, <0 on failure + */ +int iscsid_start() +{ + pthread_attr_t attr; + int rc; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + rc = pthread_create(&iscsid_opts.thread, &attr, iscsid_loop, NULL); + if (rc != 0) { + LOG_ERR(PFX "Could not start iscsid listening thread rc=%d", + rc); + goto error; + } + + return 0; + +error: + close(iscsid_opts.fd); + iscsid_opts.fd = INVALID_FD; + + return rc; +} + +/** + * iscsid_cleanup() - This is called when stoping the thread listening + * for the iscsid broadcast messages + */ +void iscsid_cleanup() +{ + int rc; + + if (iscsid_opts.fd != INVALID_FD && + iscsid_opts.thread != INVALID_THREAD) { + rc = pthread_cancel(iscsid_opts.thread); + if (rc != 0) { + LOG_ERR("Could not cancel iscsid listening thread: %s", + strerror(rc)); + } + } + + LOG_INFO(PFX "iscsid listening thread has shutdown"); +} diff --git a/iscsiuio/src/unix/iscsid_ipc.h b/iscsiuio/src/unix/iscsid_ipc.h new file mode 100644 index 0000000..60bcd71 --- /dev/null +++ b/iscsiuio/src/unix/iscsid_ipc.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * iscsid_ipc.h: Generic NIC management/utility functions + * + */ +#ifndef __ISCSID_IPC_H__ +#define __ISCSID_IPC_H__ + +#include "uip.h" +#include "mgmt_ipc.h" + +enum mgmt_ipc_err iscsid_connect(int *fd); +int iscsid_get_ipaddr(int fd, uip_ip4addr_t *ipaddr); + +int iscsid_init(); +int iscsid_start(); +void iscsid_cleanup(); + +#endif /* __ISCSID_IPC_H__ */ diff --git a/iscsiuio/src/unix/libs/Makefile.am b/iscsiuio/src/unix/libs/Makefile.am new file mode 100644 index 0000000..737546b --- /dev/null +++ b/iscsiuio/src/unix/libs/Makefile.am @@ -0,0 +1,14 @@ +AM_CFLAGS = -I${top_srcdir}/src/uip \ + -I${top_srcdir}/src/unix \ + -I${top_srcdir}/src/unix/libs \ + -I${top_srcdir}/src/apps/dhcpc \ + -I${top_srcdir}/../include \ + -I${top_srcdir}/../usr + +noinst_LIBRARIES = lib_iscsiuio_hw_cnic.a + +lib_iscsiuio_hw_cnic_a_SOURCES = ../build_date.c \ + cnic.c \ + bnx2.c \ + bnx2x.c \ + qedi.c diff --git a/iscsiuio/src/unix/libs/bnx2.c b/iscsiuio/src/unix/libs/bnx2.c new file mode 100644 index 0000000..1181cf4 --- /dev/null +++ b/iscsiuio/src/unix/libs/bnx2.c @@ -0,0 +1,1165 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * bnx2.c - bnx2 user space driver + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include "build_date.h" +#include "bnx2.h" +#include "cnic.h" +#include "logger.h" +#include "nic.h" +#include "nic_utils.h" +#include "options.h" + +#define PFX "bnx2 " + +/* Foward struct declarations */ +struct nic_ops bnx2_op; + +/******************************************************************************* + * NIC Library Strings + ******************************************************************************/ +static const char library_name[] = "bnx2"; +static const char library_version[] = PACKAGE_VERSION; +static const char library_uio_name[] = "bnx2_cnic"; + +/* The name that should be returned from /sys/class/uio/uio0/name */ +static const char cnic_uio_sysfs_name_tempate[] = "/sys/class/uio/uio%i/name"; +static const char cnic_uio_sysfs_name[] = "bnx2_cnic"; + +/******************************************************************************* + * String constants used to display human readable adapter name + ******************************************************************************/ +static const char hp_NC370T[] = + "HP NC370T Multifunction Gigabit Server Adapter"; +static const char hp_NC370I[] = + "HP NC370i Multifunction Gigabit Server Adapter"; +static const char brcm_5706S[] = "QLogic NetXtreme II BCM5706 1000Base-SX"; +static const char hp_NC370F[] = + "HP NC370F Multifunction Gigabit Server Adapter"; +static const char brcm_5708C[] = "QLogic NetXtreme II BCM5708 1000Base-T"; +static const char brcm_5708S[] = "QLogic NetXtreme II BCM5708 1000Base-SX"; +static const char brcm_5709C[] = "QLogic NetXtreme II BCM5709 1000Base-T"; +static const char brcm_5709S[] = "QLogic NetXtreme II BCM5709 1000Base-SX"; +static const char brcm_5716C[] = "QLogic NetXtreme II BCM5716 1000Base-T"; +static const char brcm_5716S[] = "QLogic NetXtreme II BCM5716 1000Base-SX"; + +/******************************************************************************* + * PCI ID constants + ******************************************************************************/ +#define PCI_VENDOR_ID_BROADCOM 0x14e4 +#define PCI_DEVICE_ID_NX2_5709 0x1639 +#define PCI_DEVICE_ID_NX2_5709S 0x163a +#define PCI_DEVICE_ID_NX2_5706 0x164a +#define PCI_DEVICE_ID_NX2_5708 0x164c +#define PCI_DEVICE_ID_NX2_5706S 0x16aa +#define PCI_DEVICE_ID_NX2_5708S 0x16ac + +#define PCI_VENDOR_ID_HP 0x103c + +#define PCI_ANY_ID (~0) + +/* This is the table used to match PCI vendor and device ID's to the + * human readable string names of the devices */ +static const struct pci_device_id bnx2_pci_tbl[] = { + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5706, + PCI_VENDOR_ID_HP, 0x3101, hp_NC370T}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5706, + PCI_VENDOR_ID_HP, 0x3106, hp_NC370I}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5706, + PCI_ANY_ID, PCI_ANY_ID, brcm_5706S}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5708, + PCI_ANY_ID, PCI_ANY_ID, brcm_5708C}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5706S, + PCI_VENDOR_ID_HP, 0x3102, hp_NC370F}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5706S, + PCI_ANY_ID, PCI_ANY_ID, brcm_5706S}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5708S, + PCI_ANY_ID, PCI_ANY_ID, brcm_5708S}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5709, + PCI_ANY_ID, PCI_ANY_ID, brcm_5709C}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5709S, + PCI_ANY_ID, PCI_ANY_ID, brcm_5709S}, + {PCI_VENDOR_ID_BROADCOM, 0x163b, + PCI_ANY_ID, PCI_ANY_ID, brcm_5716C}, + {PCI_VENDOR_ID_BROADCOM, 0x163c, + PCI_ANY_ID, PCI_ANY_ID, brcm_5716S}, +}; + +/******************************************************************************* + * bnx2 Library Functions + ******************************************************************************/ +/** + * bnx2_get_library_name() - Used to get the name of this NIC libary + * @param name - This function will return the pointer to this NIC + * library name + * @param name_size + */ +static void bnx2_get_library_name(char **name, size_t *name_size) +{ + *name = (char *)library_name; + *name_size = sizeof(library_name); +} + +/** + * bnx2_get_library_version() - Used to get the version string of this + * NIC libary + * @param version - This function will return the pointer to this NIC + * library version string + * @param version_size - This will be set with the version size + */ +static void bnx2_get_library_version(char **version, size_t *version_size) +{ + *version = (char *)library_version; + *version_size = sizeof(library_version); +} + +/** + * bnx2_get_build_date() - Used to get the build date string of this library + * @param version - This function will return the pointer to this NIC + * library build date string + * @param version_size - This will be set with the build date string size + */ +static void bnx2_get_build_date(char **build, size_t *build_size) +{ + *build = (char *)build_date; + *build_size = sizeof(build_date); +} + +/** + * bnx2_get_transport_name() - Used to get the transport name associated + * with this this NIC libary + * @param transport_name - This function will return the pointer to this NIC + * library's associated transport string + * @param transport_name_size - This will be set with the transport name size + */ +static void bnx2_get_transport_name(char **transport_name, + size_t *transport_name_size) +{ + *transport_name = (char *)bnx2i_library_transport_name; + *transport_name_size = bnx2i_library_transport_name_size; +} + +/** + * bnx2_get_uio_name() - Used to get the uio name associated with this this + * NIC libary + * @param uio_name - This function will return the pointer to this NIC + * library's associated uio string + * @param transport_name_size - This will be set with the uio name size + */ +static void bnx2_get_uio_name(char **uio_name, size_t *uio_name_size) +{ + *uio_name = (char *)library_uio_name; + *uio_name_size = sizeof(library_uio_name); +} + +/** + * bnx2_get_pci_table() - Used to get the PCI table for this NIC libary + * to determine which NIC's based off of PCI ID's + * are supported + * @param table - This function will return the pointer to the PCI table + * @param entries - This function will return the number of entries in the NIC + * library's PCI table + */ +static void bnx2_get_pci_table(struct pci_device_id **table, uint32_t *entries) +{ + *table = (struct pci_device_id *)bnx2_pci_tbl; + *entries = (uint32_t) (sizeof(bnx2_pci_tbl) / sizeof(bnx2_pci_tbl[0])); +} + +/** + * bnx2_get_ops() - Used to get the NIC library op table + * @param op - The op table of this NIC library + */ +struct nic_ops *bnx2_get_ops() +{ + return &bnx2_op; +} + +/******************************************************************************* + * bnx2 Utility Functions + ******************************************************************************/ +/******************************************************************************* + * Utility Functions Used to read register from the bnx2 device + ******************************************************************************/ +static void bnx2_wr32(bnx2_t *bp, __u32 off, __u32 val) +{ + *((volatile __u32 *)(bp->reg + off)) = val; +} + +static void bnx2_wr16(bnx2_t *bp, __u32 off, __u16 val) +{ + *((volatile __u16 *)(bp->reg + off)) = val; +} + +static __u32 bnx2_rd32(bnx2_t *bp, __u32 off) +{ + return *((volatile __u32 *)(bp->reg + off)); +} + +static int bnx2_reg_sync(bnx2_t *bp, __u32 off, __u16 length) +{ + return msync(bp->reg + off, length, MS_SYNC); +} + +/** + * bnx2_get_chip_id() - Used to retrive the chip ID from the nic + * @param dev - Device used to determin NIC type + * @return Chip ID read from the MISC ID register + */ +static int bnx2_get_chip_id(bnx2_t *bp) +{ + return bnx2_rd32(bp, BNX2_MISC_ID); +} + +/** + * bnx2_uio_verify() + * + */ +static int bnx2_uio_verify(nic_t *nic) +{ + char *raw = NULL, *raw_tmp; + uint32_t raw_size = 0; + char temp_path[sizeof(cnic_uio_sysfs_name_tempate) + 8]; + int rc = 0; + + /* Build the path to determine uio name */ + snprintf(temp_path, sizeof(temp_path), + cnic_uio_sysfs_name_tempate, nic->uio_minor); + + rc = capture_file(&raw, &raw_size, temp_path); + if (rc != 0) + goto error; + + /* sanitize name string by replacing newline with null termination */ + raw_tmp = raw; + while (*raw_tmp != '\n') + raw_tmp++; + *raw_tmp = '\0'; + + if (strncmp(raw, cnic_uio_sysfs_name, sizeof(cnic_uio_sysfs_name)) != + 0) { + LOG_ERR(PFX "%s: uio names not equal: " + "expecting %s got %s from %s", + nic->log_name, cnic_uio_sysfs_name, raw, temp_path); + rc = -EIO; + } + + free(raw); + + LOG_INFO(PFX "%s: Verified is a cnic_uio device", nic->log_name); + +error: + return rc; +} + +/******************************************************************************* + * bnx2 Utility Functions to get to the hardware consumer indexes + ******************************************************************************/ +static __u16 bnx2_get_rx_msix(bnx2_t *bp) +{ + struct status_block_msix *sblk = bp->status_blk.msix; + __u16 rx_cons; + + msync(sblk, sizeof(*sblk), MS_SYNC); + rx_cons = sblk->status_rx_quick_consumer_index; + barrier(); + if ((rx_cons & (MAX_RX_DESC_CNT)) == (MAX_RX_DESC_CNT)) + rx_cons++; + + return rx_cons; +} + +static __u16 bnx2_get_rx_msi(bnx2_t *bp) +{ + struct status_block *sblk = bp->status_blk.msi; + __u16 rx_cons; + + msync(sblk, sizeof(*sblk), MS_SYNC); + rx_cons = BNX2_SBLK_EVEN_IDX(sblk->rx2); + barrier(); + if ((rx_cons & (MAX_RX_DESC_CNT)) == (MAX_RX_DESC_CNT)) + rx_cons++; + + return rx_cons; +} + +static __u16 bnx2_get_tx_msix(bnx2_t *bp) +{ + struct status_block_msix *sblk = bp->status_blk.msix; + __u16 tx_cons; + + msync(sblk, sizeof(*sblk), MS_SYNC); + tx_cons = sblk->status_tx_quick_consumer_index; + barrier(); + if ((tx_cons & (MAX_TX_DESC_CNT)) == (MAX_TX_DESC_CNT)) + tx_cons++; + + return tx_cons; +} + +static __u16 bnx2_get_tx_msi(bnx2_t *bp) +{ + struct status_block *sblk = bp->status_blk.msi; + __u16 tx_cons; + + msync(sblk, sizeof(*sblk), MS_SYNC); + tx_cons = BNX2_SBLK_EVEN_IDX(sblk->tx2); + barrier(); + if ((tx_cons & (MAX_TX_DESC_CNT)) == (MAX_TX_DESC_CNT)) + tx_cons++; + + return tx_cons; +} + +typedef enum { + CNIC_VLAN_STRIPPING_ENABLED = 1, + CNIC_VLAN_STRIPPING_DISABLED = 2, +} CNIC_VLAN_STRIPPING_MODE; + +/** + * bnx2_strip_vlan_enabled() - This will query the device to determine whether + * VLAN tag stripping is enabled or not + * @param dev - device to check stripping or not + * @ return CNIC_VLAN_STRIPPING_ENABLED stripping is enabled + * CNIC_VLAN_STRIPPING_DISABLED stripping is not enabled + */ +static CNIC_VLAN_STRIPPING_MODE bnx2_strip_vlan_enabled(bnx2_t *bp) +{ + uint32_t val; + + val = bnx2_rd32(bp, BNX2_EMAC_RX_MODE); + + if (val & BNX2_EMAC_RX_MODE_KEEP_VLAN_TAG) + return CNIC_VLAN_STRIPPING_DISABLED; + else + return CNIC_VLAN_STRIPPING_ENABLED; +} + +/** + * bnx2_free() - Used to free a bnx2 structure + */ +static void bnx2_free(nic_t *nic) +{ + if (nic->priv) + free(nic->priv); + nic->priv = NULL; +} + + +/** + * bnx2_alloc() - Used to allocate a bnx2 structure + */ +static bnx2_t *bnx2_alloc(nic_t *nic) +{ + bnx2_t *bp = malloc(sizeof(*bp)); + if (bp == NULL) { + LOG_ERR(PFX "%s: Could not allocate bnx2 space", nic->log_name); + return NULL; + } + + /* Clear out the bnx2 contents */ + memset(bp, 0, sizeof(*bp)); + + bp->bar0_fd = INVALID_FD; + bp->flags = BNX2_UIO_TX_HAS_SENT; + + bp->parent = nic; + nic->priv = (void *)bp; + + return bp; +} + +/** + * bnx2_open() - This will initialize all the hardware resources + * @param dev - The struct nic device to open + * @return 0 on success, on failure a errno will be returned + */ +static int bnx2_open(nic_t *nic) +{ + bnx2_t *bp; + struct stat uio_stat; + int i, rc; + __u32 val; + uint32_t tx_cid; + __u32 msix_vector = 0; + char sysfs_resc_path[80]; + + /* Sanity Check: validate the parameters */ + if (nic == NULL) { + LOG_ERR(PFX "bnx2_open(): nic == NULL"); + return -EINVAL; + } + + if ((nic->priv) != NULL && + (((bnx2_t *) (nic->priv))->flags & BNX2_OPENED)) { + return 0; + } + + bp = bnx2_alloc(nic); + if (bp == NULL) { + LOG_ERR(PFX "bnx2_open(): Couldn't allocate bp priv struct", + nic->log_name); + return -ENOMEM; + } + + while (nic->fd < 0) { + nic->fd = open(nic->uio_device_name, O_RDWR | O_NONBLOCK); + if (nic->fd != INVALID_FD) { + LOG_ERR(PFX + "%s: uio device has been brought up via pid: " + "%d on fd: %d", + nic->uio_device_name, getpid(), nic->fd); + + rc = bnx2_uio_verify(nic); + if (rc != 0) + continue; + + break; + } else { + LOG_WARN(PFX "%s: Could not open device: %s, [%s]", + nic->log_name, nic->uio_device_name, + strerror(errno)); + manually_trigger_uio_event(nic, nic->uio_minor); + + /* udev might not have created the file yet */ + pthread_mutex_unlock(&nic->nic_mutex); + sleep(1); + pthread_mutex_lock(&nic->nic_mutex); + } + } + if (fstat(nic->fd, &uio_stat) < 0) { + LOG_ERR(PFX "%s: Could not fstat device", nic->log_name); + errno = -ENODEV; + goto error_alloc_rx_ring; + } + nic->uio_minor = minor(uio_stat.st_rdev); + + cnic_get_sysfs_pci_resource_path(nic, 0, sysfs_resc_path, 80); + bp->bar0_fd = open(sysfs_resc_path, O_RDWR | O_SYNC); + if (bp->bar0_fd < 0) { + LOG_ERR(PFX "%s: Could not open %s", nic->log_name, + sysfs_resc_path); + errno = -ENODEV; + goto error_alloc_rx_ring; + } + + /* TODO: hardcoded with the cnic driver */ + bp->rx_ring_size = 3; + bp->rx_buffer_size = 0x400; + + LOG_DEBUG(PFX "%s: using rx ring size: %d, rx buffer size: %d", + nic->log_name, bp->rx_ring_size, bp->rx_buffer_size); + + /* Determine the number of UIO events that have already occured */ + rc = detemine_initial_uio_events(nic, &nic->intr_count); + if (rc != 0) { + LOG_ERR("Could not determine the number ofinitial UIO events"); + nic->intr_count = 0; + } + + /* Allocate space for rx ring pointer */ + bp->rx_ring = malloc(sizeof(struct l2_fhdr *) * bp->rx_ring_size); + if (bp->rx_ring == NULL) { + LOG_ERR(PFX "%s: Could not allocate space for rx_ring", + nic->log_name); + errno = -ENOMEM; + goto error_alloc_rx_ring; + } + mlock(bp->rx_ring, sizeof(struct l2_fhdr *) * bp->rx_ring_size); + + /* Allocate space for rx pkt ring */ + bp->rx_pkt_ring = malloc(sizeof(void *) * bp->rx_ring_size); + if (bp->rx_pkt_ring == NULL) { + LOG_ERR(PFX "%s: Could not allocate space for rx_pkt_ring", + nic->log_name); + errno = -ENOMEM; + goto error_alloc_rx_pkt_ring; + } + mlock(bp->rx_pkt_ring, sizeof(void *) * bp->rx_ring_size); + + bp->reg = mmap(NULL, 0x12800, PROT_READ | PROT_WRITE, MAP_SHARED, + bp->bar0_fd, (off_t) 0); + if (bp->reg == MAP_FAILED) { + LOG_INFO(PFX "%s: Couldn't mmap registers: %s", + nic->log_name, strerror(errno)); + bp->reg = NULL; + goto error_regs; + } + + msync(bp->reg, 0x12800, MS_SYNC); + LOG_DEBUG(PFX "Chip ID: %x", bnx2_get_chip_id(bp)); + + /* on a 5709 when using MSI-X the status block is at an offset */ + if (BNX2_CHIP_NUM(bnx2_get_chip_id(bp)) == CHIP_NUM_5709) { + /* determine if we are using MSI-X */ + val = bnx2_rd32(bp, BNX2_TSCH_TSS_CFG); + if (val) { + /* We are in MSI-X mode */ + uint32_t base_cid = ((val >> 10) & 0x7ff) << 3; + msix_vector = (val >> 24) & 0xf; + + bp->status_blk_size = (128 * 9); + + tx_cid = base_cid + msix_vector - 1; + bp->flags |= BNX2_UIO_MSIX_ENABLED; + + bp->get_tx_cons = bnx2_get_tx_msix; + bp->get_rx_cons = bnx2_get_rx_msix; + + LOG_DEBUG(PFX "%s: tss_cfg: 0x%x tx cid: %d", + nic->log_name, val, tx_cid); + + LOG_INFO(PFX "%s: detected using MSI-X vector: %d", + nic->log_name, msix_vector); + } else { + /* We are not in MSI-X mode */ + bp->status_blk_size = 64; + tx_cid = 20; + + bp->get_tx_cons = bnx2_get_tx_msi; + bp->get_rx_cons = bnx2_get_rx_msi; + } + } else { + bp->status_blk_size = 64; + tx_cid = 20; + + bp->get_tx_cons = bnx2_get_tx_msi; + bp->get_rx_cons = bnx2_get_rx_msi; + } + + bp->sblk_map = mmap(NULL, bp->status_blk_size, + PROT_READ | PROT_WRITE, MAP_SHARED, + nic->fd, (off_t) nic->page_size); + if (bp->sblk_map == MAP_FAILED) { + LOG_INFO(PFX "%s: Could not mmap status block: %s", + nic->log_name, strerror(errno)); + goto error_sblk; + } + + if (bp->flags & BNX2_UIO_MSIX_ENABLED) { + uint8_t *status_blk = (uint8_t *) bp->sblk_map; + status_blk += (msix_vector * 128); + + bp->status_blk.msix = (struct status_block_msix *)status_blk; + + LOG_DEBUG(PFX "%s: msix initial cons: tx:%d rx:%d", + nic->log_name, + bp->status_blk.msix->status_tx_quick_consumer_index, + bp->status_blk.msix->status_rx_quick_consumer_index); + } else { + bp->status_blk.msi = (struct status_block *)bp->sblk_map; + + LOG_DEBUG(PFX "%s: msi initial tx:%d rx:%d", + nic->log_name, + BNX2_SBLK_EVEN_IDX(bp->status_blk.msi->tx2), + BNX2_SBLK_EVEN_IDX(bp->status_blk.msi->rx2)); + } + + bp->tx_ring = mmap(NULL, 2 * nic->page_size, + PROT_READ | PROT_WRITE, MAP_SHARED, nic->fd, + (off_t) 2 * nic->page_size); + if (bp->tx_ring == MAP_FAILED) { + LOG_INFO(PFX "%s: Could not mmap tx ring: %s", + nic->log_name, strerror(errno)); + bp->tx_ring = NULL; + goto error_tx_ring; + } + + bp->bufs = mmap(NULL, (bp->rx_ring_size + 1) * bp->rx_buffer_size, + PROT_READ | PROT_WRITE, + MAP_SHARED, nic->fd, (off_t) 3 * nic->page_size); + if (bp->bufs == MAP_FAILED) { + LOG_INFO(PFX "%s: Could not mmap buffers: %s", + nic->log_name, strerror(errno)); + bp->bufs = NULL; + goto error_bufs; + } + + bp->tx_bidx_io = MB_GET_CID_ADDR(tx_cid) + BNX2_L2CTX_TX_HOST_BIDX; + bp->tx_bseq_io = MB_GET_CID_ADDR(tx_cid) + BNX2_L2CTX_TX_HOST_BSEQ; + LOG_INFO(PFX "%s: tx_bidx_io: 0x%x tx_bseq_io: 0x%x", + nic->log_name, bp->tx_bidx_io, bp->tx_bseq_io); + + bp->rx_bidx_io = MB_GET_CID_ADDR(2) + BNX2_L2CTX_HOST_BDIDX; + bp->rx_bseq_io = MB_GET_CID_ADDR(2) + BNX2_L2CTX_HOST_BSEQ; + + bp->tx_cons = 0; + bp->tx_prod = 0; + bp->tx_pkt = bp->bufs; + + bp->rx_index = 0; + bp->rx_cons = 0; + bp->rx_prod = bp->rx_ring_size; + bp->rx_bseq = bp->rx_prod * bp->rx_buffer_size; + bnx2_wr16(bp, bp->rx_bidx_io, bp->rx_prod); + bnx2_wr32(bp, bp->rx_bseq_io, bp->rx_bseq); + + bnx2_reg_sync(bp, bp->rx_bidx_io, sizeof(__u16)); + bnx2_reg_sync(bp, bp->rx_bseq_io, sizeof(__u32)); + + for (i = 0; i < bp->rx_ring_size; i++) { + void *ptr = bp->bufs + (bp->rx_buffer_size * (i + 1)); + + bp->rx_ring[i] = (struct l2_fhdr *)ptr; + bp->rx_pkt_ring[i] = ptr + sizeof(struct l2_fhdr) + 2; + } + + /* Read the MAC address used for the iSCSI interface */ + val = bnx2_rd32(bp, BNX2_EMAC_MAC_MATCH4); + nic->mac_addr[0] = (__u8) (val >> 8); + nic->mac_addr[1] = (__u8) val; + + val = bnx2_rd32(bp, BNX2_EMAC_MAC_MATCH5); + nic->mac_addr[2] = (__u8) (val >> 24); + nic->mac_addr[3] = (__u8) (val >> 16); + nic->mac_addr[4] = (__u8) (val >> 8); + nic->mac_addr[5] = (__u8) val; + + LOG_INFO(PFX "%s: Using mac address: %2x:%2x:%2x:%2x:%2x:%2x", + nic->log_name, + nic->mac_addr[0], nic->mac_addr[1], nic->mac_addr[2], + nic->mac_addr[3], nic->mac_addr[4], nic->mac_addr[5]); + + /* Determine if Hardware VLAN tag stripping is enabled or not */ + if (CNIC_VLAN_STRIPPING_ENABLED == bnx2_strip_vlan_enabled(bp)) + nic->flags |= NIC_VLAN_STRIP_ENABLED; + + /* Prepare the multicast addresses */ + val = 4 | BNX2_RPM_SORT_USER2_BC_EN | BNX2_RPM_SORT_USER2_MC_EN; + if (BNX2_CHIP_NUM(bnx2_get_chip_id(bp)) != CHIP_NUM_5709) + val |= BNX2_RPM_SORT_USER2_PROM_VLAN; + + bnx2_wr32(bp, BNX2_RPM_SORT_USER2, 0x0); + bnx2_wr32(bp, BNX2_RPM_SORT_USER2, val); + bnx2_wr32(bp, BNX2_RPM_SORT_USER2, val | BNX2_RPM_SORT_USER2_ENA); + + rc = enable_multicast(nic); + if (rc != 0) { + errno = rc; + goto error_bufs; + } + msync(bp->reg, 0x12800, MS_SYNC); + LOG_INFO("%s: bnx2 uio initialized", nic->log_name); + + bp->flags |= BNX2_OPENED; + + return 0; + +error_bufs: + munmap(bp->tx_ring, 2 * nic->page_size); + +error_tx_ring: + munmap(bp->status_blk.msi, bp->status_blk_size); + +error_sblk: + munmap(bp->reg, 0x12800); + +error_regs: + munlock(bp->rx_pkt_ring, sizeof(void *) * bp->rx_ring_size); + free(bp->rx_pkt_ring); + bp->rx_pkt_ring = NULL; + +error_alloc_rx_pkt_ring: + munlock(bp->rx_ring, sizeof(struct l2_fhdr *) * bp->rx_ring_size); + free(bp->rx_ring); + bp->rx_ring = NULL; + +error_alloc_rx_ring: + if (nic->fd != INVALID_FD) { + close(nic->fd); + nic->fd = INVALID_FD; + } + bnx2_free(nic); + + return errno; +} + +/** + * bnx2_uio_close_resources() - Used to free resource for the bnx2 NIC + * @param nic - NIC device to free resource + * @param graceful - whether to wait to close gracefully + * @return 0 on success, <0 on failure + */ +static int bnx2_uio_close_resources(nic_t *nic, NIC_SHUTDOWN_T graceful) +{ + bnx2_t *bp = (bnx2_t *) nic->priv; + int rc = 0; + + /* Remove the multicast addresses if added */ + if ((nic->flags & NIC_ADDED_MULICAST) && + (graceful == ALLOW_GRACEFUL_SHUTDOWN)) + disable_multicast(nic); + + /* Check if there is an assoicated bnx2 device */ + if (bp == NULL) { + LOG_WARN(PFX "%s: when closing resources there is " + "no assoicated bnx2", nic->log_name); + return -EIO; + } + + /* Clean up allocated memory */ + if (bp->rx_ring != NULL) { + free(bp->rx_ring); + bp->rx_ring = NULL; + } + + if (bp->rx_pkt_ring != NULL) { + free(bp->rx_pkt_ring); + bp->rx_pkt_ring = NULL; + } + + /* Clean up mapped registers */ + if (bp->bufs != NULL) { + rc = munmap(bp->bufs, + (bp->rx_ring_size + 1) * bp->rx_buffer_size); + if (rc != 0) + LOG_WARN(PFX "%s: Couldn't unmap bufs", nic->log_name); + bp->bufs = NULL; + } + + if (bp->tx_ring != NULL) { + rc = munmap(bp->tx_ring, 2 * nic->page_size); + if (rc != 0) + LOG_WARN(PFX "%s: Couldn't unmap tx_rings", + nic->log_name); + bp->tx_ring = NULL; + } + + if (bp->status_blk.msix != NULL || bp->status_blk.msi != NULL) { + rc = munmap(bp->sblk_map, bp->status_blk_size); + if (rc != 0) + LOG_WARN(PFX "%s: Couldn't unmap status block", + nic->log_name); + bp->sblk_map = NULL; + + bp->status_blk.msix = NULL; + bp->status_blk.msi = NULL; + } + + if (bp->reg != NULL) { + rc = munmap(bp->reg, 0x12800); + if (rc != 0) + LOG_WARN(PFX "%s: Couldn't unmap regs", nic->log_name); + bp->reg = NULL; + } + + if (bp->bar0_fd != INVALID_FD) { + close(bp->bar0_fd); + bp->bar0_fd = INVALID_FD; + } + + if (nic->fd != INVALID_FD) { + rc = close(nic->fd); + if (rc != 0) { + LOG_WARN(PFX + "%s: Couldn't close uio file descriptor: %d", + nic->log_name, nic->fd); + } else { + LOG_DEBUG(PFX "%s: Closed uio file descriptor: %d", + nic->log_name, nic->fd); + } + + nic->fd = INVALID_FD; + } else { + LOG_WARN(PFX "%s: Invalid uio file descriptor: %d", + nic->log_name, nic->fd); + } + + LOG_INFO(PFX "%s: Closed all resources", nic->log_name); + + return 0; +} + +/** + * bnx2_close() - Used to close the NIC device + * @param nic - NIC device to close + * @param graceful - whether to wait to close gracefully + * @return 0 if successful, <0 if there is an error + */ +static int bnx2_close(nic_t *nic, NIC_SHUTDOWN_T graceful) +{ + /* Sanity Check: validate the parameters */ + if (nic == NULL) { + LOG_ERR(PFX "bnx2_close(): nic == NULL"); + return -EINVAL; + } + + LOG_INFO(PFX "Closing NIC device: %s", nic->log_name); + + bnx2_uio_close_resources(nic, graceful); + bnx2_free(nic); + + return 0; +} + +static void bnx2_prepare_xmit_packet(nic_t *nic, + nic_interface_t *nic_iface, + struct packet *pkt) +{ + bnx2_t *bp = (bnx2_t *) nic->priv; + struct uip_vlan_eth_hdr *eth_vlan = (struct uip_vlan_eth_hdr *)pkt->buf; + struct uip_eth_hdr *eth = (struct uip_eth_hdr *)bp->tx_pkt; + + if (eth_vlan->tpid == htons(UIP_ETHTYPE_8021Q)) { + memcpy(bp->tx_pkt, pkt->buf, sizeof(struct uip_eth_hdr)); + eth->type = eth_vlan->type; + pkt->buf_size -= (sizeof(struct uip_vlan_eth_hdr) - + sizeof(struct uip_eth_hdr)); + memcpy(bp->tx_pkt + sizeof(struct uip_eth_hdr), + pkt->buf + sizeof(struct uip_vlan_eth_hdr), + pkt->buf_size - sizeof(struct uip_eth_hdr)); + } else + memcpy(bp->tx_pkt, pkt->buf, pkt->buf_size); + + msync(bp->tx_pkt, pkt->buf_size, MS_SYNC); +} + +/** + * bnx2_get_tx_pkt() - This function is used to a TX packet from the NIC + * @param nic - The NIC device to send the packet + * + */ +void *bnx2_get_tx_pkt(nic_t *nic) +{ + bnx2_t *bp = (bnx2_t *) nic->priv; + return bp->tx_pkt; +} + +/** + * bnx2_start_xmit() - This function is used to send a packet of data + * @param nic - The NIC device to send the packet + * @param len - the length of the TX packet + * + */ +void bnx2_start_xmit(nic_t *nic, size_t len, u16_t vlan_id) +{ + bnx2_t *bp = (bnx2_t *) nic->priv; + uint16_t ring_prod; + struct tx_bd *txbd; + struct rx_bd *rxbd; + rxbd = (struct rx_bd *)(((__u8 *) bp->tx_ring) + nic->page_size); + + if ((rxbd->rx_bd_haddr_hi == 0) && (rxbd->rx_bd_haddr_lo == 0)) { + LOG_PACKET(PFX "%s: trying to transmit when device is closed", + nic->log_name); + pthread_mutex_unlock(&nic->xmit_mutex); + return; + } + + ring_prod = TX_RING_IDX(bp->tx_prod); + txbd = &bp->tx_ring[ring_prod]; + + txbd->tx_bd_mss_nbytes = len; + + if (vlan_id) { + txbd->tx_bd_vlan_tag_flags = (vlan_id << 16) | + TX_BD_FLAGS_VLAN_TAG | TX_BD_FLAGS_END | TX_BD_FLAGS_START; + } else + txbd->tx_bd_vlan_tag_flags = TX_BD_FLAGS_END | + TX_BD_FLAGS_START; + + bp->tx_bseq += len; + bp->tx_prod = NEXT_TX_BD(bp->tx_prod); + + bnx2_wr16(bp, bp->tx_bidx_io, bp->tx_prod); + bnx2_wr32(bp, bp->tx_bseq_io, bp->tx_bseq); + + bnx2_reg_sync(bp, bp->tx_bidx_io, sizeof(__u16)); + bnx2_reg_sync(bp, bp->tx_bseq_io, sizeof(__u32)); + + LOG_PACKET(PFX "%s: sent %d bytes using dev->tx_prod: %d", + nic->log_name, len, bp->tx_prod); +} + +/** + * bnx2_write() - Used to write the data to the hardware + * @param nic - NIC hardware to read from + * @param pkt - The packet which will hold the data to be sent on the wire + * @return 0 if successful, <0 if failed + */ +int bnx2_write(nic_t *nic, nic_interface_t *nic_iface, packet_t *pkt) +{ + bnx2_t *bp; + struct uip_stack *uip; + + /* Sanity Check: validate the parameters */ + if (nic == NULL || nic_iface == NULL || pkt == NULL) { + LOG_ERR(PFX "%s: bnx2_write() nic == 0x%p || " + " nic_iface == 0x%p || " + " pkt == 0x%x", nic, nic_iface, pkt); + return -EINVAL; + } + bp = (bnx2_t *)nic->priv; + uip = &nic_iface->ustack; + + if (pkt->buf_size == 0) { + LOG_ERR(PFX "%s: Trying to transmitted 0 sized packet", + nic->log_name); + return -EINVAL; + } + + if (pthread_mutex_trylock(&nic->xmit_mutex) != 0) { + LOG_PACKET(PFX "%s: Dropped previous transmitted packet", + nic->log_name); + return -EINVAL; + } + + bnx2_prepare_xmit_packet(nic, nic_iface, pkt); + bnx2_start_xmit(nic, pkt->buf_size, + (nic_iface->vlan_priority << 12) | + nic_iface->vlan_id); + + /* bump the bnx2 dev send statistics */ + nic->stats.tx.packets++; + nic->stats.tx.bytes += uip->uip_len; + + LOG_PACKET(PFX "%s: transmitted %d bytes " + "dev->tx_cons: %d, dev->tx_prod: %d, dev->tx_bseq:%d", + nic->log_name, pkt->buf_size, + bp->tx_cons, bp->tx_prod, bp->tx_bseq); + + return 0; +} + +/** + * bnx2_read() - Used to read the data from the hardware + * @param nic - NIC hardware to read from + * @param pkt - The packet which will hold the data + * @return 0 if successful, <0 if failed + */ +static int bnx2_read(nic_t *nic, packet_t *pkt) +{ + bnx2_t *bp; + int rc = 0; + uint16_t hw_cons, sw_cons; + + /* Sanity Check: validate the parameters */ + if (unlikely(nic == NULL || pkt == NULL)) { + LOG_ERR(PFX "%s: bnx2_write() nic == 0x%p || " + " pkt == 0x%x", nic, pkt); + return -EINVAL; + } + bp = (bnx2_t *)nic->priv; + + hw_cons = bp->get_rx_cons(bp); + sw_cons = bp->rx_cons; + + if (sw_cons != hw_cons) { + uint8_t rx_index = bp->rx_index % 3; + struct l2_fhdr *rx_hdr = bp->rx_ring[rx_index]; + void *rx_pkt = bp->rx_pkt_ring[rx_index]; + int len; + uint16_t errors; + + LOG_PACKET(PFX "%s: clearing rx interrupt: %d %d %d", + nic->log_name, sw_cons, hw_cons, rx_index); + + msync(rx_hdr, sizeof(struct l2_fhdr), MS_SYNC); + errors = ((rx_hdr->l2_fhdr_status & 0xffff0000) >> 16); + len = ((rx_hdr->l2_fhdr_vtag_len & 0xffff0000) >> 16) - 4; + + if (unlikely((errors & (L2_FHDR_ERRORS_BAD_CRC | + L2_FHDR_ERRORS_PHY_DECODE | + L2_FHDR_ERRORS_ALIGNMENT | + L2_FHDR_ERRORS_TOO_SHORT | + L2_FHDR_ERRORS_GIANT_FRAME)) || + (len <= 0) || + (len > (bp->rx_buffer_size - + (sizeof(struct l2_fhdr) + 2))) || + (len > pkt->max_buf_size))) { + /* One of the fields in the BD is bad */ + uint16_t status = ((rx_hdr->l2_fhdr_status & + 0x0000ffff)); + + LOG_ERR(PFX "%s: Recv error: 0x%x status: 0x%x " + "len: %d", nic->log_name, errors, status, len); + + if ((len < (bp->rx_buffer_size - + (sizeof(struct l2_fhdr) + 2))) && + (len < pkt->max_buf_size)) + dump_packet_to_log(pkt->nic_iface, rx_pkt, len); + } else { + if (len < (bp->rx_buffer_size - + (sizeof(struct l2_fhdr) + 2))) { + msync(rx_pkt, len, MS_SYNC); + /* Copy the data */ + memcpy(pkt->buf, rx_pkt, len); + pkt->buf_size = len; + + /* Properly set the packet flags */ + /* check if there is VLAN tagging on the + * packet */ + if (rx_hdr->l2_fhdr_status & + L2_FHDR_STATUS_VLAN_TAG) { + pkt->vlan_tag = + rx_hdr->l2_fhdr_vtag_len & 0x0FFF; + pkt->flags |= VLAN_TAGGED; + } else { + pkt->vlan_tag = 0; + } + + rc = 1; + + LOG_PACKET(PFX "%s: processing packet " + "length: %d", nic->log_name, len); + } else { + /* If the NIC passes up a packet bigger + * then the RX buffer, flag it */ + LOG_ERR(PFX "%s: invalid packet length %d " + "receive ", nic->log_name, len); + } + } + + bp->rx_index++; + sw_cons = NEXT_RX_BD(sw_cons); + bp->rx_prod = NEXT_RX_BD(bp->rx_prod); + bp->rx_bseq += 0x400; + + bp->rx_cons = sw_cons; + bnx2_wr16(bp, bp->rx_bidx_io, bp->rx_prod); + bnx2_wr32(bp, bp->rx_bseq_io, bp->rx_bseq); + + bnx2_reg_sync(bp, bp->rx_bidx_io, sizeof(__u16)); + bnx2_reg_sync(bp, bp->rx_bseq_io, sizeof(__u32)); + + /* bump the bnx2 dev recv statistics */ + nic->stats.rx.packets++; + nic->stats.rx.bytes += pkt->buf_size; + } + + return rc; +} + +/******************************************************************************* + * Clearing TX interrupts + ******************************************************************************/ +/** + * bnx2_clear_tx_intr() - This routine is called when a TX interrupt occurs + * @param nic - the nic the interrupt occured on + * @return 0 on success + */ +static int bnx2_clear_tx_intr(nic_t *nic) +{ + bnx2_t *bp; + uint16_t hw_cons; + + /* Sanity check: ensure the parameters passed in are valid */ + if (unlikely(nic == NULL)) { + LOG_ERR(PFX "bnx2_read() nic == NULL"); + return -EINVAL; + } + bp = (bnx2_t *) nic->priv; + hw_cons = bp->get_tx_cons(bp); + + if (bp->flags & BNX2_UIO_TX_HAS_SENT) + bp->flags &= ~BNX2_UIO_TX_HAS_SENT; + + LOG_PACKET(PFX "%s: clearing tx interrupt [%d %d]", + nic->log_name, bp->tx_cons, hw_cons); + + bp->tx_cons = hw_cons; + + /* There is a queued TX packet that needs to be sent out. The usual + * case is when stack will send an ARP packet out before sending the + * intended packet */ + if (nic->tx_packet_queue != NULL) { + packet_t *pkt; + + LOG_PACKET(PFX "%s: sending queued tx packet", nic->log_name); + pkt = nic_dequeue_tx_packet(nic); + + /* Got a TX packet buffer of the TX queue and put it onto + * the hardware */ + if (pkt != NULL) { + bnx2_prepare_xmit_packet(nic, pkt->nic_iface, pkt); + + bnx2_start_xmit(nic, pkt->buf_size, + (pkt->nic_iface->vlan_priority << 12) | + pkt->nic_iface->vlan_id); + + LOG_PACKET(PFX "%s: transmitted queued packet %d bytes " + "dev->tx_cons: %d, dev->tx_prod: %d, " + "dev->tx_bseq:%d", + nic->log_name, pkt->buf_size, + bp->tx_cons, bp->tx_prod, bp->tx_bseq); + + return -EAGAIN; + } + } + + pthread_mutex_unlock(&nic->xmit_mutex); + + return 0; +} + +/******************************************************************************* + * bnx2 NIC op's table + ******************************************************************************/ +struct nic_ops bnx2_op = { + .description = "bnx2", + .open = bnx2_open, + .close = bnx2_close, + .write = bnx2_write, + .get_tx_pkt = bnx2_get_tx_pkt, + .start_xmit = bnx2_start_xmit, + .read = bnx2_read, + .clear_tx_intr = bnx2_clear_tx_intr, + .handle_iscsi_path_req = cnic_handle_iscsi_path_req, + + .lib_ops = { + .get_library_name = bnx2_get_library_name, + .get_pci_table = bnx2_get_pci_table, + .get_library_version = bnx2_get_library_version, + .get_build_date = bnx2_get_build_date, + .get_transport_name = bnx2_get_transport_name, + .get_uio_name = bnx2_get_uio_name, + }, +}; diff --git a/iscsiuio/src/unix/libs/bnx2.h b/iscsiuio/src/unix/libs/bnx2.h new file mode 100644 index 0000000..3ec9437 --- /dev/null +++ b/iscsiuio/src/unix/libs/bnx2.h @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * bnx2.h - bnx2 user space driver + * + */ +#ifndef __BNX2_H__ +#define __BNX2_H__ + +#include "nic.h" + +/****************************************************************************** + * Default BNX2 values + ******************************************************************************/ +#define DEFAULT_NUM_RXBD 3 +#define DEFAULT_RX_LEN 0x400 + +/****************************************************************************** + * BNX2 Hardware structures + ******************************************************************************/ +/* status_block definition for MSI */ +struct status_block { + volatile __u32 status_attn_bits; + volatile __u32 status_attn_bits_ack; + volatile __u32 tx0; + volatile __u32 tx2; + volatile __u32 rx0; + volatile __u32 rx2; + volatile __u32 rx4; + volatile __u32 rx6; + volatile __u32 rx8; + volatile __u32 rx10; + volatile __u32 rx12; + volatile __u32 rx14; + volatile __u32 cmd; + volatile __u32 idx; +}; + +/* status_block definition for MSI-X */ +struct status_block_msix { +#if 0 +#if defined(__BIG_ENDIAN) + __u16 status_tx_quick_consumer_index; + __u16 status_rx_quick_consumer_index; + __u16 status_completion_producer_index; + __u16 status_cmd_consumer_index; + __u32 status_unused; + __u16 status_idx; + __u8 status_unused2; + __u8 status_blk_num; +#elif defined(__LITTLE_ENDIAN) + __u16 status_rx_quick_consumer_index; + __u16 status_tx_quick_consumer_index; + __u16 status_cmd_consumer_index; + __u16 status_completion_producer_index; + __u32 status_unused; + __u8 status_blk_num; + __u8 status_unused2; + __u16 status_idx; +#endif +#endif + __u16 status_rx_quick_consumer_index; + __u16 status_tx_quick_consumer_index; + __u16 status_cmd_consumer_index; + __u16 status_completion_producer_index; + __u32 status_unused; + __u8 status_blk_num; + __u8 status_unused2; + __u16 status_idx; +}; + +/* TX Buffer descriptor */ +struct tx_bd { + __u32 tx_bd_haddr_hi; + __u32 tx_bd_haddr_lo; + __u32 tx_bd_mss_nbytes; + __u32 tx_bd_vlan_tag_flags; +#define TX_BD_FLAGS_VLAN_TAG (1<<3) +#define TX_BD_FLAGS_END (1<<6) +#define TX_BD_FLAGS_START (1<<7) +}; + +/* RX Buffer descriptor */ +struct rx_bd { + __u32 rx_bd_haddr_hi; + __u32 rx_bd_haddr_lo; + + __u32 rx_bd_len; + __u32 rx_bd_flags; +#define RX_BD_FLAGS_END (1<<2) +#define RX_BD_FLAGS_START (1<<3) + +}; + +/* This is the RX L2 Frame header */ +struct l2_fhdr { + __u32 l2_fhdr_status; +#define L2_FHDR_ERRORS_BAD_CRC (1<<17) +#define L2_FHDR_ERRORS_PHY_DECODE (1<<18) +#define L2_FHDR_ERRORS_ALIGNMENT (1<<19) +#define L2_FHDR_ERRORS_TOO_SHORT (1<<20) +#define L2_FHDR_ERRORS_GIANT_FRAME (1<<21) +#define L2_FHDR_ERRORS_TCP_XSUM (1<<28) +#define L2_FHDR_ERRORS_UDP_XSUM (1<<31) + +#define L2_FHDR_STATUS_UDP_DATAGRAM (1<<15) +#define L2_FHDR_STATUS_TCP_DATAGRAM (1<<14) +#define L2_FHDR_STATUS_IP_DATAGRAM (1<<13) +#define L2_FHDR_STATUS_LLC_SNAP (1<<7) +#define L2_FHDR_STATUS_VLAN_TAG (1<<6) + + __u32 l2_fhdr_hash; + + __u32 l2_fhdr_vtag_len; + __u32 l2_fhdr_xsum; +}; + +/****************************************************************************** + * BNX2 Registers Defitions/Values + ******************************************************************************/ +#define BNX2_MISC_ID 0x00000808 +#define BNX2_EMAC_MAC_MATCH4 0x00001420 +#define BNX2_EMAC_MAC_MATCH5 0x00001424 + +#define BNX2_EMAC_RX_MODE 0x000014c8 +#define BNX2_EMAC_RX_MODE_RESET (1L<<0) +#define BNX2_EMAC_RX_MODE_FLOW_EN (1L<<2) +#define BNX2_EMAC_RX_MODE_KEEP_MAC_CONTROL (1L<<3) +#define BNX2_EMAC_RX_MODE_KEEP_PAUSE (1L<<4) +#define BNX2_EMAC_RX_MODE_ACCEPT_OVERSIZE (1L<<5) +#define BNX2_EMAC_RX_MODE_ACCEPT_RUNTS (1L<<6) +#define BNX2_EMAC_RX_MODE_LLC_CHK (1L<<7) +#define BNX2_EMAC_RX_MODE_PROMISCUOUS (1L<<8) +#define BNX2_EMAC_RX_MODE_NO_CRC_CHK (1L<<9) +#define BNX2_EMAC_RX_MODE_KEEP_VLAN_TAG (1L<<10) +#define BNX2_EMAC_RX_MODE_FILT_BROADCAST (1L<<11) +#define BNX2_EMAC_RX_MODE_SORT_MODE (1L<<12) + +#define BNX2_RPM_SORT_USER2 0x00001828 +#define BNX2_RPM_SORT_USER2_PM_EN (0xffffL<<0) +#define BNX2_RPM_SORT_USER2_BC_EN (1L<<16) +#define BNX2_RPM_SORT_USER2_MC_EN (1L<<17) +#define BNX2_RPM_SORT_USER2_MC_HSH_EN (1L<<18) +#define BNX2_RPM_SORT_USER2_PROM_EN (1L<<19) +#define BNX2_RPM_SORT_USER2_VLAN_EN (0xfL<<20) +#define BNX2_RPM_SORT_USER2_PROM_VLAN (1L<<24) +#define BNX2_RPM_SORT_USER2_ENA (1L<<31) + +/* + * tsch_reg definition + * offset: 0x4c00 + */ +#define BNX2_TSCH_TSS_CFG 0x00004c1c +#define BNX2_TSCH_TSS_CFG_TSS_START_CID (0x7ffL<<8) +#define BNX2_TSCH_TSS_CFG_NUM_OF_TSS_CON (0xfL<<24) +#define CNIC_UIO_INVALID_FD -1 + +#define BNX2_L2CTX_TX_HOST_BIDX 0x00000088 +#define BNX2_L2CTX_TX_HOST_BSEQ 0x00000090 + +#define BNX2_L2CTX_HOST_BDIDX 0x00000004 +#define BNX2_L2CTX_HOST_BSEQ 0x00000008 + +/* Used to determin the CHIP ID */ +/* chip num:16-31, rev:12-15, metal:4-11, bond_id:0-3 */ +#define BNX2_CHIP_NUM(bp) ((bp) & 0xffff0000) +#define CHIP_NUM_5706 0x57060000 +#define CHIP_NUM_5708 0x57080000 +#define CHIP_NUM_5709 0x57090000 + +#define CHIP_REV(bp) ((bp) & 0x0000f000) +#define CHIP_REV_Ax 0x00000000 +#define CHIP_REV_Bx 0x00001000 +#define CHIP_REV_Cx 0x00002000 + +#define CHIP_METAL(bp) ((bp) & 0x00000ff0) +#define CHIP_BONDING(bp) ((bp) & 0x0000000f) + +#define CHIP_ID(bp) ((bp) & 0xfffffff0) +#define CHIP_ID_5706_A0 0x57060000 +#define CHIP_ID_5706_A1 0x57060010 +#define CHIP_ID_5706_A2 0x57060020 +#define CHIP_ID_5708_A0 0x57080000 +#define CHIP_ID_5708_B0 0x57081000 +#define CHIP_ID_5708_B1 0x57081010 +#define CHIP_ID_5709_A0 0x57090000 +#define CHIP_ID_5709_A1 0x57090010 + +#define CHIP_BOND_ID(bp) ((bp) & 0xf) + +#define BNX2_SBLK_EVEN_IDX(x) (((x) & 0xffff0000) >> 16) + +#define TX_DESC_CNT (4096 / sizeof(struct tx_bd)) +#define MAX_TX_DESC_CNT (TX_DESC_CNT - 1) + +#define NEXT_TX_BD(x) ((((x) & (MAX_TX_DESC_CNT - 1)) == \ + (MAX_TX_DESC_CNT - 1)) ? \ + (x) + 2 : (x) + 1) + +#define TX_RING_IDX(x) ((x) & MAX_TX_DESC_CNT) + +#define RX_DESC_CNT (4096 / sizeof(struct rx_bd)) +#define MAX_RX_DESC_CNT (RX_DESC_CNT - 1) + +#define NEXT_RX_BD(x) ((((x) & (MAX_RX_DESC_CNT - 1)) == \ + (MAX_RX_DESC_CNT - 1)) ? \ + (x) + 2 : (x) + 1) + +#define MB_KERNEL_CTX_SHIFT 8 +#define MB_KERNEL_CTX_SIZE (1 << MB_KERNEL_CTX_SHIFT) +#define MB_KERNEL_CTX_MASK (MB_KERNEL_CTX_SIZE - 1) +#define MB_GET_CID_ADDR(_cid) (0x10000 + ((_cid) << MB_KERNEL_CTX_SHIFT)) + +typedef struct bnx2 { + nic_t *parent; + + uint16_t flags; +#define BNX2_UIO_MSIX_ENABLED 0x0001 +#define BNX2_UIO_TX_HAS_SENT 0x0002 +#define BNX2_OPENED 0x0004 + + int bar0_fd; + void *reg; /* Pointer to the mapped registers */ + + __u32 tx_bidx_io; + __u32 tx_bseq_io; + + __u16 tx_prod; + __u16 tx_cons; + __u32 tx_bseq; + + __u32 rx_bidx_io; + __u32 rx_bseq_io; + + __u16 rx_prod; + __u16 rx_cons; + __u32 rx_bseq; + + /* RX ring parameters */ + uint32_t rx_ring_size; + uint32_t rx_buffer_size; + + void *bufs; /* Pointer to the mapped buffer space */ + + /* Hardware Status Block locations */ + void *sblk_map; + union { + struct status_block *msi; + struct status_block_msix *msix; + } status_blk; + size_t status_blk_size; + + __u16(*get_rx_cons) (struct bnx2 *); + __u16(*get_tx_cons) (struct bnx2 *); + + uint16_t rx_index; + struct l2_fhdr **rx_ring; + void **rx_pkt_ring; + + struct tx_bd *tx_ring; + void *tx_pkt; + + struct l2_fhdr rcv_l2_fhdr; + __u8 rcv_buf[1500 + 2]; + __u32 rcv_size; +} bnx2_t; + +/****************************************************************************** + * bnx2 Function Declarations + ******************************************************************************/ +struct nic_ops *bnx2_get_ops(); +#endif /* __BNX2_H__ */ diff --git a/iscsiuio/src/unix/libs/bnx2x.c b/iscsiuio/src/unix/libs/bnx2x.c new file mode 100644 index 0000000..c5e7b71 --- /dev/null +++ b/iscsiuio/src/unix/libs/bnx2x.c @@ -0,0 +1,1656 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * bnx2x.c - bnx2x user space driver + * + */ + +/* include nic.h before linux/ethtool.h to avoid redefinitions of + * eth structs +*/ +#include "nic.h" +#include +#include +#include +#include +#include /* Needed for linux/ethtool.h on RHEL 5.x */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include "build_date.h" +#include "bnx2x.h" +#include "cnic.h" +#include "logger.h" +#include "nic_id.h" +#include "nic_utils.h" +#include "options.h" + +#define PFX "bnx2x " + +/* Foward struct declarations */ +struct nic_ops bnx2x_op; + +/******************************************************************************* + * NIC Library Strings + ******************************************************************************/ +static const char library_name[] = "bnx2x"; +static const char library_version[] = PACKAGE_VERSION; +static const char library_uio_name[] = "bnx2x_cnic"; + +/* The name that should be returned from /sys/class/uio/uio0/name */ +static const char cnic_uio_sysfs_name_tempate[] = "/sys/class/uio/uio%i/name"; +static const char bnx2x_uio_sysfs_name[] = "bnx2x_cnic"; + +/******************************************************************************* + * String constants used to display human readable adapter name + ******************************************************************************/ +static const char brcm_57710[] = "QLogic NetXtreme II BCM57710 10-Gigabit"; +static const char brcm_57711[] = "QLogic NetXtreme II BCM57711 10-Gigabit"; +static const char brcm_57711e[] = "QLogic NetXtreme II BCM57711E 10-Gigabit"; +static const char brcm_57712[] = "QLogic NetXtreme II BCM57712 10-Gigabit"; +static const char brcm_57712_MF[] = "QLogic NetXtreme II BCM57712 MF " + "10-Gigabit"; +static const char brcm_57712_VF[] = "QLogic NetXtreme II BCM57712 VF " + "10-Gigabit"; +static const char brcm_57713[] = "QLogic NetXtreme II BCM57713 10-Gigabit"; +static const char brcm_57713e[] = "QLogic NetXtreme II BCM57713E 10-Gigabit"; +static const char brcm_57800[] = "QLogic NetXtreme II BCM57800 10-Gigabit"; +static const char brcm_57800_MF[] = "QLogic NetXtreme II BCM57800 MF " + "10-Gigabit"; +static const char brcm_57800_VF[] = "QLogic NetXtreme II BCM57800 VF " + "10-Gigabit"; +static const char brcm_57810[] = "QLogic NetXtreme II BCM57810 10-Gigabit"; +static const char brcm_57810_MF[] = "QLogic NetXtreme II BCM57810 MF " + "10-Gigabit"; +static const char brcm_57810_VF[] = "QLogic NetXtreme II BCM57810 VF " + "10-Gigabit"; +static const char brcm_57811[] = "QLogic NetXtreme II BCM57811 10-Gigabit"; +static const char brcm_57811_MF[] = "QLogic NetXtreme II BCM57811 MF " + "10-Gigabit"; +static const char brcm_57811_VF[] = "QLogic NetXtreme II BCM57811 VF " + "10-Gigabit"; +static const char brcm_57840[] = "QLogic NetXtreme II BCM57840 10-Gigabit"; +static const char brcm_57840_MF[] = "QLogic NetXtreme II BCM57840 MF " + "10-Gigabit"; +static const char brcm_57840_VF[] = "QLogic NetXtreme II BCM57840 VF " + "10-Gigabit"; +static const char brcm_57840_4_10[] = "QLogic NetXtreme II BCM57840 4x" + "10-Gigabit"; +static const char brcm_57840_2_20[] = "QLogic NetXtreme II BCM57840 2x" + "20-Gigabit"; + +/******************************************************************************* + * PCI ID constants + ******************************************************************************/ +#define PCI_VENDOR_ID_BROADCOM 0x14e4 +#define PCI_VENDOR_ID_QLOGIC 0x1077 +#define PCI_DEVICE_ID_NX2_57710 0x164e +#define PCI_DEVICE_ID_NX2_57711 0x164f +#define PCI_DEVICE_ID_NX2_57711E 0x1650 +#define PCI_DEVICE_ID_NX2_57712 0x1662 +#define PCI_DEVICE_ID_NX2_57712_MF 0x1663 +#define PCI_DEVICE_ID_NX2_57712_VF 0x166f +#define PCI_DEVICE_ID_NX2_57713 0x1651 +#define PCI_DEVICE_ID_NX2_57713E 0x1652 +#define PCI_DEVICE_ID_NX2_57800 0x168a +#define PCI_DEVICE_ID_NX2_57800_MF 0x16a5 +#define PCI_DEVICE_ID_NX2_57800_VF 0x16a9 +#define PCI_DEVICE_ID_NX2_57810 0x168e +#define PCI_DEVICE_ID_NX2_57810_MF 0x16ae +#define PCI_DEVICE_ID_NX2_57810_VF 0x16af +#define PCI_DEVICE_ID_NX2_57811 0x163d +#define PCI_DEVICE_ID_NX2_57811_MF 0x163e +#define PCI_DEVICE_ID_NX2_57811_VF 0x163f +#define PCI_DEVICE_ID_NX2_57840_OBSOLETE 0x168d +#define PCI_DEVICE_ID_NX2_57840_MF_OBSOLETE 0x16ab +#define PCI_DEVICE_ID_NX2_57840_4_10 0x16a1 +#define PCI_DEVICE_ID_NX2_57840_2_20 0x16a2 +#define PCI_DEVICE_ID_NX2_57840_MF 0x16a4 +#define PCI_DEVICE_ID_NX2_57840_VF 0x16ad +#define PCI_ANY_ID (~0) + +/* This is the table used to match PCI vendor and device ID's to the + * human readable string names of the devices */ +static const struct pci_device_id bnx2x_pci_tbl[] = { + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57710, + PCI_ANY_ID, PCI_ANY_ID, brcm_57710}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57711, + PCI_ANY_ID, PCI_ANY_ID, brcm_57711}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57711E, + PCI_ANY_ID, PCI_ANY_ID, brcm_57711e}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57712, + PCI_ANY_ID, PCI_ANY_ID, brcm_57712}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57712_MF, + PCI_ANY_ID, PCI_ANY_ID, brcm_57712_MF}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57712_VF, + PCI_ANY_ID, PCI_ANY_ID, brcm_57712_VF}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57713, + PCI_ANY_ID, PCI_ANY_ID, brcm_57713}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57713E, + PCI_ANY_ID, PCI_ANY_ID, brcm_57713e}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57800, + PCI_ANY_ID, PCI_ANY_ID, brcm_57800}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57800_MF, + PCI_ANY_ID, PCI_ANY_ID, brcm_57800_MF}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57800_VF, + PCI_ANY_ID, PCI_ANY_ID, brcm_57800_VF}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57810, + PCI_ANY_ID, PCI_ANY_ID, brcm_57810}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57810_MF, + PCI_ANY_ID, PCI_ANY_ID, brcm_57810_MF}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57810_VF, + PCI_ANY_ID, PCI_ANY_ID, brcm_57810_VF}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57811, + PCI_ANY_ID, PCI_ANY_ID, brcm_57811}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57811_MF, + PCI_ANY_ID, PCI_ANY_ID, brcm_57811_MF}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57811_VF, + PCI_ANY_ID, PCI_ANY_ID, brcm_57811_VF}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57840_OBSOLETE, + PCI_ANY_ID, PCI_ANY_ID, brcm_57840}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57840_MF_OBSOLETE, + PCI_ANY_ID, PCI_ANY_ID, brcm_57840_MF}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57840_4_10, + PCI_ANY_ID, PCI_ANY_ID, brcm_57840_4_10}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57840_2_20, + PCI_ANY_ID, PCI_ANY_ID, brcm_57840_2_20}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57840_MF, + PCI_ANY_ID, PCI_ANY_ID, brcm_57840_MF}, + {PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_57840_VF, + PCI_ANY_ID, PCI_ANY_ID, brcm_57840_VF}, + {PCI_VENDOR_ID_QLOGIC, PCI_DEVICE_ID_NX2_57840_4_10, + PCI_ANY_ID, PCI_ANY_ID, brcm_57840_4_10}, + {PCI_VENDOR_ID_QLOGIC, PCI_DEVICE_ID_NX2_57840_2_20, + PCI_ANY_ID, PCI_ANY_ID, brcm_57840_2_20}, + {PCI_VENDOR_ID_QLOGIC, PCI_DEVICE_ID_NX2_57840_MF, + PCI_ANY_ID, PCI_ANY_ID, brcm_57840_MF}, + {PCI_VENDOR_ID_QLOGIC, PCI_DEVICE_ID_NX2_57840_VF, + PCI_ANY_ID, PCI_ANY_ID, brcm_57840_VF}, +}; + +static struct iro e1_iro[2] = { + {0x45a0, 0x90, 0x8, 0x0, 0x8}, /* T6.0 */ + {0x50c8, 0x90, 0x8, 0x0, 0x8}, /* T6.4 */ +}; + +static struct iro e1h_iro[2] = { + {0x1c40, 0xe0, 0x8, 0x0, 0x8}, /* T6.0 */ + {0x1e00, 0xe0, 0x8, 0x0, 0x8}, /* T6.4 */ +}; + +static struct iro e2_iro[2] = { + {0x6000, 0x20, 0x0, 0x0, 0x8}, /* T6.0 */ + {0x6000, 0x20, 0x0, 0x0, 0x8}, /* T6.4 */ +}; + +struct bnx2x_driver_version bnx2x_version = { + BNX2X_UNKNOWN_MAJOR_VERSION, + BNX2X_UNKNOWN_MINOR_VERSION, + BNX2X_UNKNOWN_SUB_MINOR_VERSION, +}; + +static int bnx2x_clear_tx_intr(nic_t *nic); + +/******************************************************************************* + * BNX2X Library Functions + ******************************************************************************/ +/** + * bnx2x_get_library_name() - Used to get the name of this NIC libary + * @param name - This function will return the pointer to this NIC + * library name + * @param name_size + */ +static void bnx2x_get_library_name(char **name, size_t *name_size) +{ + *name = (char *)library_name; + *name_size = sizeof(library_name); +} + +/** + * bnx2x_get_library_version() - Used to get the version string of this + * NIC libary + * @param version - This function will return the pointer to this NIC + * library version string + * @param version_size - This will be set with the version size + */ +static void bnx2x_get_library_version(char **version, size_t *version_size) +{ + *version = (char *)library_version; + *version_size = sizeof(library_version); +} + +/** + * bnx2x_get_build_date() - Used to get the build date string of this library + * @param version - This function will return the pointer to this NIC + * library build date string + * @param version_size - This will be set with the build date string size + */ +static void bnx2x_get_build_date(char **build, size_t *build_size) +{ + *build = (char *)build_date; + *build_size = sizeof(build_date); +} + +/** + * bnx2x_get_transport_name() - Used to get the transport name associated + * with this this NIC libary + * @param transport_name - This function will return the pointer to this NIC + * library's associated transport string + * @param transport_name_size - This will be set with the transport name size + */ +static void bnx2x_get_transport_name(char **transport_name, + size_t *transport_name_size) +{ + *transport_name = (char *)bnx2i_library_transport_name; + *transport_name_size = bnx2i_library_transport_name_size; +} + +/** + * bnx2x_get_uio_name() - Used to get the uio name associated with this this + * NIC libary + * @param uio_name - This function will return the pointer to this NIC + * library's associated uio string + * @param transport_name_size - This will be set with the uio name size + */ +static void bnx2x_get_uio_name(char **uio_name, size_t *uio_name_size) +{ + *uio_name = (char *)library_uio_name; + *uio_name_size = sizeof(library_uio_name); +} + +/** + * bnx2x_get_pci_table() - Used to get the PCI table for this NIC libary to + * determine which NIC's based off of PCI ID's are + * supported + * @param table - This function will return the pointer to the PCI table + * @param entries - This function will return the number of entries in the NIC + * library's PCI table + */ +static void bnx2x_get_pci_table(struct pci_device_id **table, + uint32_t *entries) +{ + *table = (struct pci_device_id *)bnx2x_pci_tbl; + *entries = + (uint32_t) (sizeof(bnx2x_pci_tbl) / sizeof(bnx2x_pci_tbl[0])); +} + +/** + * bnx2x_get_ops() - Used to get the NIC library op table + * @param op - The op table of this NIC library + */ +struct nic_ops *bnx2x_get_ops() +{ + return &bnx2x_op; +} + +/******************************************************************************* + * bnx2x Utility Functions + ******************************************************************************/ +/******************************************************************************* + * Utility Functions Used to read register from the bnx2x device + ******************************************************************************/ +static void bnx2x_set_drv_version_unknown(bnx2x_t *bp) +{ + bp->version.major = BNX2X_UNKNOWN_MAJOR_VERSION; + bp->version.minor = BNX2X_UNKNOWN_MINOR_VERSION; + bp->version.sub_minor = BNX2X_UNKNOWN_SUB_MINOR_VERSION; +} + +/* Return: 1 = Unknown, 0 = Known */ +static int bnx2x_is_drv_version_unknown(struct bnx2x_driver_version *version) +{ + if ((version->major == (uint16_t)BNX2X_UNKNOWN_MAJOR_VERSION) && + (version->minor == (uint16_t)BNX2X_UNKNOWN_MINOR_VERSION) && + (version->sub_minor == (uint16_t)BNX2X_UNKNOWN_SUB_MINOR_VERSION)) { + return 1; + } + + return 0; +} + +/** + * bnx2x_get_drv_version() - Used to determine the driver version + * @param bp - Device used to determine bnx2x driver version + */ +static int bnx2x_get_drv_version(bnx2x_t *bp) +{ + nic_t *nic = bp->parent; + int fd, rc; + struct ifreq ifr; + struct ethtool_drvinfo drvinfo; + char *tok, *save_ptr = NULL; + + /* Setup our control structures. */ + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, nic->eth_device_name); + + /* Open control socket. */ + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + LOG_ERR(PFX "%s: Cannot get socket to determine version " + "[0x%x %s]", nic->log_name, errno, strerror(errno)); + return -EIO; + } + + memset(&drvinfo, 0, sizeof(drvinfo)); + drvinfo.cmd = ETHTOOL_GDRVINFO; + ifr.ifr_data = (caddr_t) &drvinfo; + rc = ioctl(fd, SIOCETHTOOL, &ifr); + if (rc < 0) { + LOG_ERR(PFX "%s: call to ethool IOCTL failed [0x%x %s]", + nic->log_name, errno, strerror(errno)); + goto error; + } + + tok = strtok_r(drvinfo.version, ".", &save_ptr); + if (tok == NULL) { + rc = -EIO; + goto error; + } + bp->version.major = atoi(tok); + + tok = strtok_r(NULL, ".", &save_ptr); + if (tok == NULL) { + rc = -EIO; + goto error; + } + bp->version.minor = atoi(tok); + + tok = strtok_r(NULL, ".", &save_ptr); + if (tok == NULL) { + rc = -EIO; + goto error; + } + bp->version.sub_minor = atoi(tok); + + LOG_INFO(PFX "%s: bnx2x driver using version %d.%d.%d", + nic->log_name, + bp->version.major, bp->version.minor, bp->version.sub_minor); + + close(fd); + + return 0; + +error: + close(fd); + bnx2x_set_drv_version_unknown(bp); + + LOG_ERR(PFX "%s: error parsing driver string: '%s'", + nic->log_name, drvinfo.version); + + return rc; + +} + +static inline int bnx2x_is_ver70(bnx2x_t *bp) +{ + return (bp->version.major == 1 && bp->version.minor >= 70); +} + +static inline int bnx2x_is_ver60(bnx2x_t *bp) +{ + return (bp->version.major == 1 && (bp->version.minor == 60 || + bp->version.minor == 62 || + bp->version.minor == 64)); +} + +static inline int bnx2x_is_ver60_plus(bnx2x_t *bp) +{ + return bnx2x_is_ver60(bp) || bnx2x_is_ver70(bp); +} + +static inline int bnx2x_is_ver52(bnx2x_t *bp) +{ + return (bp->version.major == 1 && bp->version.minor == 52); +} + +static void bnx2x_wr32(bnx2x_t *bp, __u32 off, __u32 val) +{ + *((volatile __u32 *)(bp->reg + off)) = val; +} + +static void bnx2x_doorbell(bnx2x_t *bp, __u32 off, __u32 val) +{ + *((volatile __u32 *)(bp->reg2 + off)) = val; +} + +static void bnx2x_flush_doorbell(bnx2x_t *bp, __u32 off) +{ + volatile __u32 tmp __attribute__((__unused__)); + + barrier(); + tmp = *((volatile __u32 *)(bp->reg2 + off)); +} + +static __u32 bnx2x_rd32(bnx2x_t *bp, __u32 off) +{ + return *((volatile __u32 *)(bp->reg + off)); +} + +static int bnx2x_reg_sync(bnx2x_t *bp, __u32 off, __u16 length) +{ + return msync(bp->reg + off, length, MS_SYNC); +} + +static void bnx2x_update_rx_prod(bnx2x_t *bp) +{ + struct ustorm_eth_rx_producers rx_prods = { 0 }; + int i; + + rx_prods.bd_prod = bp->rx_bd_prod; + rx_prods.cqe_prod = bp->rx_prod; + + barrier(); + + for (i = 0; i < sizeof(struct ustorm_eth_rx_producers) / 4; i++) + bnx2x_wr32(bp, bp->rx_prod_io + i * 4, + ((__u32 *)&rx_prods)[i]); + + barrier(); + + bnx2x_reg_sync(bp, bp->rx_prod_io, + sizeof(struct ustorm_eth_rx_producers)); +} + +/** + * bnx2x_get_chip_id() - Used to retrive the chip ID from the nic + * @param dev - Device used to determin NIC type + * @return Chip ID read from the MISC ID register + */ +static int bnx2x_get_chip_id(bnx2x_t *bp) +{ + int val, id; + + /* Get the chip revision id and number. */ + /* chip num:16-31, rev:12-15, metal:4-11, bond_id:0-3 */ + val = bnx2x_rd32(bp, BNX2X_MISC_REG_CHIP_NUM); + id = ((val & 0xffff) << 16); + val = bnx2x_rd32(bp, BNX2X_MISC_REG_CHIP_REV); + id |= ((val & 0xf) << 12); + val = bnx2x_rd32(bp, BNX2X_MISC_REG_CHIP_METAL); + id |= ((val & 0xff) << 4); + val = bnx2x_rd32(bp, BNX2X_MISC_REG_BOND_ID); + id |= (val & 0xf); + + return id; +} + +/** + * bnx2x_uio_verify() + * + */ +static int bnx2x_uio_verify(nic_t *nic) +{ + char *raw = NULL, *raw_tmp; + uint32_t raw_size = 0; + char temp_path[sizeof(cnic_uio_sysfs_name_tempate) + 8]; + int rc = 0; + + /* Build the path to determine uio name */ + snprintf(temp_path, sizeof(temp_path), + cnic_uio_sysfs_name_tempate, nic->uio_minor); + + rc = capture_file(&raw, &raw_size, temp_path); + if (rc != 0) + goto error; + + /* sanitize name string by replacing newline with null termination */ + raw_tmp = raw; + while (*raw_tmp != '\n') + raw_tmp++; + *raw_tmp = '\0'; + + if (strncmp(raw, bnx2x_uio_sysfs_name, + sizeof(bnx2x_uio_sysfs_name)) != 0) { + LOG_ERR(PFX "%s: uio names not equal: " + "expecting %s got %s from %s", + nic->log_name, bnx2x_uio_sysfs_name, raw, temp_path); + rc = -EIO; + } + + free(raw); + + LOG_INFO(PFX "%s: Verified is a cnic_uio device", nic->log_name); + +error: + return rc; +} + +/******************************************************************************* + * bnx2x Utility Functions to get to the hardware consumer indexes + ******************************************************************************/ +static __u16 bnx2x_get_rx(bnx2x_t *bp) +{ + struct host_def_status_block *sblk = bp->status_blk.def; + __u16 rx_comp_cons; + + msync(sblk, sizeof(*sblk), MS_SYNC); + rx_comp_cons = + sblk->u_def_status_block. + index_values[HC_INDEX_DEF_U_ETH_ISCSI_RX_CQ_CONS]; + if ((rx_comp_cons & BNX2X_MAX_RCQ_DESC_CNT(bp)) == + BNX2X_MAX_RCQ_DESC_CNT(bp)) + rx_comp_cons++; + + return rx_comp_cons; +} + +static __u16 bnx2x_get_rx_60(bnx2x_t *bp) +{ + struct host_sp_status_block *sblk = bp->status_blk.sp; + __u16 rx_comp_cons; + + msync(sblk, sizeof(*sblk), MS_SYNC); + rx_comp_cons = + sblk->sp_sb.index_values[HC_SP_INDEX_ETH_ISCSI_RX_CQ_CONS]; + if ((rx_comp_cons & BNX2X_MAX_RCQ_DESC_CNT(bp)) == + BNX2X_MAX_RCQ_DESC_CNT(bp)) + rx_comp_cons++; + + return rx_comp_cons; +} + +static __u16 bnx2x_get_tx(bnx2x_t *bp) +{ + struct host_def_status_block *sblk = bp->status_blk.def; + __u16 tx_cons; + + msync(sblk, sizeof(*sblk), MS_SYNC); + tx_cons = + sblk->c_def_status_block. + index_values[HC_INDEX_DEF_C_ETH_ISCSI_CQ_CONS]; + + return tx_cons; +} + +static __u16 bnx2x_get_tx_60(bnx2x_t *bp) +{ + struct host_sp_status_block *sblk = bp->status_blk.sp; + __u16 tx_cons; + + msync(sblk, sizeof(*sblk), MS_SYNC); + tx_cons = sblk->sp_sb.index_values[HC_SP_INDEX_ETH_ISCSI_CQ_CONS]; + + return tx_cons; +} + +typedef enum { + CNIC_VLAN_STRIPPING_ENABLED = 1, + CNIC_VLAN_STRIPPING_DISABLED = 2, +} CNIC_VLAN_STRIPPING_MODE; + +/** + * bnx2x_strip_vlan_enabled() - This will query the device to determine whether + * VLAN tag stripping is enabled or not + * @param dev - device to check stripping or not + * @ return CNIC_VLAN_STRIPPING_ENABLED stripping is enabled + * CNIC_VLAN_STRIPPING_DISABLED stripping is not enabled + */ +static CNIC_VLAN_STRIPPING_MODE bnx2x_strip_vlan_enabled(bnx2x_t *bp) +{ + return CNIC_VLAN_STRIPPING_DISABLED; +} + +/** + * bnx2x_free() - Used to free a bnx2x structure + */ +static void bnx2x_free(nic_t *nic) +{ + if (nic->priv) + free(nic->priv); + nic->priv = NULL; +} + +/** + * bnx2x_alloc() - Used to allocate a bnx2x structure + */ +static bnx2x_t *bnx2x_alloc(nic_t *nic) +{ + bnx2x_t *bp = malloc(sizeof(*bp)); + + if (bp == NULL) { + LOG_ERR(PFX "%s: Could not allocate BNX2X space", + nic->log_name); + return NULL; + } + + /* Clear out the CNIC contents */ + memset(bp, 0, sizeof(*bp)); + + bp->bar0_fd = INVALID_FD; + bp->bar2_fd = INVALID_FD; + + bp->parent = nic; + nic->priv = (void *)bp; + + bnx2x_set_drv_version_unknown(bp); + + return bp; +} + +/** + * bnx2x_open() - This will initialize all the hardware resources underneath + * a struct cnic_uio device + * @param dev - The struct cnic_uio device to attach the hardware with + * @return 0 on success, on failure a errno will be returned + */ +static int bnx2x_open(nic_t *nic) +{ + bnx2x_t *bp; + struct stat uio_stat; + int i, rc; + __u32 val; + int count; + char sysfs_resc_path[80]; + uint32_t bus; + uint32_t slot; + uint32_t func; + uint32_t mode; + __u32 proto_offset; + __u32 ovtag_offset; + + /* Sanity Check: validate the parameters */ + if (nic == NULL) { + LOG_ERR(PFX "nic == NULL"); + return -EINVAL; + } + + if ((nic->priv) != NULL && + (((bnx2x_t *) (nic->priv))->flags & BNX2X_OPENED)) { + return 0; + } + + bp = bnx2x_alloc(nic); + if (bp == NULL) + return -ENOMEM; + + if (bnx2x_is_drv_version_unknown(&bnx2x_version)) { + /* If version is unknown, go read from ethtool */ + rc = bnx2x_get_drv_version(bp); + if (rc) + goto open_error; + } else { + /* Version is not unknown, just use it */ + bnx2x_version.major = bp->version.major; + bnx2x_version.minor = bp->version.minor; + bnx2x_version.sub_minor = bp->version.sub_minor; + } + + count = 0; + while ((nic->fd < 0) && count < 15) { + /* udev might not have created the file yet */ + pthread_mutex_unlock(&nic->nic_mutex); + sleep(1); + pthread_mutex_lock(&nic->nic_mutex); + + nic->fd = open(nic->uio_device_name, O_RDWR | O_NONBLOCK); + if (nic->fd != INVALID_FD) { + LOG_ERR(PFX "%s: uio device has been brought up " + "via pid: %d on fd: %d", + nic->uio_device_name, getpid(), nic->fd); + + rc = bnx2x_uio_verify(nic); + if (rc != 0) + continue; + + break; + } else { + LOG_WARN(PFX "%s: Could not open device: %s, [%s]", + nic->log_name, nic->uio_device_name, + strerror(errno)); + + manually_trigger_uio_event(nic, nic->uio_minor); + + /* udev might not have created the file yet */ + pthread_mutex_unlock(&nic->nic_mutex); + sleep(1); + pthread_mutex_lock(&nic->nic_mutex); + + count++; + } + } + if (fstat(nic->fd, &uio_stat) < 0) { + LOG_ERR(PFX "%s: Could not fstat device", nic->log_name); + rc = -ENODEV; + goto open_error; + } + nic->uio_minor = minor(uio_stat.st_rdev); + + cnic_get_sysfs_pci_resource_path(nic, 0, sysfs_resc_path, 80); + bp->bar0_fd = open(sysfs_resc_path, O_RDWR | O_SYNC); + if (bp->bar0_fd < 0) { + LOG_ERR(PFX "%s: Could not open %s", nic->log_name, + sysfs_resc_path); + rc = -ENODEV; + goto open_error; + } + + bp->reg = mmap(NULL, BNX2X_BAR_SIZE, PROT_READ | PROT_WRITE, + MAP_SHARED, bp->bar0_fd, (off_t) 0); + + if (bp->reg == MAP_FAILED) { + LOG_INFO(PFX "%s: Couldn't mmap BAR registers: %s", + nic->log_name, strerror(errno)); + bp->reg = NULL; + rc = errno; + goto open_error; + } + + msync(bp->reg, BNX2X_BAR_SIZE, MS_SYNC); + + cnic_get_sysfs_pci_resource_path(nic, 2, sysfs_resc_path, 80); + bp->bar2_fd = open(sysfs_resc_path, O_RDWR | O_SYNC); + if (bp->bar2_fd < 0) { + LOG_ERR(PFX "%s: Could not open %s", nic->log_name, + sysfs_resc_path); + rc = -ENODEV; + goto open_error; + } + + bp->reg2 = mmap(NULL, BNX2X_BAR2_SIZE, PROT_READ | PROT_WRITE, + MAP_SHARED, bp->bar2_fd, (off_t) 0); + + if (bp->reg2 == MAP_FAILED) { + LOG_INFO(PFX "%s: Couldn't mmap BAR2 registers: %s", + nic->log_name, strerror(errno)); + bp->reg2 = NULL; + rc = errno; + goto open_error; + } + + /* TODO: hardcoded with the cnic driver */ + bp->rx_ring_size = 15; + bp->rx_buffer_size = 0x400; + + LOG_DEBUG(PFX "%s: using rx ring size: %d, rx buffer size: %d", + nic->log_name, bp->rx_ring_size, bp->rx_buffer_size); + + /* Determine the number of UIO events that have already occured */ + rc = detemine_initial_uio_events(nic, &nic->intr_count); + if (rc != 0) { + LOG_ERR("Could not determine the number ofinitial UIO events"); + nic->intr_count = 0; + } + + /* Allocate space for rx pkt ring */ + bp->rx_pkt_ring = malloc(sizeof(void *) * bp->rx_ring_size); + if (bp->rx_pkt_ring == NULL) { + LOG_ERR(PFX "%s: Could not allocate space for rx_pkt_ring", + nic->log_name); + rc = errno; + goto open_error; + } + + if (bnx2x_is_ver60_plus(bp)) + bp->status_blk_size = sizeof(struct host_sp_status_block); + else if (bnx2x_is_ver52(bp)) + bp->status_blk_size = sizeof(struct host_def_status_block); + else { + LOG_INFO(PFX "%s: Unsupported bnx2x driver [%d.%d]", + nic->log_name, bp->version.major, bp->version.minor); + + rc = -ENOTSUP; + goto open_error; + } + + bp->status_blk.def = mmap(NULL, bp->status_blk_size, + PROT_READ | PROT_WRITE, MAP_SHARED, + nic->fd, (off_t) nic->page_size); + if (bp->status_blk.def == MAP_FAILED) { + LOG_INFO(PFX "%s: Could not mmap status block: %s", + nic->log_name, strerror(errno)); + bp->status_blk.def = NULL; + rc = errno; + goto open_error; + } + + bp->tx_ring = mmap(NULL, 4 * nic->page_size, + PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_LOCKED, + nic->fd, (off_t) 2 * nic->page_size); + if (bp->tx_ring == MAP_FAILED) { + LOG_INFO(PFX "%s: Could not mmap tx ring: %s", + nic->log_name, strerror(errno)); + bp->tx_ring = NULL; + rc = errno; + goto open_error; + } + + bp->rx_comp_ring.cqe = (union eth_rx_cqe *) + (((__u8 *) bp->tx_ring) + 2 * nic->page_size); + + bp->bufs = mmap(NULL, (bp->rx_ring_size + 1) * bp->rx_buffer_size, + PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_LOCKED, + nic->fd, (off_t) 3 * nic->page_size); + if (bp->bufs == MAP_FAILED) { + LOG_INFO(PFX "%s: Could not mmap buffers: %s", + nic->log_name, strerror(errno)); + bp->bufs = NULL; + rc = errno; + goto open_error; + } + + bp->chip_id = bnx2x_get_chip_id(bp); + LOG_DEBUG(PFX "Chip ID: %x", bp->chip_id); + + rc = get_bus_slot_func_num(nic, &bus, &slot, &func); + if (rc != 0) { + LOG_INFO(PFX "%s: Couldn't determine bus:slot.func", + nic->log_name); + goto open_error; + } + /* In E1/E1H use pci device function as read from sysfs. + * In E2/E3 read physical function from ME register since these chips + * support Physical Device Assignment where kernel BDF maybe arbitrary + * (depending on hypervisor). + */ + if (CHIP_IS_E2_PLUS(bp)) { + func = (bnx2x_rd32(bp, BAR_ME_REGISTER) & ME_REG_ABS_PF_NUM) >> + ME_REG_ABS_PF_NUM_SHIFT; + } + bp->func = func; + bp->port = bp->func % PORT_MAX; + + if (CHIP_IS_E2_PLUS(bp)) { + __u32 val = bnx2x_rd32(bp, MISC_REG_PORT4MODE_EN_OVWR); + if (!(val & 1)) + val = bnx2x_rd32(bp, MISC_REG_PORT4MODE_EN); + else + val = (val >> 1) & 1; + + if (val) + bp->pfid = func >> 1; + else + bp->pfid = func & 0x6; + } else { + bp->pfid = func; + } + + if (bnx2x_is_ver60_plus(bp)) + bp->port = bp->pfid & 1; + + bp->cid = 17; + bp->client_id = 17; + + if (bnx2x_is_ver60_plus(bp)) { + struct client_init_general_data *data = bp->bufs; + + bp->client_id = data->client_id; + if (data->uid.cid) + bp->cid = data->uid.cid; + if (bp->version.minor >= 78 && bp->version.sub_minor >= 55 && + data->uid.cid_override_key == UIO_USE_TX_DOORBELL) { + bp->tx_doorbell = data->uid.tx_db_off; + LOG_INFO(PFX "%s: tx doorbell override offset = 0x%x", + nic->log_name, bp->tx_doorbell); + } + } + + LOG_INFO(PFX "%s: func 0x%x, pfid 0x%x, client_id 0x%x, cid 0x%x", + nic->log_name, bp->func, bp->pfid, bp->client_id, bp->cid); + + if (CHIP_IS_E1(bp)) + bp->iro = e1_iro; + else if (CHIP_IS_E1H(bp)) + bp->iro = e1h_iro; + else if (CHIP_IS_E2_PLUS(bp)) + bp->iro = e2_iro; + + if (bnx2x_is_ver60_plus(bp)) { + __u32 cl_qzone_id = BNX2X_CL_QZONE_ID(bp, bp->client_id); + + bp->iro_idx = 0; + if (bp->version.minor >= 64) { + bp->iro_idx = 1; + cl_qzone_id = BNX2X_CL_QZONE_ID_64(bp, bp->client_id); + } + + bp->rx_prod_io = BAR_USTRORM_INTMEM + + (CHIP_IS_E2_PLUS(bp) ? + USTORM_RX_PRODS_E2_OFFSET(cl_qzone_id) : + USTORM_RX_PRODS_E1X_OFFSET(bp->port, bp->client_id)); + + if (!bp->tx_doorbell) + bp->tx_doorbell = bp->cid * 0x80 + 0x40; + + bp->get_rx_cons = bnx2x_get_rx_60; + bp->get_tx_cons = bnx2x_get_tx_60; + bp->tx_vlan_tag_bit = ETH_TX_BD_FLAGS_VLAN_TAG_T6X; + } else { + bp->rx_prod_io = BAR_USTRORM_INTMEM + + USTORM_RX_PRODS_OFFSET(bp->port, bp->client_id); + + bp->tx_doorbell = bp->cid * nic->page_size + 0x40; + + bp->get_rx_cons = bnx2x_get_rx; + bp->get_tx_cons = bnx2x_get_tx; + bp->tx_vlan_tag_bit = ETH_TX_BD_FLAGS_VLAN_TAG_T5X; + } + + bp->tx_cons = 0; + bp->tx_prod = 0; + bp->tx_bd_prod = 0; + bp->tx_pkt = bp->bufs; + + bp->rx_index = 0; + bp->rx_cons = 0; + bp->rx_bd_cons = 0; + bp->rx_prod = 127; + bp->rx_bd_prod = bp->rx_ring_size; + + for (i = 0; i < bp->rx_ring_size; i++) { + void *ptr = bp->bufs + (bp->rx_buffer_size * (i + 1)); + + bp->rx_pkt_ring[i] = ptr; + } + + val = bnx2x_rd32(bp, MISC_REG_SHARED_MEM_ADDR); + + bp->shmem_base = val; + val = bnx2x_rd32(bp, bp->shmem_base + SHMEM_ISCSI_MAC_UPPER(bp)); + nic->mac_addr[0] = (__u8) (val >> 8); + nic->mac_addr[1] = (__u8) val; + val = bnx2x_rd32(bp, bp->shmem_base + SHMEM_ISCSI_MAC_LOWER(bp)); + nic->mac_addr[2] = (__u8) (val >> 24); + nic->mac_addr[3] = (__u8) (val >> 16); + nic->mac_addr[4] = (__u8) (val >> 8); + nic->mac_addr[5] = (__u8) val; + + if (bnx2x_is_ver60_plus(bp) && CHIP_IS_E2_PLUS(bp)) { + __u32 mf_cfg_addr = 0; + __u32 mac_offset; + __u8 mac[6]; + + val = bnx2x_rd32(bp, (BNX2X_PATH(bp) ? MISC_REG_GENERIC_CR_1 : + MISC_REG_GENERIC_CR_0)); + bp->shmem_base2 = val; + if (bp->shmem_base2) { + /* size */ + val = bnx2x_rd32(bp, bp->shmem_base2); + + if (val > 0x10) + mf_cfg_addr = + bnx2x_rd32(bp, bp->shmem_base2 + 0x10); + } + + if (!mf_cfg_addr) + mf_cfg_addr = bp->shmem_base + 0x7e4; + + /* shared_feat_cfg.config */ + mode = bnx2x_rd32(bp, bp->shmem_base + 0x354); + mode &= 0x700; + LOG_DEBUG(PFX "%s: mode = 0x%x", nic->log_name, mode); + switch (mode) { + case 0x300: /* SI mode */ + mac_offset = 0xe4 + (bp->func * 0x28) + 4; + val = bnx2x_rd32(bp, mf_cfg_addr + mac_offset); + mac[0] = (__u8) (val >> 8); + mac[1] = (__u8) val; + mac_offset += 4; + val = bnx2x_rd32(bp, mf_cfg_addr + mac_offset); + mac[2] = (__u8) (val >> 24); + mac[3] = (__u8) (val >> 16); + mac[4] = (__u8) (val >> 8); + mac[5] = (__u8) val; + + if (mac[0] != 0xff) { + memcpy(nic->mac_addr, mac, 6); + } else if (bp->func > 1) { + LOG_INFO(PFX "%s: Invalid mac address: " + "%02x:%02x:%02x:%02x:%02x:%02x, abort", + nic->log_name, + mac[0], mac[1], mac[2], + mac[3], mac[4], mac[5]); + rc = -ENOTSUP; + goto open_error; + } + break; + + case 0x0: /* MF SD mode */ + case 0x500: + case 0x600: + proto_offset = 0x24 + (bp->func * 0x18); + ovtag_offset = proto_offset + 0xc; + + rc = -ENOTSUP; + val = bnx2x_rd32(bp, mf_cfg_addr + ovtag_offset); + val &= 0xffff; + /* SD mode, check for valid outer VLAN */ + if (val == 0xffff) { + LOG_ERR(PFX "%s: Invalid OV detected for SD, " + " fallback to SF mode!\n", + nic->log_name); + goto SF; + } + /* Check for iSCSI protocol */ + val = bnx2x_rd32(bp, mf_cfg_addr + proto_offset); + if ((val & 6) != 6) + goto open_error; + + mac_offset = proto_offset + 0x4; + val = bnx2x_rd32(bp, mf_cfg_addr + mac_offset); + mac[0] = (__u8) (val >> 8); + mac[1] = (__u8) val; + mac_offset += 4; + val = bnx2x_rd32(bp, mf_cfg_addr + mac_offset); + mac[2] = (__u8) (val >> 24); + mac[3] = (__u8) (val >> 16); + mac[4] = (__u8) (val >> 8); + mac[5] = (__u8) val; + memcpy(nic->mac_addr, mac, 6); + break; + } + } +SF: + LOG_INFO(PFX "%s: Using mac address: %02x:%02x:%02x:%02x:%02x:%02x", + nic->log_name, + nic->mac_addr[0], nic->mac_addr[1], nic->mac_addr[2], + nic->mac_addr[3], nic->mac_addr[4], nic->mac_addr[5]); + + /* Determine if Hardware VLAN tag stripping is enabled or not */ + if (CNIC_VLAN_STRIPPING_ENABLED == bnx2x_strip_vlan_enabled(bp)) + nic->flags |= NIC_VLAN_STRIP_ENABLED; + + msync(bp->reg, BNX2X_BAR_SIZE, MS_SYNC); + + LOG_INFO("%s: bnx2x initialized", nic->log_name); + + bnx2x_update_rx_prod(bp); + bp->flags |= BNX2X_OPENED; + + return 0; + +open_error: + if (bp->tx_ring) { + munmap(bp->tx_ring, 4 * nic->page_size); + bp->tx_ring = NULL; + } + + if (bp->status_blk.def) { + munmap(bp->status_blk.def, bp->status_blk_size); + bp->status_blk.def = NULL; + } + + if (bp->reg) { + munmap(bp->reg, BNX2X_BAR_SIZE); + bp->reg = NULL; + } + + if (bp->reg2) { + munmap(bp->reg2, BNX2X_BAR2_SIZE); + bp->reg2 = NULL; + } + + if (bp->rx_pkt_ring) { + free(bp->rx_pkt_ring); + bp->rx_pkt_ring = NULL; + } + + if (bp->bar2_fd != INVALID_FD) { + close(bp->bar2_fd); + bp->bar2_fd = INVALID_FD; + } + + if (bp->bar0_fd != INVALID_FD) { + close(bp->bar0_fd); + bp->bar0_fd = INVALID_FD; + } + if (nic->fd != INVALID_FD) { + close(nic->fd); + nic->fd = INVALID_FD; + } + bnx2x_free(nic); + + return rc; +} + +/** + * bnx2x_uio_close_resources() - Used to free resource for the NIC/CNIC + * @param nic - NIC device to free resource + * @param graceful - whether to wait to close gracefully + * @return 0 on success, <0 on failure + */ +static int bnx2x_uio_close_resources(nic_t *nic, NIC_SHUTDOWN_T graceful) +{ + bnx2x_t *bp = (bnx2x_t *) nic->priv; + int rc = 0; + + /* Check if there is an assoicated bnx2x device */ + if (bp == NULL) { + LOG_WARN(PFX "%s: when closing resources there is " + "no assoicated bnx2x", nic->log_name); + return -EIO; + } + + /* Clean up allocated memory */ + + if (bp->rx_pkt_ring != NULL) { + free(bp->rx_pkt_ring); + bp->rx_pkt_ring = NULL; + } + + /* Clean up mapped registers */ + if (bp->bufs != NULL) { + rc = munmap(bp->bufs, + (bp->rx_ring_size + 1) * bp->rx_buffer_size); + if (rc != 0) + LOG_WARN(PFX "%s: Couldn't unmap bufs", nic->log_name); + bp->bufs = NULL; + } + + if (bp->tx_ring != NULL) { + rc = munmap(bp->tx_ring, 4 * nic->page_size); + if (rc != 0) + LOG_WARN(PFX "%s: Couldn't unmap tx_rings", + nic->log_name); + bp->tx_ring = NULL; + } + + if (bp->status_blk.def != NULL) { + rc = munmap(bp->status_blk.def, bp->status_blk_size); + if (rc != 0) + LOG_WARN(PFX "%s: Couldn't unmap status block", + nic->log_name); + bp->status_blk.def = NULL; + } + + if (bp->reg != NULL) { + rc = munmap(bp->reg, BNX2X_BAR_SIZE); + if (rc != 0) + LOG_WARN(PFX "%s: Couldn't unmap regs", nic->log_name); + bp->reg = NULL; + } + + if (bp->reg2 != NULL) { + rc = munmap(bp->reg2, BNX2X_BAR2_SIZE); + if (rc != 0) + LOG_WARN(PFX "%s: Couldn't unmap regs", nic->log_name); + bp->reg2 = NULL; + } + + if (bp->bar2_fd != INVALID_FD) { + close(bp->bar2_fd); + bp->bar2_fd = INVALID_FD; + } + + if (bp->bar0_fd != INVALID_FD) { + close(bp->bar0_fd); + bp->bar0_fd = INVALID_FD; + } + + if (nic->fd != INVALID_FD) { + rc = close(nic->fd); + if (rc != 0) { + LOG_WARN(PFX + "%s: Couldn't close uio file descriptor: %d", + nic->log_name, nic->fd); + } else { + LOG_DEBUG(PFX "%s: Closed uio file descriptor: %d", + nic->log_name, nic->fd); + } + + nic->fd = INVALID_FD; + } else { + LOG_WARN(PFX "%s: Invalid uio file descriptor: %d", + nic->log_name, nic->fd); + } + + bnx2x_set_drv_version_unknown(bp); + + LOG_INFO(PFX "%s: Closed all resources", nic->log_name); + + return 0; +} + +/** + * bnx2x_close() - Used to close the NIC device + * @param nic - NIC device to close + * @param graceful - whether to wait to close gracefully + * @return 0 if successful, <0 if there is an error + */ +static int bnx2x_close(nic_t *nic, NIC_SHUTDOWN_T graceful) +{ + /* Sanity Check: validate the parameters */ + if (nic == NULL) { + LOG_ERR(PFX "bnx2x_close(): nic == NULL"); + return -EINVAL; + } + if (nic->priv == NULL) { + LOG_ERR(PFX "bnx2x_close(): nic->priv == NULL"); + return -EINVAL; + } + + LOG_INFO(PFX "Closing NIC device: %s", nic->log_name); + + bnx2x_uio_close_resources(nic, graceful); + bnx2x_free(nic); + + return 0; +} + +static void bnx2x_prepare_xmit_packet(nic_t *nic, + nic_interface_t *nic_iface, + struct packet *pkt) +{ + bnx2x_t *bp = (bnx2x_t *) nic->priv; + struct uip_vlan_eth_hdr *eth_vlan = (struct uip_vlan_eth_hdr *)pkt->buf; + struct uip_eth_hdr *eth = (struct uip_eth_hdr *)bp->tx_pkt; + + if (eth_vlan->tpid == htons(UIP_ETHTYPE_8021Q)) { + memcpy(bp->tx_pkt, pkt->buf, sizeof(struct uip_eth_hdr)); + eth->type = eth_vlan->type; + pkt->buf_size -= (sizeof(struct uip_vlan_eth_hdr) - + sizeof(struct uip_eth_hdr)); + memcpy(bp->tx_pkt + sizeof(struct uip_eth_hdr), + pkt->buf + sizeof(struct uip_vlan_eth_hdr), + pkt->buf_size - sizeof(struct uip_eth_hdr)); + } else + memcpy(bp->tx_pkt, pkt->buf, pkt->buf_size); + + msync(bp->tx_pkt, pkt->buf_size, MS_SYNC); +} + +/** + * bnx2x_get_tx_pkt() - This function is used to a TX packet from the NIC + * @param nic - The NIC device to send the packet + */ +void *bnx2x_get_tx_pkt(nic_t *nic) +{ + bnx2x_t *bp = (bnx2x_t *) nic->priv; + return bp->tx_pkt; +} + +/** + * bnx2x_start_xmit() - This function is used to send a packet of data + * @param nic - The NIC device to send the packet + * @param len - the length of the TX packet + * + */ +void bnx2x_start_xmit(nic_t *nic, size_t len, u16_t vlan_id) +{ + bnx2x_t *bp = (bnx2x_t *) nic->priv; + uint16_t ring_prod; + struct eth_tx_start_bd *txbd; + struct eth_tx_bd *txbd2; + struct eth_rx_bd *rx_bd; + rx_bd = (struct eth_rx_bd *)(((__u8 *) bp->tx_ring) + nic->page_size); + + if ((rx_bd->addr_hi == 0) && (rx_bd->addr_lo == 0)) { + LOG_PACKET(PFX "%s: trying to transmit when device is closed", + nic->log_name); + return; + } + + ring_prod = BNX2X_TX_RING_IDX(bp->tx_bd_prod); + txbd = &bp->tx_ring[ring_prod]; + + BNX2X_SET_TX_VLAN(bp, txbd, vlan_id); + + bp->tx_prod++; + bp->tx_bd_prod = BNX2X_NEXT_TX_BD(bp->tx_bd_prod); + bp->tx_bd_prod = BNX2X_NEXT_TX_BD(bp->tx_bd_prod); + + ring_prod = BNX2X_TX_RING_IDX(bp->tx_bd_prod); + txbd2 = (struct eth_tx_bd *)&bp->tx_ring[ring_prod]; + + txbd2->nbytes = len - 0x10; + txbd2->total_pkt_bytes = len; + + bp->tx_bd_prod = BNX2X_NEXT_TX_BD(bp->tx_bd_prod); + + barrier(); + if (nic->nl_process_if_down == 0) { + bnx2x_doorbell(bp, bp->tx_doorbell, 0x02 | + (bp->tx_bd_prod << 16)); + bnx2x_flush_doorbell(bp, bp->tx_doorbell); + } else { + LOG_ERR(PFX "Pkt transmission failed."); + } + + LOG_PACKET(PFX "%s: sent %d bytes using bp->tx_prod: %d", + nic->log_name, len, bp->tx_prod); +} + +/** + * bnx2x_write() - Used to write the data to the hardware + * @param nic - NIC hardware to read from + * @param pkt - The packet which will hold the data to be sent on the wire + * @return 0 if successful, <0 if failed + */ +int bnx2x_write(nic_t *nic, nic_interface_t *nic_iface, packet_t *pkt) +{ + bnx2x_t *bp; + struct uip_stack *uip; + int i = 0; + + /* Sanity Check: validate the parameters */ + if (nic == NULL || nic_iface == NULL || pkt == NULL) { + LOG_ERR(PFX "%s: bnx2x_write() nic == 0x%p || " + " nic_iface == 0x%p || " + " pkt == 0x%x", nic, nic_iface, pkt); + return -EINVAL; + } + bp = (bnx2x_t *) nic->priv; + uip = &nic_iface->ustack; + + if (pkt->buf_size == 0) { + LOG_ERR(PFX "%s: Trying to transmitted 0 sized packet", + nic->log_name); + return -EINVAL; + } + + /* Try to wait for a TX completion */ + for (i = 0; i < 15; i++) { + struct timespec sleep_req = {.tv_sec = 0, .tv_nsec = 5000000 }, + sleep_rem; + + if (bnx2x_clear_tx_intr(nic) == 0) + break; + + nanosleep(&sleep_req, &sleep_rem); + } + + if (pthread_mutex_trylock(&nic->xmit_mutex) != 0) { + LOG_PACKET(PFX "%s: Dropped previous transmitted packet", + nic->log_name); + return -EINVAL; + } + + bnx2x_prepare_xmit_packet(nic, nic_iface, pkt); + bnx2x_start_xmit(nic, pkt->buf_size, + (nic_iface->vlan_priority << 12) | + nic_iface->vlan_id); + + /* bump the cnic dev send statistics */ + nic->stats.tx.packets++; + nic->stats.tx.bytes += uip->uip_len; + + LOG_PACKET(PFX "%s: transmitted %d bytes " + "dev->tx_cons: %d, dev->tx_prod: %d, dev->tx_bd_prod:%d", + nic->log_name, pkt->buf_size, + bp->tx_cons, bp->tx_prod, bp->tx_bd_prod); + + pthread_mutex_unlock(&nic->xmit_mutex); + + return 0; +} + +static inline int bnx2x_get_rx_pad(bnx2x_t *bp, union eth_rx_cqe *cqe) +{ + int pad = 0; + + if (bnx2x_is_ver70(bp)) + pad = ((union eth_rx_cqe_70 *)cqe)->fast_path_cqe_70. \ + placement_offset; + else if (bnx2x_is_ver60(bp)) { + if (bp->version.minor >= 64) + pad = cqe->fast_path_cqe_64.placement_offset; + else + pad = cqe->fast_path_cqe.placement_offset; + } + return pad; +} + +/** + * bnx2x_read() - Used to read the data from the hardware + * @param nic - NIC hardware to read from + * @param pkt - The packet which will hold the data + * @return 0 if successful, <0 if failed + */ +static int bnx2x_read(nic_t *nic, packet_t *pkt) +{ + bnx2x_t *bp; + int rc = 0; + uint16_t hw_cons, sw_cons, bd_cons, bd_prod; + + /* Sanity Check: validate the parameters */ + if (nic == NULL || pkt == NULL) { + LOG_ERR(PFX "%s: bnx2x_read() nic == 0x%p || " + " pkt == 0x%x", nic, pkt); + return -EINVAL; + } + bp = (bnx2x_t *) nic->priv; + + hw_cons = bp->get_rx_cons(bp); + sw_cons = bp->rx_cons; + bd_cons = BNX2X_RX_BD(bp->rx_bd_cons); + bd_prod = BNX2X_RX_BD(bp->rx_bd_prod); + + if (sw_cons != hw_cons) { + uint16_t comp_ring_index = sw_cons & BNX2X_MAX_RCQ_DESC_CNT(bp); + uint8_t ring_index; + union eth_rx_cqe *cqe; + __u8 cqe_fp_flags; + void *rx_pkt; + int len, pad, cqe_size, max_len; + rc = 1; + + if (bnx2x_is_ver70(bp)) { + cqe = (union eth_rx_cqe *) + &bp->rx_comp_ring.cqe70[comp_ring_index]; + cqe_size = sizeof(union eth_rx_cqe_70); + } else { + cqe = &bp->rx_comp_ring.cqe[comp_ring_index]; + cqe_size = sizeof(union eth_rx_cqe); + } + cqe_fp_flags = cqe->fast_path_cqe.type_error_flags; + + LOG_PACKET(PFX "%s: clearing rx interrupt: %d %d", + nic->log_name, sw_cons, hw_cons); + + msync(cqe, cqe_size, MS_SYNC); + + if (!(cqe_fp_flags & ETH_FAST_PATH_RX_CQE_TYPE)) { + ring_index = bd_cons % 15; + len = cqe->fast_path_cqe.pkt_len; + pad = bnx2x_get_rx_pad(bp, cqe); + rx_pkt = bp->rx_pkt_ring[ring_index] + pad; + + /* Doto query MTU size of physical device */ + /* Ensure len is valid */ + max_len = pkt->max_buf_size < bp->rx_buffer_size ? + pkt->max_buf_size : bp->rx_buffer_size; + if (len + pad > max_len) { + LOG_DEBUG(PFX "%s: bad BD length: %d", + nic->log_name, len); + len = max_len - pad; + } + if (len > 0) { + msync(rx_pkt, len, MS_SYNC); + /* Copy the data */ + memcpy(pkt->buf, rx_pkt, len); + pkt->buf_size = len; + + /* Properly set the packet flags */ + /* check if there is VLAN tagging */ + if (cqe->fast_path_cqe.vlan_tag != 0) { + pkt->vlan_tag = + cqe->fast_path_cqe.vlan_tag; + pkt->flags |= VLAN_TAGGED; + } else { + pkt->vlan_tag = 0; + } + + LOG_PACKET(PFX + "%s: processing packet length: %d", + nic->log_name, len); + + /* bump the cnic dev recv statistics */ + nic->stats.rx.packets++; + nic->stats.rx.bytes += pkt->buf_size; + } + + bd_cons = BNX2X_NEXT_RX_IDX(bd_cons); + bd_prod = BNX2X_NEXT_RX_IDX(bd_prod); + + } + sw_cons = BNX2X_NEXT_RCQ_IDX(bp, sw_cons); + bp->rx_prod = BNX2X_NEXT_RCQ_IDX(bp, bp->rx_prod); + } + bp->rx_cons = sw_cons; + bp->rx_bd_cons = bd_cons; + bp->rx_bd_prod = bd_prod; + bp->rx_hw_prod = hw_cons; + + if (rc) + bnx2x_update_rx_prod(bp); + + return rc; +} + +/******************************************************************************* + * Clearing TX interrupts + ******************************************************************************/ +/** + * bnx2x_clear_tx_intr() - This routine is called when a TX interrupt occurs + * @param nic - the nic the interrupt occured on + * @return 0 on success + */ +static int bnx2x_clear_tx_intr(nic_t *nic) +{ + bnx2x_t *bp; + uint16_t hw_cons; + + /* Sanity check: ensure the parameters passed in are valid */ + if (unlikely(nic == NULL)) { + LOG_ERR(PFX "bnx2x_read() nic == NULL"); + return -EINVAL; + } + bp = (bnx2x_t *) nic->priv; + hw_cons = bp->get_tx_cons(bp); + + if (bp->tx_cons == hw_cons) { + if (bp->tx_cons == bp->tx_prod) + return 0; + return -EAGAIN; + } + + if (pthread_mutex_trylock(&nic->xmit_mutex)) { + LOG_ERR(PFX "%s: unable to get xmit_mutex.", nic->log_name); + return -EINVAL; + } + + LOG_PACKET(PFX "%s: clearing tx interrupt [%d %d]", + nic->log_name, bp->tx_cons, hw_cons); + bp->tx_cons = hw_cons; + + /* There is a queued TX packet that needs to be sent out. The usual + * case is when stack will send an ARP packet out before sending the + * intended packet */ + if (nic->tx_packet_queue != NULL) { + packet_t *pkt; + int i; + + LOG_PACKET(PFX "%s: sending queued tx packet", nic->log_name); + pkt = nic_dequeue_tx_packet(nic); + + /* Got a TX packet buffer of the TX queue and put it onto + * the hardware */ + if (pkt != NULL) { + bnx2x_prepare_xmit_packet(nic, pkt->nic_iface, pkt); + + bnx2x_start_xmit(nic, pkt->buf_size, + (pkt->nic_iface->vlan_priority << 12) | + pkt->nic_iface->vlan_id); + + LOG_PACKET(PFX "%s: transmitted queued packet %d bytes " + "dev->tx_cons: %d, dev->tx_prod: %d, " + "dev->tx_bd_prod:%d", + nic->log_name, pkt->buf_size, + bp->tx_cons, bp->tx_prod, bp->tx_bd_prod); + + pthread_mutex_unlock(&nic->xmit_mutex); + return 0; + } + + /* Try to wait for a TX completion */ + for (i = 0; i < 15; i++) { + struct timespec sleep_req = {.tv_sec = 0, + .tv_nsec = 5000000 + }, sleep_rem; + + hw_cons = bp->get_tx_cons(bp); + if (bp->tx_cons != hw_cons) { + LOG_PACKET(PFX + "%s: clearing tx interrupt [%d %d]", + nic->log_name, bp->tx_cons, hw_cons); + bp->tx_cons = hw_cons; + + break; + } + + nanosleep(&sleep_req, &sleep_rem); + } + } + + pthread_mutex_unlock(&nic->xmit_mutex); + + return 0; +} + +/******************************************************************************* + * bnx2x NIC op's table + ******************************************************************************/ +struct nic_ops bnx2x_op = { + .description = "bnx2x", + .open = bnx2x_open, + .close = bnx2x_close, + .write = bnx2x_write, + .get_tx_pkt = bnx2x_get_tx_pkt, + .start_xmit = bnx2x_start_xmit, + .read = bnx2x_read, + .clear_tx_intr = bnx2x_clear_tx_intr, + .handle_iscsi_path_req = cnic_handle_iscsi_path_req, + + .lib_ops = { + .get_library_name = bnx2x_get_library_name, + .get_pci_table = bnx2x_get_pci_table, + .get_library_version = bnx2x_get_library_version, + .get_build_date = bnx2x_get_build_date, + .get_transport_name = bnx2x_get_transport_name, + .get_uio_name = bnx2x_get_uio_name, + }, +}; diff --git a/iscsiuio/src/unix/libs/bnx2x.h b/iscsiuio/src/unix/libs/bnx2x.h new file mode 100644 index 0000000..ce55cfc --- /dev/null +++ b/iscsiuio/src/unix/libs/bnx2x.h @@ -0,0 +1,712 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * bnx2x.h - bnx2x user space driver + * + */ +#ifndef __BNX2X_H__ +#define __BNX2X_H__ + +#include "nic.h" + +/****************************************************************************** + * Default CNIC values + ******************************************************************************/ +#define DEFAULT_BNX2X_NUM_RXBD 15 +#define DEFAULT_BNX2X_RX_LEN 0x400 + +/****************************************************************************** + * BNX2X Hardware structures + ******************************************************************************/ +#define HC_USTORM_DEF_SB_NUM_INDICES 8 +#define HC_CSTORM_DEF_SB_NUM_INDICES 8 +#define HC_XSTORM_DEF_SB_NUM_INDICES 4 +#define HC_TSTORM_DEF_SB_NUM_INDICES 4 + +struct atten_def_status_block { + volatile __u32 attn_bits; + volatile __u32 attn_bits_ack; + volatile __u8 status_block_id; + volatile __u8 reserved0; + volatile __u16 attn_bits_index; + volatile __u32 reserved1; +}; + +struct cstorm_def_status_block_u { + volatile __u16 index_values[HC_USTORM_DEF_SB_NUM_INDICES]; + volatile __u16 status_block_index; + volatile __u8 func; + volatile __u8 status_block_id; + volatile __u32 __flags; +}; + +struct cstorm_def_status_block_c { + volatile __u16 index_values[HC_CSTORM_DEF_SB_NUM_INDICES]; + volatile __u16 status_block_index; + volatile __u8 func; + volatile __u8 status_block_id; + volatile __u32 __flags; +}; + +struct xstorm_def_status_block { + volatile __u16 index_values[HC_XSTORM_DEF_SB_NUM_INDICES]; + volatile __u16 status_block_index; + volatile __u8 func; + volatile __u8 status_block_id; + volatile __u32 __flags; +}; + +struct tstorm_def_status_block { + volatile __u16 index_values[HC_TSTORM_DEF_SB_NUM_INDICES]; + volatile __u16 status_block_index; + volatile __u8 func; + volatile __u8 status_block_id; + volatile __u32 __flags; +}; + +struct host_def_status_block { + struct atten_def_status_block atten_status_block; + struct cstorm_def_status_block_u u_def_status_block; + struct cstorm_def_status_block_c c_def_status_block; + struct xstorm_def_status_block x_def_status_block; + struct tstorm_def_status_block t_def_status_block; +}; + +#define HC_INDEX_DEF_U_ETH_ISCSI_RX_CQ_CONS 1 +#define HC_INDEX_DEF_U_ETH_ISCSI_RX_BD_CONS 3 +#define HC_INDEX_DEF_C_ETH_ISCSI_CQ_CONS 5 + +struct atten_sp_status_block { + __u32 attn_bits; + __u32 attn_bits_ack; + __u8 status_block_id; + __u8 reserved0; + __u16 attn_bits_index; + __u32 reserved1; +}; + +#define HC_SP_SB_MAX_INDICES 16 + +struct hc_sp_status_block { + __u16 index_values[HC_SP_SB_MAX_INDICES]; + __u16 running_index; + __u16 rsrv; + __u32 rsrv1; +}; + +struct host_sp_status_block { + struct atten_sp_status_block atten_status_block; + struct hc_sp_status_block sp_sb; +}; + +#define HC_SP_INDEX_ETH_ISCSI_CQ_CONS 5 +#define HC_SP_INDEX_ETH_ISCSI_RX_CQ_CONS 1 + +/* + * VLAN mode on TX BDs + */ +enum eth_tx_vlan_type { + X_ETH_NO_VLAN = 0, + X_ETH_OUTBAND_VLAN = 1, + X_ETH_INBAND_VLAN = 2, + X_ETH_FW_ADDED_VLAN = 3, + MAX_ETH_TX_VLAN_TYPE +}; + +/* TX Buffer descriptor */ +struct eth_tx_bd_flags { + __u8 as_bitfield; +/* t6.X HSI */ +#define ETH_TX_BD_FLAGS_IP_CSUM_T6X (0x1<<0) +#define ETH_TX_BD_FLAGS_IP_CSUM_SHIFT_T6X 0 +#define ETH_TX_BD_FLAGS_L4_CSUM_T6X (0x1<<1) +#define ETH_TX_BD_FLAGS_L4_CSUM_SHIFT_T6X 1 +#define ETH_TX_BD_FLAGS_VLAN_MODE_T6X (0x3<<2) +#define ETH_TX_BD_FLAGS_VLAN_MODE_SHIFT_T6X 2 +#define ETH_TX_BD_FLAGS_START_BD_T6X (0x1<<4) +#define ETH_TX_BD_FLAGS_START_BD_SHIFT_T6X 4 +#define ETH_TX_BD_FLAGS_IS_UDP_T6X (0x1<<5) +#define ETH_TX_BD_FLAGS_IS_UDP_SHIFT_T6X 5 +#define ETH_TX_BD_FLAGS_SW_LSO_T6X (0x1<<6) +#define ETH_TX_BD_FLAGS_SW_LSO_SHIFT_T6X 6 +#define ETH_TX_BD_FLAGS_IPV6_T6X (0x1<<7) +#define ETH_TX_BD_FLAGS_IPV6_SHIFT_T6X 7 + +/* Legacy t5.2 HSI defines */ +#define ETH_TX_BD_FLAGS_VLAN_TAG_T5X (0x1<<0) +#define ETH_TX_BD_FLAGS_VLAN_TAG_SHIFT_T5X 0 +#define ETH_TX_BD_FLAGS_IP_CSUM_T5X (0x1<<1) +#define ETH_TX_BD_FLAGS_IP_CSUM_SHIFT_T5X 1 +#define ETH_TX_BD_FLAGS_L4_CSUM_T5X (0x1<<2) +#define ETH_TX_BD_FLAGS_L4_CSUM_SHIFT_T5X 2 +#define ETH_TX_BD_FLAGS_END_BD_T5X (0x1<<3) +#define ETH_TX_BD_FLAGS_END_BD_SHIFT_T5X 3 +#define ETH_TX_BD_FLAGS_START_BD_T5X (0x1<<4) +#define ETH_TX_BD_FLAGS_START_BD_SHIFT_T5X 4 +#define ETH_TX_BD_FLAGS_HDR_POOL_T5X (0x1<<5) +#define ETH_TX_BD_FLAGS_HDR_POOL_SHIFT_T5X 5 +#define ETH_TX_BD_FLAGS_SW_LSO_T5X (0x1<<6) +#define ETH_TX_BD_FLAGS_SW_LSO_SHIFT_T5X 6 +#define ETH_TX_BD_FLAGS_IPV6_T5X (0x1<<7) +#define ETH_TX_BD_FLAGS_IPV6_SHIFT_T5X 7 +}; + +#define ETH_TX_BD_FLAGS_VLAN_TAG_T6X \ + (X_ETH_OUTBAND_VLAN << ETH_TX_BD_FLAGS_VLAN_MODE_SHIFT_T6X) + +#define BNX2X_SET_TX_VLAN(bp, txbd, vlan_id) \ + do { \ + if (vlan_id) { \ + (txbd)->vlan = vlan_id; \ + (txbd)->bd_flags.as_bitfield |= \ + (bp)->tx_vlan_tag_bit; \ + } else { \ + (txbd)->vlan = (bp)->tx_prod; \ + (txbd)->bd_flags.as_bitfield &= \ + ~(bp)->tx_vlan_tag_bit; \ + } \ + } while (0) + +struct eth_tx_start_bd { + __u32 addr_lo; + __u32 addr_hi; + __u16 nbd; + __u16 nbytes; + __u16 vlan; + struct eth_tx_bd_flags bd_flags; + __u8 general_data; +#define ETH_TX_START_BD_HDR_NBDS (0x3F<<0) +#define ETH_TX_START_BD_HDR_NBDS_SHIFT 0 +#define ETH_TX_START_BD_ETH_ADDR_TYPE (0x3<<6) +#define ETH_TX_START_BD_ETH_ADDR_TYPE_SHIFT 6 +}; + +struct eth_tx_bd { + __u32 addr_lo; + __u32 addr_hi; + __u16 total_pkt_bytes; + __u16 nbytes; + __u8 reserved[4]; +}; + +/* RX Buffer descriptor */ +struct eth_rx_bd { + __u32 addr_lo; + __u32 addr_hi; +}; + +struct ramrod_data { + volatile __u32 data_lo; + volatile __u32 data_hi; +}; + +struct common_ramrod_eth_rx_cqe { + volatile __u8 ramrod_type; +#define COMMON_RAMROD_ETH_RX_CQE_TYPE (0x1<<0) +#define COMMON_RAMROD_ETH_RX_CQE_TYPE_SHIFT 0 +#define COMMON_RAMROD_ETH_RX_CQE_RESERVED0 (0x7F<<1) +#define COMMON_RAMROD_ETH_RX_CQE_RESERVED0_SHIFT 1 + volatile __u8 conn_type; + volatile __u16 reserved1; + volatile __u32 conn_and_cmd_data; +#define COMMON_RAMROD_ETH_RX_CQE_CID (0xFFFFFF<<0) +#define COMMON_RAMROD_ETH_RX_CQE_CID_SHIFT 0 +#define COMMON_RAMROD_ETH_RX_CQE_CMD_ID (0xFF<<24) +#define COMMON_RAMROD_ETH_RX_CQE_CMD_ID_SHIFT 24 + struct ramrod_data protocol_data; + __u32 reserved2[4]; +}; + +struct common_ramrod_eth_rx_cqe_70 { + volatile __u8 ramrod_type; + volatile __u8 conn_type; + volatile __u16 reserved1; + volatile __u32 conn_and_cmd_data; + struct ramrod_data protocol_data; + __u32 echo; + __u32 reserved2[11]; +}; + +struct parsing_flags { + volatile __u16 flags; +}; + +struct eth_fast_path_rx_cqe { + volatile __u8 type_error_flags; +#define ETH_FAST_PATH_RX_CQE_TYPE (0x1<<0) +#define ETH_FAST_PATH_RX_CQE_TYPE_SHIFT 0 +#define ETH_FAST_PATH_RX_CQE_PHY_DECODE_ERR_FLG (0x1<<1) +#define ETH_FAST_PATH_RX_CQE_PHY_DECODE_ERR_FLG_SHIFT 1 +#define ETH_FAST_PATH_RX_CQE_IP_BAD_XSUM_FLG (0x1<<2) +#define ETH_FAST_PATH_RX_CQE_IP_BAD_XSUM_FLG_SHIFT 2 +#define ETH_FAST_PATH_RX_CQE_L4_BAD_XSUM_FLG (0x1<<3) +#define ETH_FAST_PATH_RX_CQE_L4_BAD_XSUM_FLG_SHIFT 3 +#define ETH_FAST_PATH_RX_CQE_START_FLG (0x1<<4) +#define ETH_FAST_PATH_RX_CQE_START_FLG_SHIFT 4 +#define ETH_FAST_PATH_RX_CQE_END_FLG (0x1<<5) +#define ETH_FAST_PATH_RX_CQE_END_FLG_SHIFT 5 +#define ETH_FAST_PATH_RX_CQE_RESERVED0 (0x3<<6) +#define ETH_FAST_PATH_RX_CQE_RESERVED0_SHIFT 6 + volatile __u8 status_flags; +#define ETH_FAST_PATH_RX_CQE_RSS_HASH_TYPE (0x7<<0) +#define ETH_FAST_PATH_RX_CQE_RSS_HASH_TYPE_SHIFT 0 +#define ETH_FAST_PATH_RX_CQE_RSS_HASH_FLG (0x1<<3) +#define ETH_FAST_PATH_RX_CQE_RSS_HASH_FLG_SHIFT 3 +#define ETH_FAST_PATH_RX_CQE_BROADCAST_FLG (0x1<<4) +#define ETH_FAST_PATH_RX_CQE_BROADCAST_FLG_SHIFT 4 +#define ETH_FAST_PATH_RX_CQE_MAC_MATCH_FLG (0x1<<5) +#define ETH_FAST_PATH_RX_CQE_MAC_MATCH_FLG_SHIFT 5 +#define ETH_FAST_PATH_RX_CQE_IP_XSUM_NO_VALIDATION_FLG (0x1<<6) +#define ETH_FAST_PATH_RX_CQE_IP_XSUM_NO_VALIDATION_FLG_SHIFT 6 +#define ETH_FAST_PATH_RX_CQE_L4_XSUM_NO_VALIDATION_FLG (0x1<<7) +#define ETH_FAST_PATH_RX_CQE_L4_XSUM_NO_VALIDATION_FLG_SHIFT 7 + volatile __u8 placement_offset; + volatile __u8 queue_index; + volatile __u32 rss_hash_result; + volatile __u16 vlan_tag; + volatile __u16 pkt_len; + volatile __u16 len_on_bd; + struct parsing_flags pars_flags; + volatile __u16 sgl[8]; +}; + +union eth_sgl_or_raw_data { + volatile __u16 sgl[8]; + volatile __u32 raw_data[4]; +}; + +struct eth_fast_path_rx_cqe_64 { + volatile __u8 type_error_flags; +#define ETH_FAST_PATH_RX_CQE_TYPE_64 (0x3<<0) +#define ETH_FAST_PATH_RX_CQE_TYPE_SHIFT_64 0 +#define ETH_FAST_PATH_RX_CQE_SGL_RAW_SEL (0x1<<2) +#define ETH_FAST_PATH_RX_CQE_SGL_RAW_SEL_SHIFT 2 +#define ETH_FAST_PATH_RX_CQE_PHY_DECODE_ERR_FLG_64 (0x1<<3) +#define ETH_FAST_PATH_RX_CQE_PHY_DECODE_ERR_FLG_SHIFT_64 3 +#define ETH_FAST_PATH_RX_CQE_IP_BAD_XSUM_FLG_64 (0x1<<4) +#define ETH_FAST_PATH_RX_CQE_IP_BAD_XSUM_FLG_SHIFT_64 4 +#define ETH_FAST_PATH_RX_CQE_L4_BAD_XSUM_FLG_64 (0x1<<5) +#define ETH_FAST_PATH_RX_CQE_L4_BAD_XSUM_FLG_SHIFT_64 5 +#define ETH_FAST_PATH_RX_CQE_RESERVED0_64 (0x3<<6) +#define ETH_FAST_PATH_RX_CQE_RESERVED0_SHIFT_64 6 + volatile __u8 status_flags; +#define ETH_FAST_PATH_RX_CQE_RSS_HASH_TYPE (0x7<<0) +#define ETH_FAST_PATH_RX_CQE_RSS_HASH_TYPE_SHIFT 0 +#define ETH_FAST_PATH_RX_CQE_RSS_HASH_FLG (0x1<<3) +#define ETH_FAST_PATH_RX_CQE_RSS_HASH_FLG_SHIFT 3 +#define ETH_FAST_PATH_RX_CQE_BROADCAST_FLG (0x1<<4) +#define ETH_FAST_PATH_RX_CQE_BROADCAST_FLG_SHIFT 4 +#define ETH_FAST_PATH_RX_CQE_MAC_MATCH_FLG (0x1<<5) +#define ETH_FAST_PATH_RX_CQE_MAC_MATCH_FLG_SHIFT 5 +#define ETH_FAST_PATH_RX_CQE_IP_XSUM_NO_VALIDATION_FLG (0x1<<6) +#define ETH_FAST_PATH_RX_CQE_IP_XSUM_NO_VALIDATION_FLG_SHIFT 6 +#define ETH_FAST_PATH_RX_CQE_L4_XSUM_NO_VALIDATION_FLG (0x1<<7) +#define ETH_FAST_PATH_RX_CQE_L4_XSUM_NO_VALIDATION_FLG_SHIFT 7 + volatile __u8 queue_index; + volatile __u8 placement_offset; + volatile __u32 rss_hash_result; + volatile __u16 vlan_tag; + volatile __u16 pkt_len; + volatile __u16 len_on_bd; + struct parsing_flags pars_flags; + union eth_sgl_or_raw_data sgl_or_raw_data; +}; + +struct eth_fast_path_rx_cqe_70 { + volatile __u8 type_error_flags; + volatile __u8 status_flags; + volatile __u8 queue_index; + volatile __u8 placement_offset; + volatile __u32 rss_hash_result; + volatile __u16 vlan_tag; + volatile __u16 pkt_len; + volatile __u16 len_on_bd; + struct parsing_flags pars_flags; + union eth_sgl_or_raw_data sgl_or_raw_data; + __u32 reserved1[8]; +}; + +struct eth_rx_cqe_next_page { + __u32 addr_lo; + __u32 addr_hi; + __u32 reserved[6]; +}; + +struct eth_rx_cqe_next_page_70 { + __u32 addr_lo; + __u32 addr_hi; + __u32 reserved[14]; +}; + +union eth_rx_cqe { + struct eth_fast_path_rx_cqe fast_path_cqe; + struct eth_fast_path_rx_cqe_64 fast_path_cqe_64; + struct common_ramrod_eth_rx_cqe ramrod_cqe; + struct eth_rx_cqe_next_page next_page_cqe; +}; + +union eth_rx_cqe_70 { + struct eth_fast_path_rx_cqe_70 fast_path_cqe_70; + struct common_ramrod_eth_rx_cqe_70 ramrod_cqe_70; + struct eth_rx_cqe_next_page_70 next_page_cqe_70; +}; + +struct uio_init_data { + __u32 cid; + __u32 tx_db_off; + __u32 cid_override_key; +#define UIO_USE_TX_DOORBELL 0x017855DB +}; + +struct client_init_general_data { + __u8 client_id; + __u8 statistics_counter_id; + __u8 statistics_en_flg; + __u8 is_fcoe_flg; + __u8 activate_flg; + __u8 sp_client_id; + __u16 mtu; + __u8 statistics_zero_flg; + __u8 func_id; + __u8 cos; + __u8 traffic_type; + struct uio_init_data uid; +}; + +/****************************************************************************** + * BNX2X Registers and HSI + ******************************************************************************/ +#define BNX2X_BAR_SIZE 0x500000 +#define BNX2X_BAR2_SIZE 0x12000 + +#define BNX2X_CHIP_ID(bp) (bp->chip_id & 0xfffffff0) + +#define PORT_MAX 2 + +/* [R 4] This field indicates the type of the device. '0' - 2 Ports; '1' - 1 + * Port. */ +#define BNX2X_MISC_REG_BOND_ID 0xa400 +/* [R 8] These bits indicate the metal revision of the chip. This value + * starts at 0x00 for each all-layer tape-out and increments by one for each + * tape-out. */ +#define BNX2X_MISC_REG_CHIP_METAL 0xa404 +/* [R 16] These bits indicate the part number for the chip. */ +#define BNX2X_MISC_REG_CHIP_NUM 0xa408 +/* [R 4] These bits indicate the base revision of the chip. This value + * starts at 0x0 for the A0 tape-out and increments by one for each + * all-layer tape-out. */ +#define BNX2X_MISC_REG_CHIP_REV 0xa40c + +/* From the bnx2x driver */ +#define CHIP_NUM(bp) (bp->chip_id >> 16) +#define CHIP_NUM_57710 0x164e +#define CHIP_NUM_57711 0x164f +#define CHIP_NUM_57711E 0x1650 +#define CHIP_NUM_57712 0x1662 +#define CHIP_NUM_57712_MF 0x1663 +#define CHIP_NUM_57712_VF 0x166f +#define CHIP_NUM_57713 0x1651 +#define CHIP_NUM_57713E 0x1652 +#define CHIP_NUM_57800 0x168a +#define CHIP_NUM_57800_MF 0x16a5 +#define CHIP_NUM_57800_VF 0x16a9 +#define CHIP_NUM_57810 0x168e +#define CHIP_NUM_57810_MF 0x16ae +#define CHIP_NUM_57810_VF 0x16af +#define CHIP_NUM_57811 0x163d +#define CHIP_NUM_57811_MF 0x163e +#define CHIP_NUM_57811_VF 0x163f +#define CHIP_NUM_57840_OBSOLETE 0x168d +#define CHIP_NUM_57840_MF_OBSOLETE 0x16ab +#define CHIP_NUM_57840_4_10 0x16a1 +#define CHIP_NUM_57840_2_20 0x16a2 +#define CHIP_NUM_57840_MF 0x16a4 +#define CHIP_NUM_57840_VF 0x16ad + +#define CHIP_IS_E1(bp) (CHIP_NUM(bp) == CHIP_NUM_57710) +#define CHIP_IS_57711(bp) (CHIP_NUM(bp) == CHIP_NUM_57711) +#define CHIP_IS_57711E(bp) (CHIP_NUM(bp) == CHIP_NUM_57711E) +#define CHIP_IS_57712(bp) (CHIP_NUM(bp) == CHIP_NUM_57712) +#define CHIP_IS_57712_VF(bp) (CHIP_NUM(bp) == CHIP_NUM_57712_VF) +#define CHIP_IS_57712_MF(bp) (CHIP_NUM(bp) == CHIP_NUM_57712_MF) +#define CHIP_IS_57800(bp) (CHIP_NUM(bp) == CHIP_NUM_57800) +#define CHIP_IS_57800_VF(bp) (CHIP_NUM(bp) == CHIP_NUM_57800_MF) +#define CHIP_IS_57800_MF(bp) (CHIP_NUM(bp) == CHIP_NUM_57800_VF) +#define CHIP_IS_57810(bp) (CHIP_NUM(bp) == CHIP_NUM_57810) +#define CHIP_IS_57810_VF(bp) (CHIP_NUM(bp) == CHIP_NUM_57810_MF) +#define CHIP_IS_57810_MF(bp) (CHIP_NUM(bp) == CHIP_NUM_57810_VF) +#define CHIP_IS_57811(bp) (CHIP_NUM(bp) == CHIP_NUM_57811) +#define CHIP_IS_57811_VF(bp) (CHIP_NUM(bp) == CHIP_NUM_57811_MF) +#define CHIP_IS_57811_MF(bp) (CHIP_NUM(bp) == CHIP_NUM_57811_VF) + +#define CHIP_IS_57840(bp) \ + ((CHIP_NUM(bp) == CHIP_NUM_57840_4_10) || \ + (CHIP_NUM(bp) == CHIP_NUM_57840_2_20) || \ + (CHIP_NUM(bp) == CHIP_NUM_57840_OBSOLETE)) +#define CHIP_IS_57840_MF(bp) ((CHIP_NUM(bp) == CHIP_NUM_57840_MF) || \ + (CHIP_NUM(bp) == CHIP_NUM_57840_MF_OBSOLETE)) +#define CHIP_IS_57840_VF(bp) (CHIP_NUM(bp) == CHIP_NUM_57840_VF) +#define CHIP_IS_E1H(bp) (CHIP_IS_57711(bp) || \ + CHIP_IS_57711E(bp)) + +#define CHIP_IS_E2(bp) (CHIP_IS_57712(bp) || \ + CHIP_IS_57712_MF(bp) || \ + CHIP_IS_57712_VF(bp)) +#define CHIP_IS_E3(bp) (CHIP_IS_57800(bp) || \ + CHIP_IS_57800_MF(bp) || \ + CHIP_IS_57800_VF(bp) || \ + CHIP_IS_57810(bp) || \ + CHIP_IS_57810_MF(bp) || \ + CHIP_IS_57810_VF(bp) || \ + CHIP_IS_57840(bp) || \ + CHIP_IS_57840_MF(bp) || \ + CHIP_IS_57840_VF(bp) || \ + CHIP_IS_57811(bp) || \ + CHIP_IS_57811_MF(bp) || \ + CHIP_IS_57811_VF(bp)) + +#define CHIP_IS_E1x(bp) (CHIP_IS_E1((bp)) || CHIP_IS_E1H((bp))) +#define USES_WARPCORE(bp) (CHIP_IS_E3(bp)) +#define IS_E1H_OFFSET (!CHIP_IS_E1H(bp)) +/* End of From the bnx2x driver */ + +#define CHIP_IS_E2_PLUS(bp) (CHIP_IS_E2(bp) || CHIP_IS_E3(bp)) + +#define MISC_REG_SHARED_MEM_ADDR 0xa2b4 + +#define MISC_REG_BOND_ID 0xa400 +#define MISC_REG_CHIP_METAL 0xa404 +#define MISC_REG_CHIP_NUM 0xa408 +#define MISC_REG_CHIP_REV 0xa40c + +#define MISC_REG_PORT4MODE_EN 0xa750 +#define MISC_REG_PORT4MODE_EN_OVWR 0xa720 + +#define MISC_REG_GENERIC_CR_0 0xa460 +#define MISC_REG_GENERIC_CR_1 0xa464 + +#define BAR_USTRORM_INTMEM 0x400000 +#define BAR_CSTRORM_INTMEM 0x410000 +#define BAR_XSTRORM_INTMEM 0x420000 +#define BAR_TSTRORM_INTMEM 0x430000 + +#define BAR_ME_REGISTER 0x450000 +#define ME_REG_PF_NUM_SHIFT 0 +#define ME_REG_PF_NUM\ + (7L<iro[bp->iro_idx]) + +#define USTORM_RX_PRODS_E1X_OFFSET(port, client_id) \ + (IRO_ENT.base + ((port) * IRO_ENT.m1) + ((client_id) * IRO_ENT.m2)) + +#define USTORM_RX_PRODS_E2_OFFSET(qzone_id) \ + (IRO_ENT.base + ((qzone_id) * IRO_ENT.m1)) + +#define ETH_MAX_RX_CLIENTS_E1H 28 +#define ETH_MAX_RX_CLIENTS_E2 28 + +#define BNX2X_CL_QZONE_ID(bp, cli) \ + (cli + (bp->port * (CHIP_IS_E2(bp) ? \ + ETH_MAX_RX_CLIENTS_E2 : \ + ETH_MAX_RX_CLIENTS_E1H))) + +#define BNX2X_CL_QZONE_ID_64(bp, cli) \ + (CHIP_IS_E2_PLUS(bp) ? (cli) : \ + (cli + (bp->port * ETH_MAX_RX_CLIENTS_E1H))) + +#define BNX2X_PATH(bp) (!CHIP_IS_E2_PLUS(bp) ? 0 : (bp)->func & 1) + +#define SHMEM_P0_ISCSI_MAC_UPPER 0x4c +#define SHMEM_P0_ISCSI_MAC_LOWER 0x50 +#define SHMEM_P1_ISCSI_MAC_UPPER 0x1dc +#define SHMEM_P1_ISCSI_MAC_LOWER 0x1e0 + +#define SHMEM_ISCSI_MAC_UPPER(bp) \ + (((bp)->port == 0) ? \ + SHMEM_P0_ISCSI_MAC_UPPER : SHMEM_P1_ISCSI_MAC_UPPER) + +#define SHMEM_ISCSI_MAC_LOWER(bp) \ + (((bp)->port == 0) ? \ + SHMEM_P0_ISCSI_MAC_LOWER : SHMEM_P1_ISCSI_MAC_LOWER) + +#define BNX2X_RCQ_DESC_CNT (4096 / sizeof(union eth_rx_cqe)) +#define BNX2X_RCQ_DESC_CNT_70 (4096 / sizeof(union eth_rx_cqe_70)) +#define BNX2X_MAX_RCQ_DESC_CNT(bp) \ + ((bnx2x_is_ver70(bp) ? BNX2X_RCQ_DESC_CNT_70 : BNX2X_RCQ_DESC_CNT) - 1) + +#define BNX2X_RX_DESC_CNT (4096 / sizeof(struct eth_rx_bd)) +#define BNX2X_MAX_RX_DESC_CNT (BNX2X_RX_DESC_CNT - 2) +#define BNX2X_NUM_RX_BD (BNX2X_RX_DESC_CNT * 1) +#define BNX2X_MAX_RX_BD (BNX2X_NUM_RX_BD - 1) + +#define BNX2X_TX_DESC_CNT (4096 / sizeof(struct eth_tx_start_bd)) +#define BNX2X_MAX_TX_DESC_CNT (BNX2X_TX_DESC_CNT - 1) + +#define BNX2X_NEXT_RX_IDX(x) ((((x) & (BNX2X_RX_DESC_CNT - 1)) == \ + (BNX2X_MAX_RX_DESC_CNT - 1)) ? \ + (x) + 3 : (x) + 1) + +#define BNX2X_NEXT_RCQ_IDX(bp, x) \ + ((((x) & BNX2X_MAX_RCQ_DESC_CNT(bp)) == \ + (BNX2X_MAX_RCQ_DESC_CNT(bp) - 1)) ? (x) + 2 : (x) + 1) +#define BNX2X_RX_BD(x) ((x) & BNX2X_MAX_RX_BD) + +#define BNX2X_NEXT_TX_BD(x) ((((x) & (BNX2X_MAX_TX_DESC_CNT - 1)) == \ + (BNX2X_MAX_TX_DESC_CNT - 1)) ? \ + (x) + 2 : (x) + 1) + +#define BNX2X_TX_RING_IDX(x) ((x) & BNX2X_MAX_TX_DESC_CNT) + +struct ustorm_eth_rx_producers { + __u16 cqe_prod; + __u16 bd_prod; + __u16 sge_prod; + __u16 reserved; +}; + +#define BNX2X_UNKNOWN_MAJOR_VERSION -1 +#define BNX2X_UNKNOWN_MINOR_VERSION -1 +#define BNX2X_UNKNOWN_SUB_MINOR_VERSION -1 +struct bnx2x_driver_version { + uint16_t major; + uint16_t minor; + uint16_t sub_minor; +}; + +typedef struct bnx2x { + nic_t *parent; + + struct bnx2x_driver_version version; + + uint16_t flags; +#define CNIC_UIO_UNITIALIZED 0x0001 +#define CNIC_UIO_INITIALIZED 0x0002 +#define CNIC_UIO_ENABLED 0x0004 +#define CNIC_UIO_DISABLED 0x0008 +#define CNIC_UIO_IPv6_ENABLED 0x0010 +#define CNIC_UIO_ADDED_MULICAST 0x0020 +#define CNIC_UIO_MSIX_ENABLED 0x0200 +#define CNIC_UIO_TX_HAS_SENT 0x0400 +#define BNX2X_OPENED 0x0800 + + void *reg; /* Pointer to the BAR1 mapped registers */ + void *reg2; /* Pointer to the BAR2 mapped registers */ + + int bar0_fd; + int bar2_fd; + + __u32 chip_id; + __u32 shmem_base; + __u32 shmem_base2; + int func; + int port; + int pfid; + __u32 cid; + __u32 client_id; + + struct iro *iro; + int iro_idx; + + __u32 tx_doorbell; + + __u16 tx_prod; + __u16 tx_bd_prod; + __u16 tx_cons; + __u8 tx_vlan_tag_bit; + + __u32 rx_prod_io; + + __u16 rx_prod; + __u16 rx_bd_prod; + __u16 rx_cons; + __u16 rx_bd_cons; + __u16 rx_hw_prod; + + __u16(*get_rx_cons) (struct bnx2x *); + __u16(*get_tx_cons) (struct bnx2x *); + + /* RX ring parameters */ + uint32_t rx_ring_size; + uint32_t rx_buffer_size; + + void *bufs; /* Pointer to the mapped buffer space */ + + /* Hardware Status Block locations */ + void *sblk_map; + union { + struct host_def_status_block *def; + struct host_sp_status_block *sp; + } status_blk; + + int status_blk_size; + + uint16_t rx_index; + union { + union eth_rx_cqe *cqe; + union eth_rx_cqe_70 *cqe70; + } rx_comp_ring; + void **rx_pkt_ring; + + struct eth_tx_start_bd *tx_ring; + void *tx_pkt; + +} bnx2x_t; + +/****************************************************************************** + * bnx2x Function Declarations + ******************************************************************************/ +void bnx2x_start_xmit(nic_t *nic, size_t len, u16_t vlan_id); + +struct nic_ops *bnx2x_get_ops(); +#endif /* __BNX2X_H__ */ diff --git a/iscsiuio/src/unix/libs/cnic.c b/iscsiuio/src/unix/libs/cnic.c new file mode 100644 index 0000000..9cdf933 --- /dev/null +++ b/iscsiuio/src/unix/libs/cnic.c @@ -0,0 +1,671 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * cnic.c - CNIC UIO uIP user space stack + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "uip_arp.h" +#include "nic.h" +#include "nic_utils.h" +#include "logger.h" +#include "options.h" + +#include "cnic.h" +#include "iscsi_if.h" +#include "ipv6_ndpc.h" +#include "qedi.h" + +/******************************************************************************* + * Constants + ******************************************************************************/ +#define PFX "CNIC " + +/******************************************************************************* + * Constants shared between the bnx2 and bnx2x modules + ******************************************************************************/ +const char bnx2i_library_transport_name[] = "bnx2i"; +const size_t bnx2i_library_transport_name_size = + sizeof(bnx2i_library_transport_name); + +/******************************************************************************* + * Constants for qedi module + ******************************************************************************/ +const char qedi_library_transport_name[] = "qedi"; +const size_t qedi_library_transport_name_size = + sizeof(qedi_library_transport_name); + +/****************************************************************************** + * Netlink Functions + ******************************************************************************/ + +static int cnic_arp_send(nic_t *nic, nic_interface_t *nic_iface, int fd, + __u8 *mac_addr, __u32 ip_addr, char *addr_str) +{ + struct ether_header *eth; + struct ether_arp *arp; + __u32 dst_ip = ip_addr; + int pkt_size = sizeof(*eth) + sizeof(*arp); + int rc; + struct in_addr addr; + static const uint8_t multicast_mac[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + LOG_DEBUG(PFX "%s: host:%d - try getting xmit mutex cnic arp send", + nic->log_name, nic->host_no); + rc = pthread_mutex_trylock(&nic->xmit_mutex); + if (rc != 0) { + LOG_DEBUG(PFX "%s: could not get xmit_mutex", nic->log_name); + return -EAGAIN; + } + + eth = (*nic->ops->get_tx_pkt) (nic); + if (eth == NULL) { + LOG_WARN(PFX "%s: couldn't get tx packet", nic->log_name); + pthread_mutex_unlock(&nic->xmit_mutex); + return -EAGAIN; + } + + nic_fill_ethernet_header(nic_iface, eth, + nic->mac_addr, (void *)multicast_mac, + &pkt_size, (void *)&arp, ETHERTYPE_ARP); + + arp->arp_hrd = htons(ARPHRD_ETHER); + arp->arp_pro = htons(ETHERTYPE_IP); + arp->arp_hln = ETH_ALEN; + arp->arp_pln = 4; + arp->arp_op = htons(ARPOP_REQUEST); + memcpy(arp->arp_sha, nic->mac_addr, ETH_ALEN); + memset(arp->arp_tha, 0, ETH_ALEN); + + /* Copy the IP address's into the ARP response */ + memcpy(arp->arp_spa, nic_iface->ustack.hostaddr, 4); + memcpy(arp->arp_tpa, &dst_ip, 4); + + (*nic->nic_library->ops->start_xmit) (nic, pkt_size, + (nic_iface->vlan_priority << 12) | + nic_iface->vlan_id); + + memcpy(&addr.s_addr, &dst_ip, sizeof(addr.s_addr)); + LOG_DEBUG(PFX "%s: Sent cnic arp request for IP: %s", + nic->log_name, addr_str); + pthread_mutex_unlock(&nic->xmit_mutex); + + return 0; +} + +static int cnic_neigh_soliciation_send(nic_t *nic, + nic_interface_t *nic_iface, int fd, + __u8 *mac_addr, + struct in6_addr *addr6_dst, + char *addr_str) +{ + struct ether_header *eth; + struct ip6_hdr *ipv6_hdr; + int rc, pkt_size; + char buf[INET6_ADDRSTRLEN]; + struct ndpc_reqptr req_ptr; + + rc = pthread_mutex_trylock(&nic->xmit_mutex); + if (rc != 0) { + LOG_WARN(PFX "%s: could not get xmit_mutex", nic->log_name); + return -EAGAIN; + } + + /* Build the ethernet header */ + eth = (*nic->ops->get_tx_pkt) (nic); + if (eth == NULL) { + LOG_WARN(PFX "%s: couldn't get tx packet", nic->log_name); + return -EAGAIN; + } + + /* Copy the requested target address to the ipv6.dst */ + ipv6_hdr = + (struct ip6_hdr *)((u8_t *) eth + sizeof(struct ether_header)); + + memcpy(ipv6_hdr->ip6_dst.s6_addr, addr6_dst->s6_addr, + sizeof(struct in6_addr)); + + nic_fill_ethernet_header(nic_iface, eth, nic->mac_addr, nic->mac_addr, + &pkt_size, (void *)&ipv6_hdr, ETHERTYPE_IPV6); + req_ptr.eth = (void *)eth; + req_ptr.ipv6 = (void *)ipv6_hdr; + if (ndpc_request(&nic_iface->ustack, &req_ptr, &pkt_size, + NEIGHBOR_SOLICIT)) + return -EAGAIN; + + /* Debug to print out the pkt context */ + inet_ntop(AF_INET6, ipv6_hdr->ip6_dst.s6_addr, buf, sizeof(buf)); + LOG_DEBUG(PFX "%s: ipv6 dst addr: %s", nic->log_name, buf); + LOG_DEBUG(PFX "neighbor sol content " + "dst mac %02x:%02x:%02x:%02x:%02x:%02x", + eth->ether_dhost[0], eth->ether_dhost[1], + eth->ether_dhost[2], eth->ether_dhost[3], + eth->ether_dhost[4], eth->ether_dhost[5]); + LOG_DEBUG(PFX "src mac %02x:%02x:%02x:%02x:%02x:%02x", + eth->ether_shost[0], eth->ether_shost[1], + eth->ether_shost[2], eth->ether_shost[3], + eth->ether_shost[4], eth->ether_shost[5]); + (*nic->nic_library->ops->start_xmit) (nic, pkt_size, + (nic_iface->vlan_priority << 12) | + nic_iface->vlan_id); + + LOG_DEBUG(PFX "%s: Sent cnic ICMPv6 neighbor request %s", + nic->log_name, addr_str); + + pthread_mutex_unlock(&nic->xmit_mutex); + + return 0; +} + +static int cnic_nl_neigh_rsp(nic_t *nic, int fd, + struct iscsi_uevent *ev, + struct iscsi_path *path_req, + __u8 *mac_addr, + nic_interface_t *nic_iface, int status, int type) +{ + int rc; + uint8_t *ret_buf; + struct iscsi_uevent *ret_ev; + struct iscsi_path *path_rsp; + struct sockaddr_nl dest_addr; + char addr_dst_str[INET6_ADDRSTRLEN]; + + memset(&dest_addr, 0, sizeof(dest_addr)); + dest_addr.nl_family = AF_NETLINK; + dest_addr.nl_pid = 0; + dest_addr.nl_groups = 0; /* unicast */ + + ret_buf = calloc(1, NLMSG_SPACE(sizeof(struct iscsi_uevent) + 256)); + if (ret_buf == NULL) { + LOG_ERR(PFX "Could not allocate memory for path req resposne"); + return -ENOMEM; + } + + memset(ret_buf, 0, NLMSG_SPACE(sizeof(struct iscsi_uevent) + 256)); + + /* prepare the iscsi_uevent buffer */ + ret_ev = (struct iscsi_uevent *)ret_buf; + ret_ev->type = ISCSI_UEVENT_PATH_UPDATE; + ret_ev->transport_handle = ev->transport_handle; + ret_ev->u.set_path.host_no = ev->r.req_path.host_no; + + /* Prepare the iscsi_path buffer */ + path_rsp = (struct iscsi_path *)(ret_buf + sizeof(*ret_ev)); + path_rsp->handle = path_req->handle; + if (type == AF_INET) { + path_rsp->ip_addr_len = 4; + memcpy(&path_rsp->src.v4_addr, nic_iface->ustack.hostaddr, + sizeof(nic_iface->ustack.hostaddr)); + + inet_ntop(AF_INET, &path_rsp->src.v4_addr, + addr_dst_str, sizeof(addr_dst_str)); + } else { + u8_t *src_ipv6; + int ret; + + /* Depending on the IPv6 address of the target we will need to + * determine whether we use the assigned IPv6 address or the + * link local IPv6 address */ + if (ndpc_request(&nic_iface->ustack, &path_req->dst.v6_addr, + &ret, CHECK_LINK_LOCAL_ADDR)) { + src_ipv6 = (u8_t *)all_zeroes_addr6; + LOG_DEBUG(PFX "RSP Check LL failed"); + goto src_done; + } + if (ret) { + /* Get link local IPv6 address */ + src_ipv6 = (u8_t *)&nic_iface->ustack.linklocal6; + } else { + if (ndpc_request(&nic_iface->ustack, + &path_req->dst.v6_addr, + &src_ipv6, GET_HOST_ADDR)) { + src_ipv6 = (u8_t *)all_zeroes_addr6; + LOG_DEBUG(PFX "RSP Get host addr failed"); + } + if (src_ipv6 == NULL) { + src_ipv6 = (u8_t *)all_zeroes_addr6; + LOG_DEBUG(PFX "RSP no Best matched addr found"); + } + } +src_done: + path_rsp->ip_addr_len = 16; + memcpy(&path_rsp->src.v6_addr, src_ipv6, + sizeof(nic_iface->ustack.hostaddr6)); + + inet_ntop(AF_INET6, &path_rsp->src.v6_addr, + addr_dst_str, sizeof(addr_dst_str)); + } + memcpy(path_rsp->mac_addr, mac_addr, 6); + path_rsp->vlan_id = (nic_iface->vlan_priority << 12) | + nic_iface->vlan_id; + path_rsp->pmtu = nic_iface->mtu ? nic_iface->mtu : path_req->pmtu; + + rc = __kipc_call(fd, ret_ev, sizeof(*ret_ev) + sizeof(*path_rsp)); + if (rc > 0) { + LOG_DEBUG(PFX "neighbor reply sent back to kernel " + "%s at %02x:%02x:%02x:%02x:%02x:%02x with vlan %d", + addr_dst_str, + mac_addr[0], mac_addr[1], + mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5], + nic_iface->vlan_id); + + } else { + LOG_ERR(PFX "send neighbor reply failed: %d", rc); + } + + free(ret_buf); + + return rc; +} + +static const struct timeval tp_wait = { + .tv_sec = 0, + .tv_usec = 250000, +}; + +/** + * cnic_handle_ipv4_iscsi_path_req() - This function will handle the IPv4 + * path req calls the bnx2i kernel module + * @param nic - The nic the message is directed towards + * @param fd - The file descriptor to be used to extract the private data + * @param ev - The iscsi_uevent + * @param buf - The private message buffer + */ +int cnic_handle_ipv4_iscsi_path_req(nic_t *nic, int fd, + struct iscsi_uevent *ev, + struct iscsi_path *path, + nic_interface_t *nic_iface) +{ + struct in_addr src_addr, dst_addr, + src_matching_addr, dst_matching_addr, netmask; + __u8 mac_addr[6]; + int rc; + uint16_t arp_retry; + int status = 0; +#define MAX_ARP_RETRY 4 + + memset(mac_addr, 0, sizeof(mac_addr)); + memcpy(&dst_addr, &path->dst.v4_addr, sizeof(dst_addr)); + memcpy(&src_addr, nic_iface->ustack.hostaddr, sizeof(src_addr)); + + if (nic_iface->ustack.netmask[0] | nic_iface->ustack.netmask[1]) + memcpy(&netmask.s_addr, nic_iface->ustack.netmask, + sizeof(src_addr)); + else + netmask.s_addr = calculate_default_netmask(dst_addr.s_addr); + + src_matching_addr.s_addr = src_addr.s_addr & netmask.s_addr; + dst_matching_addr.s_addr = dst_addr.s_addr & netmask.s_addr; + + LOG_DEBUG(PFX "%s: src=%s", nic->log_name, inet_ntoa(src_addr)); + LOG_DEBUG(PFX "%s: dst=%s", nic->log_name, inet_ntoa(dst_addr)); + LOG_DEBUG(PFX "%s: nm=%s", nic->log_name, inet_ntoa(netmask)); + if (src_matching_addr.s_addr != dst_matching_addr.s_addr) { + /* If there is an assigned gateway address then use it + * if the source address doesn't match */ + if (nic_iface->ustack.default_route_addr[0] | + nic_iface->ustack.default_route_addr[1]) { + memcpy(&dst_addr, + &nic_iface->ustack.default_route_addr, + sizeof(dst_addr)); + } else { + LOG_DEBUG(PFX "%s: no default route address", + nic->log_name); + } + } + arp_retry = 0; + + rc = uip_lookup_arp_entry(dst_addr.s_addr, mac_addr); + if (rc != 0) { + while ((arp_retry < MAX_ARP_RETRY) && (event_loop_stop == 0)) { + char *dst_addr_str; + int count; + struct timespec ts; + struct timeval tp; + struct timeval tp_abs; + + dst_addr_str = inet_ntoa(dst_addr); + + LOG_INFO(PFX "%s: Didn't find IPv4: '%s' in ARP table", + nic->log_name, dst_addr_str); + rc = cnic_arp_send(nic, nic_iface, fd, + mac_addr, + dst_addr.s_addr, dst_addr_str); + if (rc != 0) { + status = -EIO; + goto done; + } + + for (count = 0; count < 8; count++) { + /* Convert from timeval to timespec */ + rc = gettimeofday(&tp, NULL); + + timeradd(&tp, &tp_wait, &tp_abs); + + ts.tv_sec = tp_abs.tv_sec; + ts.tv_nsec = tp_abs.tv_usec * 1000; + + /* Wait 1s for if_down */ + pthread_mutex_lock(&nic->nl_process_mutex); + rc = pthread_cond_timedwait + (&nic->nl_process_if_down_cond, + &nic->nl_process_mutex, &ts); + + if (rc == ETIMEDOUT) { + pthread_mutex_unlock + (&nic->nl_process_mutex); + + rc = uip_lookup_arp_entry(dst_addr. + s_addr, + mac_addr); + if (rc == 0) + goto done; + } else { + nic->nl_process_if_down = 0; + pthread_mutex_unlock + (&nic->nl_process_mutex); + + arp_retry = MAX_ARP_RETRY; + goto done; + + } + } + + arp_retry++; + } + } + +done: + + if (arp_retry >= MAX_ARP_RETRY) { + status = -EIO; + rc = -EIO; + } + + if (ev) { + cnic_nl_neigh_rsp(nic, fd, ev, path, mac_addr, + nic_iface, status, AF_INET); + } + + return rc; +} + +/** + * cnic_handle_ipv6_iscsi_path_req() - This function will handle the IPv4 + * path req calls the bnx2i kernel module + * @param nic - The nic the message is directed towards + * @param fd - The file descriptor to be used to extract the private data + * @param ev - The iscsi_uevent + * @param buf - The private message buffer + */ +int cnic_handle_ipv6_iscsi_path_req(nic_t *nic, int fd, + struct iscsi_uevent *ev, + struct iscsi_path *path, + nic_interface_t *nic_iface) +{ + __u8 mac_addr[6]; + int rc, i; + uint16_t neighbor_retry; + int status = 0; + char addr_dst_str[INET6_ADDRSTRLEN]; + struct in6_addr src_addr, dst_addr, + src_matching_addr, dst_matching_addr, netmask; + struct in6_addr *addr; + struct ndpc_reqptr req_ptr; + + memset(mac_addr, 0, sizeof(mac_addr)); + + inet_ntop(AF_INET6, &path->dst.v6_addr, + addr_dst_str, sizeof(addr_dst_str)); + + /* Depending on the IPv6 address of the target we will need to + * determine whether we use the assigned IPv6 address or the + * link local IPv6 address */ + memcpy(&dst_addr, &path->dst.v6_addr, sizeof(struct in6_addr)); + if (ndpc_request(&nic_iface->ustack, &dst_addr, + &rc, CHECK_LINK_LOCAL_ADDR)) { + neighbor_retry = MAX_ARP_RETRY; + LOG_DEBUG(PFX "Check LL failed"); + goto done; + } + if (rc) { + LOG_DEBUG(PFX "Use LL"); + /* Get link local IPv6 address */ + addr = (struct in6_addr *)&nic_iface->ustack.linklocal6; + } else { + LOG_DEBUG(PFX "Use Best matched"); + if (ndpc_request(&nic_iface->ustack, + &dst_addr, + &addr, GET_HOST_ADDR)) { + neighbor_retry = MAX_ARP_RETRY; + LOG_DEBUG(PFX "Use Best matched failed"); + goto done; + } + if (addr == NULL) { + neighbor_retry = MAX_ARP_RETRY; + LOG_DEBUG(PFX "No Best matched found"); + goto done; + } + } + /* Got the best matched src IP address */ + memcpy(&src_addr, addr, sizeof(struct in6_addr)); + + if (nic_iface->ustack.netmask6[0] | nic_iface->ustack.netmask6[1] | + nic_iface->ustack.netmask6[2] | nic_iface->ustack.netmask6[3] | + nic_iface->ustack.netmask6[4] | nic_iface->ustack.netmask6[5] | + nic_iface->ustack.netmask6[6] | nic_iface->ustack.netmask6[7]) + memcpy(&netmask.s6_addr, nic_iface->ustack.netmask6, + sizeof(struct in6_addr)); + else + memcpy(&netmask.s6_addr, all_zeroes_addr6, + sizeof(struct in6_addr)); + + inet_ntop(AF_INET6, &src_addr.s6_addr16, addr_dst_str, + sizeof(addr_dst_str)); + LOG_DEBUG(PFX "src IP addr %s", addr_dst_str); + inet_ntop(AF_INET6, &dst_addr.s6_addr16, addr_dst_str, + sizeof(addr_dst_str)); + LOG_DEBUG(PFX "dst IP addr %s", addr_dst_str); + inet_ntop(AF_INET6, &netmask.s6_addr16, addr_dst_str, + sizeof(addr_dst_str)); + LOG_DEBUG(PFX "prefix mask %s", addr_dst_str); + + for (i = 0; i < 4; i++) { + src_matching_addr.s6_addr32[i] = src_addr.s6_addr32[i] & + netmask.s6_addr32[i]; + dst_matching_addr.s6_addr32[i] = dst_addr.s6_addr32[i] & + netmask.s6_addr32[i]; + if (src_matching_addr.s6_addr32[i] != + dst_matching_addr.s6_addr32[i]) { + /* No match with the prefix mask, use default route */ + if (memcmp(nic_iface->ustack.default_route_addr6, + all_zeroes_addr6, sizeof(*addr))) { + memcpy(&dst_addr, + nic_iface->ustack.default_route_addr6, + sizeof(dst_addr)); + inet_ntop(AF_INET6, &dst_addr.s6_addr16, + addr_dst_str, sizeof(addr_dst_str)); + LOG_DEBUG(PFX "Use default router IP addr %s", + addr_dst_str); + break; + } else { + neighbor_retry = MAX_ARP_RETRY; + goto done; + } + } + } + +#define MAX_ARP_RETRY 4 + neighbor_retry = 0; + + req_ptr.eth = (void *)mac_addr; + req_ptr.ipv6 = (void *)&dst_addr; + if (ndpc_request(&nic_iface->ustack, &req_ptr, &rc, CHECK_ARP_TABLE)) { + /* ndpc request failed, skip neighbor solicit send */ + neighbor_retry = MAX_ARP_RETRY; + goto done; + } + if (!rc) { + inet_ntop(AF_INET6, &dst_addr.s6_addr16, + addr_dst_str, sizeof(addr_dst_str)); + LOG_DEBUG(PFX + "%s: Preparing to send IPv6 neighbor solicitation " + "to dst: '%s'", nic->log_name, addr_dst_str); + while ((neighbor_retry < MAX_ARP_RETRY) + && (event_loop_stop == 0)) { + int count; + struct timespec ts; + struct timeval tp; + struct timeval tp_abs; + + LOG_INFO(PFX "%s: Didn't find IPv6: '%s'\n", + nic->log_name, addr_dst_str); + + rc = cnic_neigh_soliciation_send(nic, nic_iface, fd, + mac_addr, + &dst_addr, + addr_dst_str); + if (rc != 0) { + status = -EIO; + goto done; + } + + for (count = 0; count < 8; count++) { + /* Convert from timeval to timespec */ + rc = gettimeofday(&tp, NULL); + + timeradd(&tp, &tp_wait, &tp_abs); + + ts.tv_sec = tp_abs.tv_sec; + ts.tv_nsec = tp_abs.tv_usec * 1000; + + pthread_mutex_lock(&nic->nl_process_mutex); + rc = pthread_cond_timedwait + (&nic->nl_process_if_down_cond, + &nic->nl_process_mutex, &ts); + + if (rc == ETIMEDOUT) { + pthread_mutex_unlock + (&nic->nl_process_mutex); + + req_ptr.eth = (void *)mac_addr; + req_ptr.ipv6 = (void *)&dst_addr; + if (ndpc_request + (&nic_iface->ustack, &req_ptr, &rc, + CHECK_ARP_TABLE)) { + /* ndpc request failed, + force retry */ + rc = 0; + } + if (rc) + goto done; + } else { + nic->nl_process_if_down = 0; + pthread_mutex_unlock + (&nic->nl_process_mutex); + + neighbor_retry = MAX_ARP_RETRY; + goto done; + } + } + neighbor_retry++; + } + } + +done: + if (neighbor_retry >= MAX_ARP_RETRY) { + status = -EIO; + rc = -EIO; + } + + if (ev) { + cnic_nl_neigh_rsp(nic, fd, ev, path, mac_addr, + nic_iface, status, AF_INET6); + } + return rc; +} + +/** + * cnic_handle_iscsi_path_req() - This function will handle the path req calls + * the bnx2i kernel module + * @param nic - The nic the message is directed towards + * @param fd - The file descriptor to be used to extract the private data + * @param ev - The iscsi_uevent + * @param path - The private message buffer + * @param nic_iface - The nic_iface to use for this connection request + */ +int cnic_handle_iscsi_path_req(nic_t *nic, int fd, struct iscsi_uevent *ev, + struct iscsi_path *path, + nic_interface_t *nic_iface) +{ + + LOG_DEBUG(PFX "%s: Netlink message with VLAN ID: %d, path MTU: %d " + "minor: %d ip_addr_len: %d", + nic->log_name, path->vlan_id, path->pmtu, 0 /* TODO FIX */ , + path->ip_addr_len); + + if (path->ip_addr_len == 4) + return cnic_handle_ipv4_iscsi_path_req(nic, fd, ev, path, + nic_iface); + else if (path->ip_addr_len == 16) + return cnic_handle_ipv6_iscsi_path_req(nic, fd, ev, path, + nic_iface); + else { + LOG_DEBUG(PFX "%s: unknown ip_addr_len: %d size dropping ", + nic->log_name, path->ip_addr_len); + return -EIO; + } +} diff --git a/iscsiuio/src/unix/libs/cnic.h b/iscsiuio/src/unix/libs/cnic.h new file mode 100644 index 0000000..c86595c --- /dev/null +++ b/iscsiuio/src/unix/libs/cnic.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * cnic.h - CNIC UIO uIP user space stack + * + */ +#ifndef __CNIC_NL_H__ +#define __CNIC_NL_H__ + +/******************************************************************************* + * Constants shared between the bnx2 and bnx2x modules + ******************************************************************************/ +extern const char bnx2i_library_transport_name[]; +extern const size_t bnx2i_library_transport_name_size; +extern const char qedi_library_transport_name[]; +extern const size_t qedi_library_transport_name_size; + +int cnic_nl_open(); +void cnic_nl_close(); + +int cnic_handle_iscsi_path_req(nic_t *nic, int, struct iscsi_uevent *, + struct iscsi_path *path, + nic_interface_t *nic_iface); + +#endif /* __CNIC_NL_H__ */ diff --git a/iscsiuio/src/unix/libs/qedi.c b/iscsiuio/src/unix/libs/qedi.c new file mode 100644 index 0000000..3414cb5 --- /dev/null +++ b/iscsiuio/src/unix/libs/qedi.c @@ -0,0 +1,1188 @@ +/* + * Copyright (c) 2016, Cavium Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * qedi.c - qedi user space driver + * This file handles different qedi NIC operations, + * qedi_open - initializes all hardware resources under NIC device + * qedi_close - closes the NIC device + * qedi_read - reads data to the hardware + * qedi_write - writes data to the hardware + * qedi_start_xmit - sends a pkt of data on NIC device + * qedi_get_tx_pkt - gets a Tx pkt from NIC + * qedi_clear_tx_intr - clears the Tx interrupt + * NOTE: nic_t is used as NIC device, + * qedi is not attached to netdev hence it is not mandatory + * for netdev to be upd + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include "build_date.h" +#include "bnx2x.h" +#include "qedi.h" +#include "cnic.h" +#include "logger.h" +#include "nic.h" +#include "nic_id.h" +#include "nic_utils.h" +#include "options.h" + +#define PFX "qedi " + +extern int nl_sock; + +static pthread_mutex_t host_mutex = PTHREAD_MUTEX_INITIALIZER; + +/* Foward struct declarations */ +struct nic_ops qedi_op; + +/******************************************************************************* + * NIC Library Strings + ******************************************************************************/ +static const char library_name[] = "qedi"; +static const char library_version[] = PACKAGE_VERSION; +static const char library_uio_name[] = "qedi_uio"; + +/* The name that should be returned from /sys/class/uio/uio0/name */ +static const char cnic_uio_sysfs_name_tempate[] = "/sys/class/uio/uio%i/name"; +static const char qedi_uio_sysfs_name[] = "qedi_uio"; +static const char qedi_host_mac_template[] = + "/sys/class/iscsi_host/host%i/hwaddress"; + +struct qedi_driver_version qedi_version = { + QEDI_UNKNOWN_MAJOR_VERSION, + QEDI_UNKNOWN_MINOR_VERSION, + QEDI_UNKNOWN_SUB_MINOR_VERSION, +}; + +static int qedi_clear_tx_intr(nic_t *nic); + +/******************************************************************************* + * QEDI Library Functions + ******************************************************************************/ +/** + * qedi_get_library_name() - Used to get the name of this NIC library + * @param name - This function will return the pointer to this NIC + * library name + * @param name_size + */ +static void qedi_get_library_name(char **name, size_t *name_size) +{ + *name = (char *)library_name; + *name_size = sizeof(library_name); +} + +/** + * qedi_get_library_version() - Used to get the version string of this + * NIC library + * @param version - This function will return the pointer to this NIC + * library version string + * @param version_size - This will be set with the version size + */ +static void qedi_get_library_version(char **version, size_t *version_size) +{ + *version = (char *)library_version; + *version_size = sizeof(library_version); +} + +/** + * qedi_get_build_date() - Used to get the build date string of this library + * @param version - This function will return the pointer to this NIC + * library build date string + * @param version_size - This will be set with the build date string size + */ +static void qedi_get_build_date(char **build, size_t *build_size) +{ + *build = (char *)build_date; + *build_size = sizeof(build_date); +} + +/** + * qedi_get_transport_name() - Used to get the transport name associated + * with this this NIC library + * @param transport_name - This function will return the pointer to this NIC + * library's associated transport string + * @param transport_name_size - This will be set with the transport name size + */ +static void qedi_get_transport_name(char **transport_name, + size_t *transport_name_size) +{ + *transport_name = (char *)qedi_library_transport_name; + *transport_name_size = qedi_library_transport_name_size; +} + +/** + * qedi_get_uio_name() - Used to get the uio name associated with this this + * NIC library + * @param uio_name - This function will return the pointer to this NIC + * library's associated uio string + * @param transport_name_size - This will be set with the uio name size + */ +static void qedi_get_uio_name(char **uio_name, size_t *uio_name_size) +{ + *uio_name = (char *)library_uio_name; + *uio_name_size = sizeof(library_uio_name); +} + +/** + * qedi_get_ops() - Used to get the NIC library op table + * @param op - The op table of this NIC library + */ +struct nic_ops *qedi_get_ops() +{ + return &qedi_op; +} + +/******************************************************************************* + * qedi Utility Functions + ******************************************************************************/ +/******************************************************************************* + * Utility Functions Used to read register from the qedi device + ******************************************************************************/ +static void qedi_set_drv_version_unknown(qedi_t *bp) +{ + bp->version.major = QEDI_UNKNOWN_MAJOR_VERSION; + bp->version.minor = QEDI_UNKNOWN_MINOR_VERSION; + bp->version.sub_minor = QEDI_UNKNOWN_SUB_MINOR_VERSION; +} + +/* Return: 1 = Unknown, 0 = Known */ +static int qedi_is_drv_version_unknown(struct qedi_driver_version *version) +{ + if ((version->major == (uint16_t)QEDI_UNKNOWN_MAJOR_VERSION) && + (version->minor == (uint16_t)QEDI_UNKNOWN_MINOR_VERSION) && + (version->sub_minor == (uint16_t)QEDI_UNKNOWN_SUB_MINOR_VERSION)) { + return 1; + } + + return 0; +} + +/** + * qedi_get_drv_version() - Used to determine the driver version + * @param bp - Device used to determine qedi driver version + */ +static int qedi_get_drv_version(qedi_t *bp) +{ + nic_t *nic = bp->parent; + + /* + * CAPABILITIES: Get the iscsi driver version from qedi + * This may be obtained from sysfs + */ + LOG_INFO(PFX "%s: qedi driver using version %d.%d.%d", + nic->log_name, + bp->version.major, bp->version.minor, bp->version.sub_minor); + + return 0; +} + +/******************************************************************************/ + +/** + * qedi_get_chip_id() - Used to retrieve the chip ID from the nic + * @param dev - Device used to determin NIC type + * @return Chip ID read from the MISC ID register + */ +static int qedi_get_chip_id(qedi_t *bp) +{ + /* int val, id; */ + + /* Get the chip revision id and number. */ + /* chip num:16-31, rev:12-15, metal:4-11, bond_id:0-3 */ + /* + * CAPABILITIES: Get the CHIP info from qedi through sysfs or uio struct. + */ + return 0; +} + +/** + * qedi_uio_verify() + * + */ +static int qedi_uio_verify(nic_t *nic) +{ + char *raw = NULL, *raw_tmp; + uint32_t raw_size = 0; + char temp_path[sizeof(cnic_uio_sysfs_name_tempate) + 8]; + int rc = 0; + + /* Build the path to determine uio name */ + snprintf(temp_path, sizeof(temp_path), + cnic_uio_sysfs_name_tempate, nic->uio_minor); + + rc = capture_file(&raw, &raw_size, temp_path); + if (rc != 0) + goto error; + + /* sanitize name string by replacing newline with null termination */ + raw_tmp = raw; + while (*raw_tmp != '\n') + raw_tmp++; + *raw_tmp = '\0'; + + if (strncmp(raw, qedi_uio_sysfs_name, + sizeof(qedi_uio_sysfs_name)) != 0) { + LOG_ERR(PFX "%s: uio names not equal: expecting %s got %s from %s", + nic->log_name, qedi_uio_sysfs_name, raw, temp_path); + rc = -EIO; + } + + free(raw); + + LOG_INFO(PFX "%s: Verified is a qedi_uio device", nic->log_name); + +error: + return rc; +} + +static int qedi_get_mac_addr(qedi_t *bp) +{ + nic_t *nic = bp->parent; + char *raw = NULL, *raw_tmp; + uint32_t raw_size = 0; + char temp_path[sizeof(qedi_host_mac_template) + 8]; + int rc = 0; + + /* Build the path to determine mac address */ + snprintf(temp_path, sizeof(temp_path), + qedi_host_mac_template, nic->host_no); + + rc = capture_file(&raw, &raw_size, temp_path); + if (rc != 0) + goto error; + + /* sanitize name string by replacing newline with null termination */ + raw_tmp = raw; + while (*raw_tmp != '\n') + raw_tmp++; + *raw_tmp = '\0'; + + rc = sscanf(raw, "%02x:%02x:%02x:%02x:%02x:%02x", + (uint32_t *)&nic->mac_addr[0], (uint32_t *)&nic->mac_addr[1], + (uint32_t *)&nic->mac_addr[2], (uint32_t *)&nic->mac_addr[3], + (uint32_t *)&nic->mac_addr[4], (uint32_t *)&nic->mac_addr[5]); + if (rc != 1) { + LOG_WARN(PFX "%s: Could not parse mac_addr", + nic->log_name); + rc = -ENODEV; + goto error; + } + +error: + if (raw) + free(raw); + return rc; +} + +/******************************************************************************* + * qedi Utility Functions to get to the hardware consumer indexes + ******************************************************************************/ + +static __u32 qedi_get_rx(qedi_t *bp) +{ + return ((struct qedi_uio_ctrl *)bp->uctrl_map)->host_rx_cons; +} + +static __u32 qedi_get_tx(qedi_t *bp) +{ + return ((struct qedi_uio_ctrl *)bp->uctrl_map)->hw_tx_cons; +} + +/** + * qedi_free() - Used to free a qedi structure + */ +static void qedi_free(nic_t *nic) +{ + if (nic->priv) + free(nic->priv); + nic->priv = NULL; +} + +/** + * qedi_alloc() - Used to allocate a qedi structure + */ +static qedi_t *qedi_alloc(nic_t *nic) +{ + qedi_t *bp = malloc(sizeof(*bp)); + + if (!bp) { + LOG_ERR(PFX "%s: Could not allocate QEDI space", + nic->log_name); + return NULL; + } + + /* Clear out the CNIC contents */ + memset(bp, 0, sizeof(*bp)); + + bp->parent = nic; + nic->priv = (void *)bp; + get_iscsi_transport_handle(nic, &nic->transport_handle); + qedi_set_drv_version_unknown(bp); + + return bp; +} + +int uio_get_map_offset(nic_t *nic, uint8_t map, uint32_t *offset) +{ + char *raw = NULL; + uint32_t raw_size = 0; + ssize_t elements_read; + char temp_path[sizeof(UIO_OFFSET_TMPL) + 8]; + int rc = 0; + + /* Capture RX buffer size */ + snprintf(temp_path, sizeof(temp_path), + UIO_OFFSET_TMPL, nic->uio_minor, map); + + rc = capture_file(&raw, &raw_size, temp_path); + if (rc != 0) + goto error; + + elements_read = sscanf(raw, "0x%x", offset); + if (elements_read != 1) { + LOG_ERR(PFX "%s: Couldn't get the offset from %s", + nic->log_name, temp_path); + rc = -EIO; + goto error; + } + + rc = 0; +error: + if (raw) + free(raw); + + return rc; +} + +int uio_get_map_info(nic_t *nic, uint8_t map, char *attr, uint32_t *val) +{ + char *raw = NULL; + uint32_t raw_size = 0; + ssize_t elements_read; + char temp_path[sizeof(UIO_ATTR_TMPL) + 8]; + int rc = 0; + + /* Capture RX buffer size */ + snprintf(temp_path, sizeof(temp_path), + UIO_ATTR_TMPL, nic->uio_minor, map, attr); + + rc = capture_file(&raw, &raw_size, temp_path); + if (rc != 0) + goto error; + + elements_read = sscanf(raw, "0x%x", val); + if (elements_read != 1) { + LOG_ERR(PFX "%s: Couldn't get the offset from %s", + nic->log_name, temp_path); + rc = -EIO; + goto error; + } + + rc = 0; +error: + if (raw) + free(raw); + + return rc; +} + +/** + * qedi_open() - This will initialize all the hardware resources underneath + * a struct cnic_uio device + * @param dev - The struct cnic_uio device to attach the hardware with + * @return 0 on success, on failure a errno will be returned + */ +static int qedi_open(nic_t *nic) +{ + qedi_t *bp = NULL; + struct stat uio_stat; + int i, rc; + size_t count; + uint32_t bus; + uint32_t slot; + uint32_t func; + uint32_t offset; + + /* Sanity Check: validate the parameters */ + if (!nic) { + LOG_ERR(PFX "nic == NULL"); + return -EINVAL; + } + + if ((nic->priv) != NULL && + (((qedi_t *)(nic->priv))->flags & QEDI_OPENED)) { + return 0; + } + + if (nic->host_no == INVALID_HOST_NO) { + rc = sscanf(nic->config_device_name, "host%d", &nic->host_no); + if (rc != 1) { + LOG_WARN(PFX "%s: Could not parse for host number", + nic->config_device_name); + rc = -ENODEV; + goto open_error; + } + } + + bp = qedi_alloc(nic); + if (!bp) + return -ENOMEM; + + if (qedi_is_drv_version_unknown(&qedi_version)) { + /* If version is unknown, go read from ethtool */ + rc = qedi_get_drv_version(bp); + if (rc) + goto open_error; + } else { + /* Version is not unknown, just use it */ + qedi_version.major = bp->version.major; + qedi_version.minor = bp->version.minor; + qedi_version.sub_minor = bp->version.sub_minor; + } + + count = 0; + while ((nic->fd < 0) && count < 15) { + /* udev might not have created the file yet */ + pthread_mutex_unlock(&nic->nic_mutex); + sleep(1); + pthread_mutex_lock(&nic->nic_mutex); + + nic->fd = open(nic->uio_device_name, O_RDWR | O_NONBLOCK); + if (nic->fd != INVALID_FD) { + LOG_ERR(PFX "%s: uio device has been brought up via pid: %d on fd: %d", + nic->uio_device_name, getpid(), nic->fd); + + rc = qedi_uio_verify(nic); + if (rc != 0) + continue; + + break; + } else { + LOG_WARN(PFX "%s: Could not open device: %s, [%s]", + nic->log_name, nic->uio_device_name, + strerror(errno)); + + manually_trigger_uio_event(nic, nic->uio_minor); + + /* udev might not have created the file yet */ + pthread_mutex_unlock(&nic->nic_mutex); + sleep(1); + pthread_mutex_lock(&nic->nic_mutex); + + count++; + } + } + if (fstat(nic->fd, &uio_stat) < 0) { + LOG_ERR(PFX "%s: Could not fstat device", nic->log_name); + rc = -ENODEV; + goto open_error; + } + nic->uio_minor = minor(uio_stat.st_rdev); + + /* + * CAPABILITIES: acquire the rx buffer size and rx ring size from qedi + */ + + bp->rx_ring_size = RX_RING_SIZE; + bp->rx_buffer_size = PKT_BUF_SIZE; + + LOG_DEBUG(PFX "%s: using rx ring size: %d, rx buffer size: %d", + nic->log_name, bp->rx_ring_size, bp->rx_buffer_size); + + /* Determine the number of UIO events that have already occurred */ + rc = detemine_initial_uio_events(nic, &nic->intr_count); + if (rc != 0) { + LOG_ERR(PFX "Could not get the no. of initial UIO events"); + nic->intr_count = 0; + } + + /* Allocate space for rx pkt ring */ + bp->rx_pkt_ring = malloc(sizeof(void *) * bp->rx_ring_size); + if (!bp->rx_pkt_ring) { + LOG_ERR(PFX "%s: Could not allocate space for rx_pkt_ring", + nic->log_name); + rc = errno; + goto open_error; + } + + /* + * Map the uio struct and packet buffer + */ + offset = 0; + rc = uio_get_map_info(nic, QEDI_UCTRL_MAP_REG, "size", &offset); + if (rc) { + LOG_INFO(PFX "Failed to get the map size rc=%d", rc); + goto open_error; + } + LOG_INFO(PFX "uctrl map size=%u", offset); + + offset = 0; + rc = uio_get_map_info(nic, QEDI_RING_MAP_REG, "size", &offset); + if (rc) { + LOG_INFO(PFX "Failed to get the map size rc=%d", rc); + goto open_error; + } + LOG_INFO(PFX "ring map size=%u", offset); + + offset = 0; + rc = uio_get_map_info(nic, QEDI_BUF_MAP_REG, "size", &offset); + if (rc) { + LOG_INFO(PFX "Failed to get the map size rc=%d", rc); + goto open_error; + } + LOG_INFO(PFX "buf map size=%u", offset); + + offset = 0; + rc = uio_get_map_offset(nic, QEDI_UCTRL_MAP_REG, &offset); + if (rc) { + LOG_INFO(PFX "Failed to get the map offset rc=%d", rc); + goto open_error; + } + + bp->uctrl_map = mmap(NULL, sizeof(struct qedi_uio_ctrl), + PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_LOCKED, + nic->fd, (off_t)0); + if (bp->uctrl_map == MAP_FAILED) { + LOG_INFO(PFX "%s: Could not mmap uio ctrl struct: %s", + nic->log_name, strerror(errno)); + bp->uctrl_map = NULL; + rc = errno; + goto open_error; + } + + bp->uctrl_map_offset = offset; + bp->uctrl_map += offset; + + bp->rx_comp_ring = mmap(NULL, nic->page_size, + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, + nic->fd, (off_t)nic->page_size); + if (bp->rx_comp_ring == MAP_FAILED) { + LOG_INFO(PFX "%s: Could not mmap rx_comp_ring: %s", + nic->log_name, strerror(errno)); + bp->rx_comp_ring = NULL; + rc = errno; + goto open_error; + } + + bp->bufs = mmap(NULL, (bp->rx_ring_size + 1) * bp->rx_buffer_size, + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, + nic->fd, (off_t)2 * nic->page_size); + if (bp->bufs == MAP_FAILED) { + LOG_INFO(PFX "%s: Could not mmap pkt buffers: %s", + nic->log_name, strerror(errno)); + bp->bufs = NULL; + rc = errno; + goto open_error; + } + + /* + * Get all CHIP related info from qedi + */ + bp->chip_id = qedi_get_chip_id(bp); + LOG_DEBUG(PFX "Chip ID: %x", bp->chip_id); + + rc = get_bus_slot_func_num(nic, &bus, &slot, &func); + if (rc != 0) { + LOG_INFO(PFX "%s: Couldn't determine bus:slot.func", + nic->log_name); + goto open_error; + } + + /* + * Get all function, pfid, client_id and cid info from qedi + */ + LOG_INFO(PFX "%s: func 0x%x, pfid 0x%x, client_id 0x%x, cid 0x%x", + nic->log_name, bp->func, bp->pfid, bp->client_id, bp->cid); + + bp->get_rx_cons = qedi_get_rx; + bp->get_tx_cons = qedi_get_tx; + bp->tx_cons = 0; + bp->tx_prod = 0; + bp->tx_bd_prod = 0; + bp->tx_pkt = bp->bufs; + bp->rx_pkts = bp->bufs + bp->rx_buffer_size; + + bp->rx_index = 0; + bp->rx_cons = 0; + bp->rx_bd_cons = 0; + bp->rx_prod = 127; + bp->rx_bd_prod = bp->rx_ring_size; + + for (i = 0; i < bp->rx_ring_size; i++) { + void *ptr = bp->bufs + (bp->rx_buffer_size * (i + 1)); + + bp->rx_pkt_ring[i] = ptr; + } + + qedi_get_mac_addr(bp); + LOG_INFO(PFX "%s: Using mac address: %02x:%02x:%02x:%02x:%02x:%02x", + nic->log_name, + nic->mac_addr[0], nic->mac_addr[1], nic->mac_addr[2], + nic->mac_addr[3], nic->mac_addr[4], nic->mac_addr[5]); + + qedi_get_library_name(&nic->library_name, &count); + LOG_INFO("%s: qedi initialized", nic->log_name); + + bp->flags |= QEDI_OPENED; + + return 0; + +open_error: + + if (bp->bufs) { + munmap(bp->bufs, (bp->rx_ring_size + 1) * bp->rx_buffer_size); + bp->bufs = NULL; + } + + if (bp->rx_comp_ring) { + munmap(bp->rx_comp_ring, nic->page_size); + bp->rx_comp_ring = NULL; + } + + if (bp->uctrl_map) { + bp->uctrl_map -= bp->uctrl_map_offset; + munmap(bp->uctrl_map, sizeof(struct qedi_uio_ctrl)); + bp->uctrl_map = NULL; + } + + if (bp->rx_pkt_ring) { + free(bp->rx_pkt_ring); + bp->rx_pkt_ring = NULL; + } + + if (nic->fd != INVALID_FD) { + close(nic->fd); + nic->fd = INVALID_FD; + } + + qedi_free(nic); + + return rc; +} + +/** + * qedi_uio_close_resources() - Used to free resource for the NIC/CNIC + * @param nic - NIC device to free resource + * @param graceful - whether to wait to close gracefully + * @return 0 on success, <0 on failure + */ +static int qedi_uio_close_resources(nic_t *nic, NIC_SHUTDOWN_T graceful) +{ + qedi_t *bp = (qedi_t *)nic->priv; + int rc = 0; + + /* Check if there is an assoicated qedi device */ + if (!bp) { + LOG_WARN(PFX "%s: when closing resources there is no assoicated qedi", + nic->log_name); + return -EIO; + } + + /* Clean up allocated memory */ + + if (bp->rx_pkt_ring) { + free(bp->rx_pkt_ring); + bp->rx_pkt_ring = NULL; + } + + /* Clean up mapped registers */ + if (bp->bufs) { + rc = munmap(bp->bufs, + (bp->rx_ring_size + 1) * bp->rx_buffer_size); + if (rc != 0) + LOG_ERR(PFX "%s: Couldn't unmap bufs", nic->log_name); + bp->bufs = NULL; + } + + if (bp->rx_comp_ring) { + rc = munmap(bp->rx_comp_ring, nic->page_size); + if (rc != 0) + LOG_ERR(PFX "%s: Couldn't unmap ring", nic->log_name); + bp->rx_comp_ring = NULL; + } + + if (bp->uctrl_map) { + bp->uctrl_map -= bp->uctrl_map_offset; + rc = munmap(bp->uctrl_map, sizeof(struct qedi_uio_ctrl)); + if (rc != 0) { + LOG_ERR(PFX "%s: Couldn't unmap uio ctrl", + nic->log_name); + } + bp->uctrl_map = NULL; + } + + if (nic->fd != INVALID_FD) { + rc = close(nic->fd); + if (rc != 0) { + LOG_ERR(PFX + "%s: Couldn't close uio file descriptor: %d", + nic->log_name, nic->fd); + } else { + LOG_DEBUG(PFX "%s: Closed uio file descriptor: %d", + nic->log_name, nic->fd); + } + + nic->fd = INVALID_FD; + } else { + LOG_ERR(PFX "%s: Invalid uio file descriptor: %d", + nic->log_name, nic->fd); + } + + qedi_set_drv_version_unknown(bp); + + LOG_INFO(PFX "%s: Closed all resources", nic->log_name); + + return 0; +} + +/** + * qedi_close() - Used to close the NIC device + * @param nic - NIC device to close + * @param graceful - whether to wait to close gracefully + * @return 0 if successful, <0 if there is an error + */ +static int qedi_close(nic_t *nic, NIC_SHUTDOWN_T graceful) +{ + /* Sanity Check: validate the parameters */ + if (!nic) { + LOG_ERR(PFX "%s: nic == NULL", __func__); + return -EINVAL; + } + if (!nic->priv) { + LOG_ERR(PFX "%s: nic->priv == NULL", __func__); + return -EINVAL; + } + + LOG_INFO(PFX "Closing NIC device: %s", nic->log_name); + + qedi_uio_close_resources(nic, graceful); + qedi_free(nic); + + return 0; +} + +static void qedi_prepare_xmit_packet(nic_t *nic, + nic_interface_t *nic_iface, + struct packet *pkt) +{ + qedi_t *bp = (qedi_t *)nic->priv; + struct uip_vlan_eth_hdr *eth_vlan = (struct uip_vlan_eth_hdr *)pkt->buf; + struct uip_eth_hdr *eth = (struct uip_eth_hdr *)bp->tx_pkt; + + LOG_DEBUG(PFX "%s: pkt->buf_size=%d tpid=0x%x", nic->log_name, + pkt->buf_size, eth_vlan->tpid); + + if (eth_vlan->tpid == htons(UIP_ETHTYPE_8021Q)) { + memcpy(bp->tx_pkt, pkt->buf, sizeof(struct uip_eth_hdr)); + eth->type = eth_vlan->type; + pkt->buf_size -= (sizeof(struct uip_vlan_eth_hdr) - + sizeof(struct uip_eth_hdr)); + + LOG_DEBUG(PFX "%s: pkt->buf_size=%d type=0x%x", nic->log_name, + pkt->buf_size, eth->type); + LOG_DEBUG(PFX "%s: pkt->buf_size - eth_hdr_size = %d", nic->log_name, + pkt->buf_size - sizeof(struct uip_eth_hdr)); + + memcpy(bp->tx_pkt + sizeof(struct uip_eth_hdr), + pkt->buf + sizeof(struct uip_vlan_eth_hdr), + pkt->buf_size - sizeof(struct uip_eth_hdr)); + } else { + LOG_DEBUG(PFX "%s: NO VLAN pkt->buf_size=%d", nic->log_name, + pkt->buf_size); + memcpy(bp->tx_pkt, pkt->buf, pkt->buf_size); + } + + msync(bp->tx_pkt, pkt->buf_size, MS_SYNC); +} + +/** + * qedi_get_tx_pkt() - This function is used to a TX packet from the NIC + * @param nic - The NIC device to send the packet + */ +void *qedi_get_tx_pkt(nic_t *nic) +{ + qedi_t *bp = (qedi_t *)nic->priv; + + return bp->tx_pkt; +} + +/** + * qedi_start_xmit() - This function is used to send a packet of data + * @param nic - The NIC device to send the packet + * @param len - the length of the TX packet + * + */ +void qedi_start_xmit(nic_t *nic, size_t len, u16_t vlan_id) +{ + qedi_t *bp = (qedi_t *)nic->priv; + uint8_t *ubuf; + struct iscsi_uevent *ev; + struct iscsi_path *path_data; + struct qedi_uio_ctrl *uctrl; + int rc = 0; + uint16_t buflen; + + uctrl = (struct qedi_uio_ctrl *)bp->uctrl_map; + + buflen = sizeof(struct iscsi_uevent) + sizeof(struct iscsi_path); + ubuf = calloc(1, NLMSG_SPACE(buflen)); + if (!ubuf) { + LOG_ERR(PFX "%s: alloc failed for uevent buf", __func__); + return; + } + + memset(ubuf, 0, NLMSG_SPACE(buflen)); + + /* prepare the iscsi_uevent buffer */ + ev = (struct iscsi_uevent *)ubuf; + ev->type = ISCSI_UEVENT_PATH_UPDATE; + ev->transport_handle = nic->transport_handle; + ev->u.set_path.host_no = nic->host_no; + + /* Prepare the iscsi_path buffer */ + path_data = (struct iscsi_path *)(ubuf + sizeof(struct iscsi_uevent)); + path_data->handle = QEDI_PATH_HANDLE; + path_data->vlan_id = vlan_id; + uctrl->host_tx_pkt_len = len; + LOG_DEBUG(PFX "%s: host_no:%d vlan_id=%d, tx_pkt_len=%d", + nic->log_name, ev->u.set_path.host_no, path_data->vlan_id, uctrl->host_tx_pkt_len); + + LOG_DEBUG(PFX "%s: ACQUIRE HOST MUTEX", nic->log_name); + pthread_mutex_lock(&host_mutex); + rc = __kipc_call(nl_sock, ev, buflen); + if (rc > 0) { + bp->tx_prod++; + uctrl->host_tx_prod++; + LOG_DEBUG(PFX "%s: bp->tx_prod: %d, uctrl->host_tx_prod=%d", + nic->log_name, bp->tx_prod, uctrl->host_tx_prod); + + msync(uctrl, sizeof(struct qedi_uio_ctrl), MS_SYNC); + LOG_PACKET(PFX "%s: sent %d bytes using bp->tx_prod: %d", + nic->log_name, len, bp->tx_prod); + } else { + LOG_ERR(PFX "Pkt transmission failed: %d", rc); + } + + LOG_DEBUG(PFX "%s: RELEASE HOST MUTEX", nic->log_name); + pthread_mutex_unlock(&host_mutex); + free(ubuf); +} + +/** + * qedi_write() - Used to write the data to the hardware + * @param nic - NIC hardware to read from + * @param pkt - The packet which will hold the data to be sent on the wire + * @return 0 if successful, <0 if failed + */ +int qedi_write(nic_t *nic, nic_interface_t *nic_iface, packet_t *pkt) +{ + qedi_t *bp; + struct uip_stack *uip; + int i = 0; + + /* Sanity Check: validate the parameters */ + if (!nic || !nic_iface || !pkt) { + LOG_ERR(PFX "%s: qedi_write() nic == 0x%p || nic_iface == 0x%p || pkt == 0x%x", + nic, nic_iface, pkt); + return -EINVAL; + } + bp = (qedi_t *)nic->priv; + uip = &nic_iface->ustack; + + if (pkt->buf_size == 0) { + LOG_ERR(PFX "%s: Trying to transmitted 0 sized packet", + nic->log_name); + return -EINVAL; + } + + /* Try to wait for a TX completion */ + for (i = 0; i < 15; i++) { + struct timespec sleep_req = {.tv_sec = 0, .tv_nsec = 5000000 }, + sleep_rem; + + LOG_DEBUG(PFX "%s: host:%d - calling clear_tx_intr from qedi_write", + nic->log_name, nic->host_no); + if (qedi_clear_tx_intr(nic) == 0) + break; + + nanosleep(&sleep_req, &sleep_rem); + } + + LOG_DEBUG(PFX "%s: host:%d - try getting xmit mutex", + nic->log_name, nic->host_no); + if (pthread_mutex_trylock(&nic->xmit_mutex) != 0) { + LOG_DEBUG(PFX "%s: Dropped previous transmitted packet", + nic->log_name); + return -EINVAL; + } + + qedi_prepare_xmit_packet(nic, nic_iface, pkt); + qedi_start_xmit(nic, pkt->buf_size, + (nic_iface->vlan_priority << 12) | + nic_iface->vlan_id); + + /* bump up the tx stats */ + nic->stats.tx.packets++; + nic->stats.tx.bytes += uip->uip_len; + + LOG_DEBUG(PFX "%s: transmitted %d bytes dev->tx_cons: %d, dev->tx_prod: %d, dev->tx_bd_prod:%d", + nic->log_name, pkt->buf_size, + bp->tx_cons, bp->tx_prod, bp->tx_bd_prod); + + LOG_DEBUG(PFX "%s: host:%d - releasing xmit mutex", + nic->log_name, nic->host_no); + pthread_mutex_unlock(&nic->xmit_mutex); + + return 0; +} + +/** + * qedi_read() - Used to read the data from the hardware + * @param nic - NIC hardware to read from + * @param pkt - The packet which will hold the data + * @return 0 if successful, < 0 if failed + */ +static int qedi_read(nic_t *nic, packet_t *pkt) +{ + qedi_t *bp; + void *rx_pkt; + int rc = 0; + uint32_t sw_cons, bd_cons; + uint32_t hw_prod, bd_prod; + uint32_t rx_pkt_idx; + int len; + struct qedi_rx_bd *rx_bd; + struct qedi_uio_ctrl *uctrl; + uint16_t vlan_id; + + /* Sanity Check: validate the parameters */ + if (!nic || !pkt) { + LOG_ERR(PFX "%s: qedi_read() nic == 0x%p || pkt == 0x%x", + nic, pkt); + return -EINVAL; + } + + bp = (qedi_t *)nic->priv; + msync(bp->uctrl_map, sizeof(struct qedi_uio_ctrl), MS_SYNC); + msync(bp->rx_comp_ring, nic->page_size, MS_SYNC); + uctrl = (struct qedi_uio_ctrl *)bp->uctrl_map; + hw_prod = uctrl->hw_rx_prod; + bd_prod = uctrl->hw_rx_bd_prod; + sw_cons = uctrl->host_rx_cons; + bd_cons = uctrl->host_rx_bd_cons; + rx_bd = bp->rx_comp_ring + (bd_prod * sizeof(*rx_bd)); + len = rx_bd->rx_pkt_len; + rx_pkt_idx = rx_bd->rx_pkt_index; + vlan_id = rx_bd->vlan_id; + + LOG_DEBUG(PFX "%s:hw_prod %d bd_prod %d, rx_pkt_idx %d, rxlen %d", + nic->log_name, hw_prod, bd_prod, rx_bd->rx_pkt_index, len); + LOG_DEBUG(PFX "%s: sw_con %d bd_cons %d num BD %d", + nic->log_name, sw_cons, bd_cons, QEDI_NUM_RX_BD); + + if (bd_cons != bd_prod) { + LOG_DEBUG(PFX "%s: clearing rx interrupt: %d %d", + nic->log_name, sw_cons, hw_prod); + rc = 1; + rx_pkt = bp->rx_pkts + (bp->rx_buffer_size * rx_pkt_idx); + + if (len > 0) { + msync(rx_pkt, len, MS_SYNC); + /* Copy the data */ + memcpy(pkt->buf, rx_pkt, len); + pkt->buf_size = len; + if (vlan_id) { + pkt->vlan_tag = vlan_id; + pkt->flags |= VLAN_TAGGED; + } else { + pkt->vlan_tag = 0; + } + + LOG_DEBUG(PFX "%s: processing packet length: %d", + nic->log_name, len); + + /* bump up the recv stats */ + nic->stats.rx.packets++; + nic->stats.rx.bytes += pkt->buf_size; + } else { + rc = 0; + } + + sw_cons = (sw_cons + 1) % RX_RING_SIZE; + bd_cons = (bd_cons + 1) % QEDI_NUM_RX_BD; + uctrl->host_rx_cons_cnt++; + } + + uctrl->host_rx_bd_cons = bd_cons; + uctrl->host_rx_cons = sw_cons; + + msync(uctrl, sizeof(struct qedi_uio_ctrl), MS_SYNC); + msync(bp->rx_comp_ring, nic->page_size, MS_SYNC); + return rc; +} + +/******************************************************************************* + * Clearing TX interrupts + ******************************************************************************/ +/** + * qedi_clear_tx_intr() - This routine is called when a TX interrupt occurs + * @param nic - the nic the interrupt occurred on + * @return 0 on success + */ + +static int qedi_clear_tx_intr(nic_t *nic) +{ + qedi_t *bp; + uint32_t hw_cons; + struct qedi_uio_ctrl *uctrl; + + /* Sanity check: ensure the parameters passed in are valid */ + if (unlikely(!nic)) { + LOG_ERR(PFX "%s: nic == NULL", __func__); + return -EINVAL; + } + + bp = (qedi_t *)nic->priv; + uctrl = (struct qedi_uio_ctrl *)bp->uctrl_map; + msync(bp->uctrl_map, sizeof(struct qedi_uio_ctrl), MS_SYNC); + hw_cons = uctrl->hw_tx_cons; + + if (bp->tx_cons == hw_cons) { + if (bp->tx_cons == bp->tx_prod) + return 0; + return -EAGAIN; + } + + if (pthread_mutex_trylock(&nic->xmit_mutex)) { + LOG_ERR(PFX "%s: unable to get xmit_mutex.", nic->log_name); + return -EINVAL; + } + + LOG_DEBUG(PFX "%s: clearing tx interrupt [%d %d]", + nic->log_name, bp->tx_cons, hw_cons); + bp->tx_cons = hw_cons; + + /* There is a queued TX packet that needs to be sent out. The usual + * case is when stack will send an ARP packet out before sending the + * intended packet + */ + if (nic->tx_packet_queue) { + packet_t *pkt; + int i; + + LOG_DEBUG(PFX "%s: sending queued tx packet", nic->log_name); + pkt = nic_dequeue_tx_packet(nic); + + /* Got a TX packet buffer of the TX queue and put it onto + * the hardware + */ + if (pkt) { + qedi_prepare_xmit_packet(nic, pkt->nic_iface, pkt); + + qedi_start_xmit(nic, pkt->buf_size, + (pkt->nic_iface->vlan_priority << 12) | + pkt->nic_iface->vlan_id); + + LOG_DEBUG(PFX "%s: transmitted queued packet %d bytes, dev->tx_cons: %d, dev->tx_prod: %d, dev->tx_bd_prod:%d", + nic->log_name, pkt->buf_size, + bp->tx_cons, bp->tx_prod, bp->tx_bd_prod); + + pthread_mutex_unlock(&nic->xmit_mutex); + return 0; + } + + /* Try to wait for a TX completion */ + for (i = 0; i < 15; i++) { + struct timespec sleep_req = {.tv_sec = 0, + .tv_nsec = 5000000 + }, sleep_rem; + + hw_cons = uctrl->hw_tx_cons; + if (bp->tx_cons != hw_cons) { + LOG_PACKET(PFX + "%s: clearing tx interrupt [%d %d]", + nic->log_name, bp->tx_cons, hw_cons); + bp->tx_cons = hw_cons; + + break; + } + + nanosleep(&sleep_req, &sleep_rem); + } + } + + LOG_DEBUG(PFX "%s: host:%d - releasing xmit mutex", + nic->log_name, nic->host_no); + pthread_mutex_unlock(&nic->xmit_mutex); + + return 0; +} + +/******************************************************************************* + * qedi NIC op's table + ******************************************************************************/ +struct nic_ops qedi_op = { + .description = "qedi", + .open = qedi_open, + .close = qedi_close, + .write = qedi_write, + .get_tx_pkt = qedi_get_tx_pkt, + .start_xmit = qedi_start_xmit, + .read = qedi_read, + .clear_tx_intr = qedi_clear_tx_intr, + .handle_iscsi_path_req = cnic_handle_iscsi_path_req, + + .lib_ops = { + .get_library_name = qedi_get_library_name, + .get_library_version = qedi_get_library_version, + .get_build_date = qedi_get_build_date, + .get_transport_name = qedi_get_transport_name, + .get_uio_name = qedi_get_uio_name, + }, +}; diff --git a/iscsiuio/src/unix/libs/qedi.h b/iscsiuio/src/unix/libs/qedi.h new file mode 100644 index 0000000..7e0140a --- /dev/null +++ b/iscsiuio/src/unix/libs/qedi.h @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2016, Cavium Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * qedi.h - qedi user space driver + * + */ +#ifndef __QEDI_H__ +#define __QEDI_H__ + +#include "nic.h" + +#define RX_RING_SIZE 15 +#define PKT_BUF_SIZE 0X400 +#define QEDI_PAGE_SIZE 4096 + +#define QEDI_UNKNOWN_MAJOR_VERSION -1 +#define QEDI_UNKNOWN_MINOR_VERSION -1 +#define QEDI_UNKNOWN_SUB_MINOR_VERSION -1 +struct qedi_driver_version { + uint16_t major; + uint16_t minor; + uint16_t sub_minor; +}; + +#define QEDI_UCTRL_MAP_REG 0 +#define QEDI_RING_MAP_REG 1 +#define QEDI_BUF_MAP_REG 2 +#define UIO_ATTR_TMPL "/sys/class/uio/uio%u/maps/map%u/%s" +#define UIO_ADDR_TMPL "/sys/class/uio/uio%u/maps/map%u/addr" +#define UIO_OFFSET_TMPL "/sys/class/uio/uio%u/maps/map%u/offset" +#define UIO_SIZE_TMPL "/sys/class/uio/uio%u/maps/map%u/size" + +struct qedi_uio_ctrl { + /* meta data */ + __u32 uio_hsi_version; + + /* user writes */ + __u32 host_tx_prod; + __u32 host_rx_cons; + __u32 host_rx_bd_cons; + __u32 host_tx_pkt_len; + __u32 host_rx_cons_cnt; + + /* driver writes */ + __u32 hw_tx_cons; + __u32 hw_rx_prod; + __u32 hw_rx_bd_prod; + __u32 hw_rx_prod_cnt; + + /* other */ + __u8 mac_addr[6]; + __u8 reserve[2]; +}; + +struct qedi_rx_bd { + __u32 rx_pkt_index; + __u32 rx_pkt_len; + __u16 vlan_id; +}; + +#define QEDI_RX_DESC_CNT (QEDI_PAGE_SIZE / sizeof(struct qedi_rx_bd)) +#define QEDI_MAX_RX_DESC_CNT (QEDI_RX_DESC_CNT - 1) +#define QEDI_NUM_RX_BD (QEDI_RX_DESC_CNT * 1) +#define QEDI_MAX_RX_BD (QEDI_NUM_RX_BD - 1) + +#define QEDI_NEXT_RX_IDX(x) ((((x) & (QEDI_MAX_RX_DESC_CNT)) == \ + (QEDI_MAX_RX_DESC_CNT - 1)) ? \ + (x) + 2 : (x) + 1) + +#define QEDI_PATH_HANDLE 0xFE0000000 + +typedef struct qedi { + nic_t *parent; + + struct qedi_driver_version version; + + uint16_t flags; +#define CNIC_UIO_UNITIALIZED 0x0001 +#define CNIC_UIO_INITIALIZED 0x0002 +#define CNIC_UIO_ENABLED 0x0004 +#define CNIC_UIO_DISABLED 0x0008 +#define CNIC_UIO_IPv6_ENABLED 0x0010 +#define CNIC_UIO_ADDED_MULICAST 0x0020 +#define CNIC_UIO_MSIX_ENABLED 0x0200 +#define CNIC_UIO_TX_HAS_SENT 0x0400 +#define QEDI_OPENED 0x0800 + + __u32 chip_id; + int func; + int port; + int pfid; + __u32 cid; + __u32 client_id; + + __u32 tx_prod; + __u32 tx_bd_prod; + __u32 tx_cons; + __u8 tx_vlan_tag_bit; + + __u32 rx_prod; + __u32 rx_bd_prod; + __u32 rx_cons; + __u32 rx_bd_cons; + __u32 rx_hw_prod; + + __u32 (*get_rx_cons)(struct qedi *); + __u32 (*get_tx_cons)(struct qedi *); + + /* RX ring parameters */ + uint32_t rx_ring_size; + uint32_t rx_buffer_size; + + void *bufs; /* Pointer to the mapped buffer space */ + void *uctrl_map; /* UIO control structure */ + uint32_t uctrl_map_offset; /* UIO control structure mmap offset */ + + uint32_t rx_index; + void *rx_comp_ring; + void **rx_pkt_ring; + void *tx_pkt; + void *rx_pkts; +} qedi_t; + +/****************************************************************************** + * qedi Function Declarations + ******************************************************************************/ +void qedi_start_xmit(nic_t *nic, size_t len, u16_t vlan_id); +struct nic_ops *qedi_get_ops(); + +#endif /* __QEDI_H__ */ diff --git a/iscsiuio/src/unix/logger.c b/iscsiuio/src/unix/logger.c new file mode 100644 index 0000000..87e16cd --- /dev/null +++ b/iscsiuio/src/unix/logger.c @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * logger.c - Logging Utilities + * + */ +#include +#include +#include +#include +#include + +#include "options.h" +#include "logger.h" + +/****************************************************************************** + * Default logger values + ******************************************************************************/ +static const char default_logger_filename[] = "/var/log/iscsiuio.log"; + +struct logger main_log = { + .enabled = LOGGER_ENABLED, + .fp = NULL, + .log_file = (char *)default_logger_filename, + .level = LOG_LEVEL_INFO, + .lock = PTHREAD_MUTEX_INITIALIZER, + + .stats = { + .debug = 0, + .info = 0, + .warn = 0, + .error = 0, + + .last_log_time = 0, + }, +}; + +/****************************************************************************** + * Logger Functions + ******************************************************************************/ +/** + * log_uip() - Main logging function + * @param level_str - log level string + * @param fmt - log format + */ +void log_uip(char *level_str, char *fmt, ...) +{ + char time_buf[32]; + va_list ap, ap2; + + pthread_mutex_lock(&main_log.lock); + va_start(ap, fmt); + + if (main_log.fp == NULL) + goto end; + + main_log.stats.last_log_time = time(NULL); + strftime(time_buf, 26, "%a %b %d %T %Y", + localtime(&main_log.stats.last_log_time)); + va_copy(ap2, ap); + + if (main_log.enabled == LOGGER_ENABLED) { + fprintf(main_log.fp, "%s [%s]", level_str, time_buf); + vfprintf(main_log.fp, fmt, ap); + fprintf(main_log.fp, "\n"); + } + + if (opt.debug == DEBUG_ON) { + fprintf(stdout, "%s [%s]", level_str, time_buf); + vfprintf(stdout, fmt, ap2); + fprintf(stdout, "\n"); + + /* Force the printing of the log file */ + fflush(main_log.fp); + + /* Force the printing of the log out to standard output */ + fflush(stdout); + } + +end: + va_end(ap2); + va_end(ap); + pthread_mutex_unlock(&main_log.lock); +} + +/****************************************************************************** + * Initialize/Clean up routines + ******************************************************************************/ +/** + * init_logger() - Prepare the logger + * @param filename - path to where the log will be written to + * @return 0 on success, <0 on failure + */ +int init_logger(char *filename) +{ + int rc = 0; + + pthread_mutex_lock(&main_log.lock); + + if (opt.debug != DEBUG_ON) { + rc = -EIO; + goto disable; + } + main_log.fp = fopen(filename, "a"); + if (main_log.fp == NULL) { + fprintf(stderr, "WARN: Could not create log file: %s <%s>\n", + filename, strerror(errno)); + rc = -EIO; + } +disable: + if (rc) + main_log.enabled = LOGGER_DISABLED; + else + main_log.enabled = LOGGER_ENABLED; + + pthread_mutex_unlock(&main_log.lock); + + if (!rc) + LOG_INFO("Initialize logger using log file: %s", filename); + + return rc; +} + +void fini_logger(int type) +{ + pthread_mutex_lock(&main_log.lock); + + if (main_log.fp != NULL) { + fclose(main_log.fp); + main_log.fp = NULL; + + if (opt.debug == DEBUG_ON) { + printf("Closed logger\n"); + fflush(stdout); + } + } + + if (type == SHUTDOWN_LOGGER) { + if ((main_log.log_file != NULL) && + (main_log.log_file != default_logger_filename)) { + free(main_log.log_file); + main_log.log_file = NULL; + } + } + + main_log.enabled = LOGGER_DISABLED; + + pthread_mutex_unlock(&main_log.lock); +} diff --git a/iscsiuio/src/unix/logger.h b/iscsiuio/src/unix/logger.h new file mode 100644 index 0000000..06e2084 --- /dev/null +++ b/iscsiuio/src/unix/logger.h @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * logger.h - Logging Utilities + * + */ +#ifndef __LOGGER_H__ +#define __LOGGER_H__ + +#include +#include +#include +#include +#include + +/******************************************************************************* + * Logger Levels + ******************************************************************************/ +#define LOG_LEVEL_PACKET 5 +#define LOG_LEVEL_DEBUG 4 +#define LOG_LEVEL_INFO 3 +#define LOG_LEVEL_WARN 2 +#define LOG_LEVEL_ERR 1 +#define LOG_LEVEL_UNKNOWN 0 + +#define LOG_LEVEL_PACKET_STR "PKT " +#define LOG_LEVEL_DEBUG_STR "DBG " +#define LOG_LEVEL_INFO_STR "INFO " +#define LOG_LEVEL_WARN_STR "WARN " +#define LOG_LEVEL_ERR_STR "ERR " +#define LOG_LEVEL_UNKNOWN_STR "? " + +/******************************************************************************* + * Logging Macro's + ******************************************************************************/ +#define LOG_PACKET(fmt, args...) { if (LOG_LEVEL_PACKET <= \ + main_log.level) { \ + log_uip(LOG_LEVEL_PACKET_STR, fmt,\ + ##args);\ + } } +#define LOG_DEBUG(fmt, args...) { if (LOG_LEVEL_DEBUG <= main_log.level) { \ + log_uip(LOG_LEVEL_DEBUG_STR, fmt,\ + ##args);\ + } } + +#define LOG_INFO(fmt, args...) { if (LOG_LEVEL_INFO <= main_log.level) { \ + log_uip(LOG_LEVEL_INFO_STR, fmt,\ + ##args); \ + } } +#define LOG_WARN(fmt, args...) { if (LOG_LEVEL_WARN <= main_log.level) { \ + log_uip(LOG_LEVEL_WARN_STR, fmt,\ + ##args); \ + } } +#define LOG_ERR(fmt, args...) { if (LOG_LEVEL_ERR <= main_log.level) { \ + log_uip(LOG_LEVEL_ERR_STR, fmt,\ + ##args); \ + } } + +/******************************************************************************* + * Logging Statistics + ******************************************************************************/ +struct logger_stats { + uint64_t debug; + uint64_t info; + uint64_t warn; + uint64_t error; + + time_t last_log_time; +}; + +/******************************************************************************* + * Logger Structure + ******************************************************************************/ +struct logger { + FILE *fp; + char *log_file; + int8_t level; + +#define LOGGER_ENABLED 0x01 +#define LOGGER_DISABLED 0x02 + int8_t enabled; + + pthread_mutex_t lock; + + struct logger_stats stats; +}; + +extern struct logger main_log; + +int init_logger(char *); +void log_uip(char *level_str, char *fmt, ...); +void fini_logger(int); + +#define CLOSE_LOGGER 0x01 +#define SHUTDOWN_LOGGER 0x02 + +#endif diff --git a/iscsiuio/src/unix/main.c b/iscsiuio/src/unix/main.c new file mode 100644 index 0000000..5e3f66c --- /dev/null +++ b/iscsiuio/src/unix/main.c @@ -0,0 +1,435 @@ +/* + * Copyright (c) 2001, Adam Dunkels. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack. + * + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef NO_SYSTEMD +#include +#endif + +#include "uip.h" +#include "uip_arp.h" +#include "uip_eth.h" + +#include "timer.h" + +#include "build_date.h" +#include "config.h" +#include "iscsid_ipc.h" +#include "logger.h" +#include "nic.h" +#include "nic_id.h" +#include "nic_nl.h" +#include "nic_utils.h" +#include "options.h" +#include "packet.h" + +#include "dhcpc.h" + +#include "iscsid_ipc.h" +#include "brcm_iscsi.h" + +/******************************************************************************* + * Constants + ******************************************************************************/ +#define PFX "main " + +static const char default_pid_filepath[] = "/run/iscsiuio.pid"; + +/******************************************************************************* + * Global Variables + ******************************************************************************/ +static const struct option long_options[] = { + {"foreground", no_argument, NULL, 'f'}, + {"debug", required_argument, NULL, 'd'}, + {"pid", required_argument, NULL, 'p'}, + {"version", no_argument, NULL, 'v'}, + {"help", no_argument, NULL, 'h'}, + {NULL, no_argument, NULL, 0} +}; + +struct options opt = { + .debug = DEBUG_OFF, +}; + +int event_loop_stop; +extern nic_t *nic_list; + +struct utsname cur_utsname; + +/** + * cleanup() - This function is called when this program is to be closed + * This function will clean up all the cnic uio interfaces and + * flush/close the logger + */ +static void cleanup() +{ + iscsid_cleanup(); + + nic_remove_all(); + + unload_all_nic_libraries(); + + LOG_INFO("Done waiting for cnic's/stacks to gracefully close"); + + fini_logger(SHUTDOWN_LOGGER); +} + +/** + * signal_handle_thread() - This is the signal handling thread of this program + * This is the only thread which will handle signals. + * All signals are routed here and handled here to + * provide consistant handling. + */ +static pthread_t signal_thread; +static void *signal_handle_thread(void *arg) +{ + sigset_t set; + int rc; + int signal; + + sigfillset(&set); + + LOG_INFO("signal handling thread ready"); + +signal_wait: + rc = sigwait(&set, &signal); + + switch (signal) { + case SIGINT: + LOG_INFO("Caught SIGINT signal"); + break; + case SIGUSR1: + LOG_INFO("Caught SIGUSR1 signal, rotate log"); + fini_logger(SHUTDOWN_LOGGER); + rc = init_logger(main_log.log_file); + if (rc != 0) + fprintf(stderr, "WARN: Could not initialize the logger in " + "signal!\n"); + goto signal_wait; + default: + break; + } + event_loop_stop = 1; + + LOG_INFO("terminating..."); + + cleanup(); + exit(EXIT_SUCCESS); +} + +static void show_version() +{ + printf("%s: Version '%s', Build Date: '%s'\n", + APP_NAME, PACKAGE_VERSION, build_date); +} + +static void main_usage() +{ + show_version(); + + printf("\nUsage: %s [OPTION]\n", APP_NAME); + printf("iscsiuio daemon.\n" + "-f, --foreground make the program run in the foreground\n" + "-d, --debug debuglevel print debugging information\n" + "-p, --pid pidfile use pid file (default %s).\n" + "-h, --help display this help and exit\n" + "-v, --version display version and exit\n", + default_pid_filepath); +} + +static void daemon_init() +{ + int fd; + + fd = open("/dev/null", O_RDWR); + if (fd == -1) + exit(-1); + + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + setsid(); + chdir("/"); + close(fd); +} + +#define ISCSI_OOM_PATH_LEN 48 + +int oom_adjust(void) +{ + int fd; + char path[ISCSI_OOM_PATH_LEN]; + struct stat statb; + + if (nice(-10) < 0) + LOG_DEBUG("Could not increase process priority: %s", + strerror(errno)); + + snprintf(path, ISCSI_OOM_PATH_LEN, "/proc/%d/oom_score_adj", getpid()); + if (stat(path, &statb)) { + /* older kernel so use old oom_adj file */ + snprintf(path, ISCSI_OOM_PATH_LEN, "/proc/%d/oom_adj", + getpid()); + } + fd = open(path, O_WRONLY); + if (fd < 0) + return -1; + if (write(fd, "-16", 3) < 0) /* for 2.6.11 */ + LOG_DEBUG("Could not set oom score to -16: %s", + strerror(errno)); + if (write(fd, "-17", 3) < 0) /* for Andrea's patch */ + LOG_DEBUG("Could not set oom score to -17: %s", + strerror(errno)); + close(fd); + return 0; +} + + +/******************************************************************************* + * Main routine + ******************************************************************************/ +int main(int argc, char *argv[]) +{ + int rc; + sigset_t set; + const char *pid_file = default_pid_filepath; + int fd; + int foreground = 0; + pid_t pid; + pthread_attr_t attr; + int pipefds[2]; + + /* Record the start time for the user space daemon */ + opt.start_time = time(NULL); + + /* parse the parameters */ + while (1) { + int c, option_index; + + c = getopt_long(argc, argv, "fd:p:vh", + long_options, &option_index); + + if (c == -1) + break; + + switch (c) { + + case 'f': + foreground = 1; + break; + + /* Enable debugging mode */ + case 'd': + main_log.level = atoi(optarg); + opt.debug = DEBUG_ON; + break; + case 'p': + pid_file = optarg; + break; + case 'v': + show_version(); + exit(EXIT_SUCCESS); + case 'h': + default: + main_usage(); + exit(EXIT_SUCCESS); + } + } + + if (main_log.enabled == LOGGER_ENABLED) { + /* initialize the logger */ + rc = init_logger(main_log.log_file); + if (rc != 0 && opt.debug == DEBUG_ON) + fprintf(stderr, "WARN: Could not initialize the logger\n"); + } + + LOG_INFO("Started iSCSI uio stack: Ver " PACKAGE_VERSION); + LOG_INFO("Build date: %s", build_date); + + if (opt.debug == DEBUG_ON) + LOG_INFO("Debug mode enabled"); + + event_loop_stop = 0; + nic_list = NULL; + + /* Determine the current kernel version */ + memset(&cur_utsname, 0, sizeof(cur_utsname)); + + rc = uname(&cur_utsname); + if (rc == 0) { + LOG_INFO("Running on sysname: '%s', release: '%s', " + "version '%s' machine: '%s'", + cur_utsname.sysname, cur_utsname.release, + cur_utsname.version, cur_utsname.machine); + } else + LOG_WARN("Could not determine kernel version"); + + /* Initialze the iscsid listener */ + rc = iscsid_init(); + if (rc != 0) + goto error; + + if (!foreground) { + char buf[64]; + ssize_t written_bytes; + + fd = open(pid_file, O_WRONLY | O_CREAT, 0644); + if (fd < 0) { + fprintf(stderr, "ERR: Unable to create pid file: %s\n", + pid_file); + exit(1); + } + + if (pipe(pipefds) < 0) { + fprintf(stderr, "ERR: Unable to create a PIPE: %s\n", + strerror(errno)); + exit(1); + } + + pid = fork(); + if (pid < 0) { + fprintf(stderr, "ERR: Starting daemon failed\n"); + exit(1); + } else if (pid) { + char msgbuf[4]; + + /* parent: wait for child msg then exit */ + close(pipefds[1]); + read(pipefds[0], msgbuf, sizeof(msgbuf)); + exit(0); + } + + /* the child */ + rc = chdir("/"); + if (rc == -1) + fprintf(stderr, "WARN: Unable to chdir(\") [%s]\n", strerror(errno)); + + if (lockf(fd, F_TLOCK, 0) < 0) { + fprintf(stderr, "ERR: Unable to lock pid file: %s [%s]\n", + pid_file, strerror(errno)); + exit(1); + } + + rc = ftruncate(fd, 0); + if (rc == -1) + fprintf(stderr, "WARN: ftruncate(%d, 0) failed [%s]\n", + fd, strerror(errno)); + + sprintf(buf, "%d\n", getpid()); + written_bytes = write(fd, buf, strlen(buf)); + if (written_bytes == -1) { + fprintf(stderr, "ERR: Could not write pid file [%s]\n", + strerror(errno)); + exit(1); + } + close(fd); + + daemon_init(); + } + + /* Load the NIC libraries */ + rc = load_all_nic_libraries(); + if (rc != 0) + goto error; + + brcm_iscsi_init(); + + /* ensure we don't see any signals */ + sigemptyset(&set); + sigaddset(&set, SIGINT); + sigaddset(&set, SIGQUIT); + sigaddset(&set, SIGTERM); + sigaddset(&set, SIGUSR1); + rc = pthread_sigmask(SIG_SETMASK, &set, NULL); + + /* Spin off the signal handling thread */ + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + rc = pthread_create(&signal_thread, &attr, signal_handle_thread, NULL); + if (rc != 0) + LOG_ERR("Could not create signal handling thread"); + + /* Using sysfs to discover iSCSI hosts */ + nic_discover_iscsi_hosts(); + + /* oom-killer will not kill us at the night... */ + if (oom_adjust()) + LOG_DEBUG("Can not adjust oom-killer's pardon"); + + /* we don't want our active sessions to be paged out... */ + if (mlockall(MCL_CURRENT | MCL_FUTURE)) { + LOG_ERR("failed to mlockall, exiting..."); + goto error; + } + + /* Start the iscsid listener */ + rc = iscsid_start(); + if (rc != 0) + goto error; + + if (!foreground) { + /* signal parent they can go away now */ + close(pipefds[0]); + write(pipefds[1], "ok\n", 3); + close(pipefds[1]); + } + +#ifndef NO_SYSTEMD + sd_notify(0, "READY=1\n" + "STATUS=Ready to process requests\n"); +#endif + + /* NetLink connection to listen to NETLINK_ISCSI private messages */ + if (nic_nl_open() != 0) + goto error; + +error: + cleanup(); + exit(EXIT_FAILURE); +} diff --git a/iscsiuio/src/unix/nic.c b/iscsiuio/src/unix/nic.c new file mode 100644 index 0000000..f449935 --- /dev/null +++ b/iscsiuio/src/unix/nic.c @@ -0,0 +1,1548 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * nic.c - Generic NIC management/utility functions + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dhcpc.h" +#include "ipv6_ndpc.h" + +#include "logger.h" +#include "nic.h" +#include "nic_utils.h" +#include "options.h" + +#include "uip.h" +#include "uip_arp.h" +#include "uip_eth.h" +#include "uip-neighbor.h" + +#include "bnx2.h" +#include "bnx2x.h" +#include "qedi.h" +#include "ipv6.h" + +/****************************************************************************** + * Constants + *****************************************************************************/ +#define PFX "nic " +#define PCI_ANY_ID (~0) + +/****************************************************************************** + * Global variables + *****************************************************************************/ +/* Used to store a list of NIC libraries */ +pthread_mutex_t nic_lib_list_mutex = PTHREAD_MUTEX_INITIALIZER; +nic_lib_handle_t *nic_lib_list; + +/* Used to store a list of active cnic devices */ +pthread_mutex_t nic_list_mutex = PTHREAD_MUTEX_INITIALIZER; +nic_t *nic_list; + +/****************************************************************************** + * Functions to handle NIC libraries + *****************************************************************************/ +/** + * alloc_nic_library_handle() - Used to allocate a NIC library handle + * @return NULL if memory couldn't be allocated, pointer to the handle + * to the NIC library handle + */ +static nic_lib_handle_t *alloc_nic_library_handle() +{ + nic_lib_handle_t *handle; + + handle = malloc(sizeof(*handle)); + if (handle == NULL) { + LOG_ERR("Could not allocate memory for library handle"); + return NULL; + } + + memset(handle, 0, sizeof(*handle)); + handle->ops = NULL; + + pthread_mutex_init(&handle->mutex, NULL); + + return handle; +} + +static void free_nic_library_handle(nic_lib_handle_t *handle) +{ + free(handle); +} + +/** + * load_nic_library() - This function is used to load a NIC library + * @param handle - This is the library handle to load + * @return 0 = Success; <0 = failure + */ +static int load_nic_library(nic_lib_handle_t *handle) +{ + int rc; + char *library_name; + size_t library_name_size; + char *library_version; + size_t library_version_size; + char *build_date_str; + size_t build_date_str_size; + + pthread_mutex_lock(&handle->mutex); + + /* Validate the NIC ops table ensure that all the fields are not NULL */ + if ((handle->ops->open) == NULL || + (handle->ops->close) == NULL || + (handle->ops->read) == NULL || + (handle->ops->write) == NULL || + (handle->ops->clear_tx_intr == NULL)) { + LOG_ERR("Invalid NIC ops table: open: 0x%x, close: 0x%x," + "read: 0x%x, write: 0x%x clear_tx_intr: 0x%x " + "lib_ops: 0x%x", + handle->ops->open, handle->ops->close, + handle->ops->read, handle->ops->write, + handle->ops->clear_tx_intr, handle->ops->lib_ops); + rc = -EINVAL; + handle->ops = NULL; + goto error; + } + + /* Validate the NIC library ops table to ensure that all the proper + * fields are filled */ + if ((handle->ops->lib_ops.get_library_name == NULL) || + (handle->ops->lib_ops.get_library_version == NULL) || + (handle->ops->lib_ops.get_build_date == NULL) || + (handle->ops->lib_ops.get_transport_name == NULL)) { + rc = -EINVAL; + goto error; + } + + (*handle->ops->lib_ops.get_library_name) (&library_name, + &library_name_size); + (*handle->ops->lib_ops.get_library_version) (&library_version, + &library_version_size); + (*handle->ops->lib_ops.get_build_date) (&build_date_str, + &build_date_str_size); + + LOG_DEBUG("Loaded nic library '%s' Version: '%s' build on %s'", + library_name, library_version, build_date_str); + + pthread_mutex_unlock(&handle->mutex); + + return 0; + +error: + pthread_mutex_unlock(&handle->mutex); + + return rc; +} + +static struct nic_ops *(*nic_get_ops[]) () = { +bnx2_get_ops, bnx2x_get_ops, qedi_get_ops}; + +int load_all_nic_libraries() +{ + int rc, i = 0; + nic_lib_handle_t *handle; + + for (i = 0; i < sizeof(nic_get_ops) / sizeof(nic_get_ops[0]); i++) { + /* Add the CNIC library */ + handle = alloc_nic_library_handle(); + if (handle == NULL) { + LOG_ERR("Could not allocate memory for CNIC nic lib"); + return -ENOMEM; + } + + handle->ops = (*nic_get_ops[i]) (); + + rc = load_nic_library(handle); + if (rc != 0) { + free_nic_library_handle(handle); + return rc; + } + /* Add the CNIC library to the list of library handles */ + pthread_mutex_lock(&nic_lib_list_mutex); + + /* Add this library to the list of nic libraries we + * know about */ + if (nic_lib_list == NULL) { + nic_lib_list = handle; + } else { + nic_lib_handle_t *current = nic_lib_list; + + while (current->next != NULL) + current = current->next; + + current->next = handle; + } + pthread_mutex_unlock(&nic_lib_list_mutex); + + LOG_DEBUG("Added '%s' nic library", handle->ops->description); + } + + return rc; +} + +int unload_all_nic_libraries() +{ + nic_lib_handle_t *current, *next; + + pthread_mutex_lock(&nic_lib_list_mutex); + current = nic_lib_list; + + while (current != NULL) { + next = current->next; + free_nic_library_handle(current); + + current = next; + } + + pthread_mutex_unlock(&nic_lib_list_mutex); + + nic_lib_list = NULL; + + return 0; +} + +NIC_LIBRARY_EXIST_T does_nic_uio_name_exist(char *name, + nic_lib_handle_t **handle) +{ + NIC_LIBRARY_EXIST_T rc; + nic_lib_handle_t *current; + + pthread_mutex_lock(&nic_lib_list_mutex); + current = nic_lib_list; + + while (current != NULL) { + char *uio_name; + size_t uio_name_size; + + (*current->ops->lib_ops.get_uio_name) (&uio_name, + &uio_name_size); + + if (strncmp(name, uio_name, uio_name_size) == 0) { + if (handle) + *handle = current; + + rc = NIC_LIBRARY_EXSITS; + goto done; + } + + current = current->next; + } + + rc = NIC_LIBRARY_DOESNT_EXIST; + +done: + pthread_mutex_unlock(&nic_lib_list_mutex); + return rc; +} + +NIC_LIBRARY_EXIST_T does_nic_library_exist(char *name, + nic_lib_handle_t **handle) +{ + NIC_LIBRARY_EXIST_T rc; + nic_lib_handle_t *current; + + pthread_mutex_lock(&nic_lib_list_mutex); + current = nic_lib_list; + + while (current != NULL) { + char *library_name; + size_t library_name_size; + + (*current->ops->lib_ops.get_library_name) (&library_name, + &library_name_size); + + if (strncmp(name, library_name, library_name_size) == 0) { + if (handle) + *handle = current; + + rc = NIC_LIBRARY_EXSITS; + goto done; + } + + current = current->next; + } + + rc = NIC_LIBRARY_DOESNT_EXIST; + +done: + pthread_mutex_unlock(&nic_lib_list_mutex); + return rc; +} + +/** + * find_nic_lib_using_pci_id() - Find the proper NIC library using the + * PCI ID's + * @param vendor - PCI vendor ID to search on + * @param device - PCI device ID to search on + * @param subvendor - PCI subvendor ID to search on + * @param subdevice - PCI subdevice ID to search on + * @param handle - This function will return the nic lib handle if found + * @return 0 if found, <0 not found + */ +int find_nic_lib_using_pci_id(uint32_t vendor, uint32_t device, + uint32_t subvendor, uint32_t subdevice, + nic_lib_handle_t **handle, + struct pci_device_id **pci_entry) +{ + int rc; + nic_lib_handle_t *current; + + pthread_mutex_lock(&nic_lib_list_mutex); + current = nic_lib_list; + + while (current != NULL) { + struct pci_device_id *pci_table; + uint32_t entries; + int i; + + if (current->ops->lib_ops.get_pci_table != NULL) { + current->ops->lib_ops.get_pci_table(&pci_table, + &entries); + } else { + current = current->next; + continue; + } + /* Sanity check the the pci table coming from the + * hardware library */ + if (entries > MAX_PCI_DEVICE_ENTRIES) { + LOG_WARN(PFX "Too many pci_table entries(%d) skipping", + entries); + continue; + } + + for (i = 0; i < entries; i++) { + LOG_DEBUG(PFX "Checking against: " + "vendor: 0x%x device:0x%x " + "subvendor:0x%x subdevice:0x%x", + pci_table[i].vendor, pci_table[i].device, + pci_table[i].subvendor, + pci_table[i].subdevice); + + if ((pci_table[i].vendor == vendor) && + (pci_table[i].device == device) && + (pci_table[i].subvendor == PCI_ANY_ID || + pci_table[i].subvendor == subvendor) && + (pci_table[i].subdevice == PCI_ANY_ID || + pci_table[i].subdevice == subdevice)) { + *handle = current; + *pci_entry = &pci_table[i]; + rc = 0; + goto done; + } + } + + current = current->next; + } + rc = -EINVAL; + +done: + pthread_mutex_unlock(&nic_lib_list_mutex); + + return rc; +} + +/** + * nic_init() - This will properly initialize a struct cnic_uio device + * @return NULL is there is a failure and pointer to an allocated/initialized + * struct cnic_uio on success + */ +nic_t *nic_init() +{ + nic_t *nic; + + nic = malloc(sizeof(*nic)); + if (nic == NULL) { + LOG_ERR("Couldn't malloc space for nic"); + return NULL; + } + + memset(nic, 0, sizeof(*nic)); + nic->uio_minor = -1; + nic->fd = INVALID_FD; + nic->host_no = INVALID_HOST_NO; + nic->next = NULL; + nic->thread = INVALID_THREAD; + nic->enable_thread = INVALID_THREAD; + nic->flags |= NIC_DISABLED; + nic->state = NIC_STOPPED; + nic->free_packet_queue = NULL; + nic->tx_packet_queue = NULL; + nic->nic_library = NULL; + nic->pci_id = NULL; + nic->page_size = getpagesize(); + + /* nic_mutex is used to protect nic ops */ + pthread_mutex_init(&nic->nic_mutex, NULL); + pthread_mutex_init(&nic->xmit_mutex, NULL); + pthread_mutex_init(&nic->free_packet_queue_mutex, NULL); + + pthread_cond_init(&nic->enable_wait_cond, NULL); + pthread_cond_init(&nic->enable_done_cond, NULL); + pthread_cond_init(&nic->nic_loop_started_cond, NULL); + pthread_cond_init(&nic->disable_wait_cond, NULL); + + nic->rx_poll_usec = DEFAULT_RX_POLL_USEC; + + pthread_mutex_init(&nic->nl_process_mutex, NULL); + pthread_cond_init(&nic->nl_process_if_down_cond, NULL); + pthread_cond_init(&nic->nl_process_cond, NULL); + nic->nl_process_thread = INVALID_THREAD; + nic->nl_process_if_down = 0; + nic->nl_process_head = 0; + nic->nl_process_tail = 0; + memset(&nic->nl_process_ring, 0, sizeof(nic->nl_process_ring)); + + nic->ping_thread = INVALID_THREAD; + + return nic; +} + +void nic_add(nic_t *nic) +{ + /* Add this device to our list of nics */ + if (nic_list == NULL) { + nic_list = nic; + } else { + nic_t *current = nic_list; + + while (current->next != NULL) + current = current->next; + + current->next = nic; + } +} + +/** + * nic_remove() - Used to remove the NIC for the nic list + * @param nic - the nic to remove + */ +int nic_remove(nic_t *nic) +{ + int rc; + nic_t *prev, *current; + struct stat file_stat; + nic_interface_t *nic_iface, *next_nic_iface, *vlan_iface; + + pthread_mutex_lock(&nic->nic_mutex); + + /* Check if the file node exists before closing */ + if (nic->uio_device_name) { + rc = stat(nic->uio_device_name, &file_stat); + if ((rc == 0) && (nic->ops)) + nic->ops->close(nic, 0); + } + pthread_mutex_unlock(&nic->nic_mutex); + + nic->state = NIC_EXIT; + + if (nic->enable_thread != INVALID_THREAD) { + LOG_DEBUG(PFX "%s: Canceling nic enable thread", nic->log_name); + + rc = pthread_cancel(nic->enable_thread); + if (rc != 0) + LOG_DEBUG(PFX "%s: Couldn't send cancel to nic enable " + "thread", nic->log_name); + + nic->enable_thread = INVALID_THREAD; + LOG_DEBUG(PFX "%s: nic enable thread cleaned", nic->log_name); + } else { + LOG_DEBUG(PFX "%s: NIC enable thread already canceled", + nic->log_name); + } + + if (nic->thread != INVALID_THREAD) { + LOG_DEBUG(PFX "%s: Canceling nic thread", nic->log_name); + + rc = pthread_cancel(nic->thread); + if (rc != 0) + LOG_DEBUG(PFX "%s: Couldn't send cancel to nic", + nic->log_name); + + nic->thread = INVALID_THREAD; + LOG_DEBUG(PFX "%s: nic thread cleaned", nic->log_name); + } else { + LOG_DEBUG(PFX "%s: NIC thread already canceled", nic->log_name); + } + + if (nic->nl_process_thread != INVALID_THREAD) { + LOG_DEBUG(PFX "%s: Canceling nic nl thread", nic->log_name); + + rc = pthread_cancel(nic->nl_process_thread); + if (rc != 0) + LOG_DEBUG(PFX "%s: Couldn't send cancel to nic nl " + "thread", nic->log_name); + + nic->nl_process_thread = INVALID_THREAD; + LOG_DEBUG(PFX "%s: nic nl thread cleaned", nic->log_name); + } else { + LOG_DEBUG(PFX "%s: NIC nl thread already canceled", + nic->log_name); + } + + current = prev = nic_list; + while (current != NULL) { + if (current == nic) + break; + + prev = current; + current = current->next; + } + + if (current != NULL) { + if (current == nic_list) + nic_list = current->next; + else + prev->next = current->next; + + /* Before freeing the nic, must free all the associated + nic_iface */ + nic_iface = current->nic_iface; + while (nic_iface != NULL) { + vlan_iface = nic_iface->vlan_next; + while (vlan_iface != NULL) { + next_nic_iface = vlan_iface->vlan_next; + free(vlan_iface); + vlan_iface = next_nic_iface; + } + next_nic_iface = nic_iface->next; + free(nic_iface); + nic_iface = next_nic_iface; + } + free(nic); + } else { + LOG_ERR(PFX "%s: Couldn't find nic to remove", nic->log_name); + } + + return 0; +} + +/** + * nic_close() - Used to indicate to a NIC that it should close + * Must be called with nic->nic_mutex + * @param nic - the nic to close + * @param graceful - ALLOW_GRACEFUL_SHUTDOWN will check the nic state + * before proceeding to close() + * FORCE_SHUTDOWN will force the nic to close() + * reguardless of the state + * @param clean - this will free the proper strings assoicated + * with the NIC + * + */ +void nic_close(nic_t *nic, NIC_SHUTDOWN_T graceful, int clean) +{ + int rc; + nic_interface_t *nic_iface, *vlan_iface; + struct stat file_stat; + + /* The NIC could be configured by the uIP config file + * but not assoicated with a hardware library just yet + * we will need to check for this */ + if (nic->ops == NULL) { + LOG_WARN(PFX "%s: when closing nic->ops == NULL", + nic->log_name); + goto error; + } + + /* Check if the file node exists */ + rc = stat(nic->uio_device_name, &file_stat); + if ((rc == 0) && (nic->ops)) + rc = (*nic->ops->close) (nic, graceful); + if (rc != 0) { + LOG_ERR(PFX "%s: Could not close nic", nic->log_name); + } else { + nic->state = NIC_STOPPED; + nic->flags &= ~NIC_ENABLED; + nic->flags |= NIC_DISABLED; + } + + nic_iface = nic->nic_iface; + while (nic_iface != NULL) { + if (!((nic_iface->flags & NIC_IFACE_PERSIST) == + NIC_IFACE_PERSIST)) { + uip_reset(&nic_iface->ustack); + vlan_iface = nic_iface->vlan_next; + while (vlan_iface != NULL) { + uip_reset(&vlan_iface->ustack); + vlan_iface = vlan_iface->vlan_next; + } + } + nic_iface = nic_iface->next; + } + + /* The NIC must be destroyed and init'ed once again, + * POSIX defines that the mutex will be undefined it + * init'ed twice without a destroy */ + pthread_mutex_destroy(&nic->xmit_mutex); + pthread_mutex_init(&nic->xmit_mutex, NULL); + + if (clean & FREE_CONFIG_NAME) { + /* Free any named strings we might be holding onto */ + if (nic->flags & NIC_CONFIG_NAME_MALLOC) { + free(nic->config_device_name); + nic->flags &= ~NIC_CONFIG_NAME_MALLOC; + } + nic->config_device_name = NULL; + } + + if (clean & FREE_UIO_NAME) { + if (nic->flags & NIC_UIO_NAME_MALLOC) { + free(nic->uio_device_name); + nic->uio_device_name = NULL; + + nic->flags &= ~NIC_UIO_NAME_MALLOC; + } + } + + LOG_ERR(PFX "%s: nic closed", nic->log_name); +error: + return; +} + +/** + * nic_iface_init() - This function is used to add an interface to the + * structure cnic_uio + * @return 0 on success, <0 on failure + */ +nic_interface_t *nic_iface_init() +{ + nic_interface_t *nic_iface = malloc(sizeof(*nic_iface)); + if (nic_iface == NULL) { + LOG_ERR("Could not allocate space for nic iface"); + return NULL; + } + + memset(nic_iface, 0, sizeof(*nic_iface)); + nic_iface->next = NULL; + nic_iface->vlan_next = NULL; + nic_iface->iface_num = IFACE_NUM_INVALID; + nic_iface->request_type = IP_CONFIG_OFF; + + return nic_iface; +} + +/** + * nic_add_nic_iface() - This function is used to add an interface to the + * nic structure + * Called with nic_mutex held + * @param nic - struct nic device to add the interface to + * @param nic_iface - network interface used to add to the nic + * @return 0 on success, <0 on failure + */ +int nic_add_nic_iface(nic_t *nic, nic_interface_t *nic_iface) +{ + nic_interface_t *current, *prev; + + /* Make sure it doesn't already exist */ + current = nic_find_nic_iface(nic, nic_iface->protocol, + nic_iface->vlan_id, nic_iface->iface_num, + nic_iface->request_type); + if (current) { + LOG_DEBUG(PFX "%s: nic interface for VLAN: %d, protocol: %d" + " already exist", nic->log_name, nic_iface->vlan_id, + nic_iface->protocol); + return 0; + } + + prev = NULL; + current = nic->nic_iface; + while (current != NULL) { + if (current->protocol == nic_iface->protocol) { + /* Replace parent */ + nic_iface->vlan_next = current; + nic_iface->next = current->next; + current->next = NULL; + if (prev) + prev->next = nic_iface; + else + nic->nic_iface = nic_iface; + goto done; + } + prev = current; + current = current->next; + } + nic_iface->next = nic->nic_iface; + nic->nic_iface = nic_iface; +done: + /* Set nic_interface common fields */ + nic_iface->parent = nic; + memcpy(&nic_iface->ustack.uip_ethaddr.addr, nic->mac_addr, ETH_ALEN); + nic->num_of_nic_iface++; + + LOG_INFO(PFX "%s: Added nic interface for VLAN: %d, protocol: %d", + nic->log_name, nic_iface->vlan_id, nic_iface->protocol); + + return 0; +} + +/****************************************************************************** + * Routine to process interrupts from the NIC device + ******************************************************************************/ +/** + * nic_process_intr() - Routine used to process interrupts from the hardware + * @param nic - NIC hardware to process the interrupt on + * @return 0 on success, <0 on failure + */ +int nic_process_intr(nic_t *nic, int discard_check) +{ + fd_set fdset; + int ret; + int count; + struct timeval tv; + + /* Simple sanity checks */ + if (discard_check != 1 && nic->state != NIC_RUNNING) { + LOG_ERR(PFX "%s: Couldn't process interrupt NIC not running", + nic->log_name); + return -EBUSY; + } + + if (discard_check != 1 && nic->fd == INVALID_FD) { + LOG_ERR(PFX "%s: NIC fd not valid", nic->log_name); + return -EIO; + } + + FD_ZERO(&fdset); + FD_SET(nic->fd, &fdset); + + tv.tv_sec = 0; + pthread_mutex_lock(&nic->nic_mutex); + if (nic->flags & NIC_LONG_SLEEP) + tv.tv_usec = 1000; + else + tv.tv_usec = nic->rx_poll_usec; + pthread_mutex_unlock(&nic->nic_mutex); + + /* Wait for an interrupt to come in or timeout */ + ret = select(nic->fd + 1, &fdset, NULL, NULL, &tv); + switch (ret) { + case 1: + /* Usually there should only be one file descriptor ready + * to read */ + break; + case 0: + return ret; + case -1: + LOG_ERR(PFX "%s: error waiting for interrupt: %s", + nic->log_name, strerror(errno)); + return 0; + default: + LOG_ERR(PFX "%s: unknown number of FD's, ignoring: %d ret", + nic->log_name, ret); + return 0; + } + + ret = read(nic->fd, &count, sizeof(count)); + pthread_mutex_lock(&nic->nic_mutex); + if (ret > 0) { + nic->stats.interrupts++; + LOG_PACKET(PFX "%s: interrupt count: %d prev: %d", + nic->log_name, count, nic->intr_count); + + if (count == nic->intr_count) { + LOG_PACKET(PFX "%s: got interrupt but count still the " + "same", nic->log_name, count); + } + + /* Check if we missed an interrupt. With UIO, + * the count should be incremental */ + if (count != nic->intr_count + 1) { + nic->stats.missed_interrupts++; + LOG_PACKET(PFX "%s: Missed interrupt! on %d not %d", + nic->log_name, count, nic->intr_count); + } + + nic->intr_count = count; + + if (strcmp(nic->ops->description, "qedi")) { + LOG_DEBUG(PFX "%s: host:%d - calling clear_tx_intr from process_intr", + nic->log_name, nic->host_no); + (*nic->ops->clear_tx_intr) (nic); + } + + ret = 1; + } + pthread_mutex_unlock(&nic->nic_mutex); + + return ret; +} + +void prepare_ipv4_packet(nic_t *nic, + nic_interface_t *nic_iface, + struct uip_stack *ustack, packet_t *pkt) +{ + u16_t ipaddr[2]; + arp_table_query_t arp_query; + dest_ipv4_addr_t dest_ipv4_addr; + struct arp_entry *tabptr; + int queue_rc; + int vlan_id = 0; + + /* If the rx vlan tag is not stripped and vlan is present in the pkt, + manual stripping is required because tx is using hw vlan tag! */ + if (pkt->network_layer == pkt->data_link_layer + + sizeof(struct uip_vlan_eth_hdr)) { + /* VLAN is detected in the pkt buf */ + memcpy(pkt->data_link_layer + 12, pkt->network_layer - 2, + pkt->buf_size - sizeof(struct uip_vlan_eth_hdr) + 2); + } + dest_ipv4_addr = uip_determine_dest_ipv4_addr(ustack, ipaddr); + if (dest_ipv4_addr == LOCAL_BROADCAST) { + uip_build_eth_header(ustack, ipaddr, NULL, pkt, vlan_id); + return; + } + + arp_query = is_in_arp_table(ipaddr, &tabptr); + + switch (arp_query) { + case IS_IN_ARP_TABLE: + uip_build_eth_header(ustack, + ipaddr, tabptr, pkt, vlan_id); + break; + case NOT_IN_ARP_TABLE: + queue_rc = nic_queue_tx_packet(nic, nic_iface, pkt); + if (queue_rc) { + LOG_ERR("could not queue TX packet: %d", queue_rc); + } else { + uip_build_arp_request(ustack, ipaddr); + } + break; + default: + LOG_ERR("Unknown arp state"); + break; + } +} + +void prepare_ipv6_packet(nic_t *nic, + nic_interface_t *nic_iface, + struct uip_stack *ustack, packet_t *pkt) +{ + struct uip_eth_hdr *eth; + struct uip_vlan_eth_hdr *eth_vlan; + int vlan_id = 0; + + if (pkt->network_layer == pkt->data_link_layer + + sizeof(struct uip_vlan_eth_hdr)) { + /* VLAN is detected in the pkt buf */ + memcpy(pkt->data_link_layer + 12, pkt->network_layer - 2, + pkt->buf_size - sizeof(struct uip_vlan_eth_hdr) + 2); + } + eth = (struct uip_eth_hdr *)ustack->data_link_layer; + eth_vlan = (struct uip_vlan_eth_hdr *)ustack->data_link_layer; + if (vlan_id == 0) { + eth->type = htons(UIP_ETHTYPE_IPv6); + } else { + eth_vlan->tpid = htons(UIP_ETHTYPE_8021Q); + eth_vlan->vid = htons(vlan_id); + eth_vlan->type = htons(UIP_ETHTYPE_IPv6); + } +} + +void prepare_ustack(nic_t *nic, + nic_interface_t *nic_iface, + struct uip_stack *ustack, struct packet *pkt) +{ + struct ether_header *eth = NULL; + ustack->uip_buf = pkt->buf; + ustack->uip_len = pkt->buf_size; + + pkt->nic = nic; + pkt->nic_iface = nic_iface; + + ustack->data_link_layer = pkt->buf; + /* Adjust the network layer pointer depending if + * there is a VLAN tag or not, or if the hardware + * has stripped out the + * VLAN tag */ + ustack->network_layer = ustack->data_link_layer + + sizeof(struct uip_eth_hdr); + /* Init buffer to be IPv6 */ + if (nic_iface->ustack.ip_config == IPV6_CONFIG_DHCP || + nic_iface->ustack.ip_config == IPV6_CONFIG_STATIC) { + eth = (struct ether_header *)ustack->data_link_layer; + eth->ether_type = htons(UIP_ETHTYPE_IPv6); + } +} + +int do_timers_per_nic_iface(nic_t *nic, nic_interface_t *nic_iface, + struct timer *arp_timer) +{ + packet_t *pkt; + struct uip_stack *ustack = &nic_iface->ustack; + int i; + + pkt = get_next_free_packet(nic); + if (pkt == NULL) + return -EIO; + + if (nic_iface->protocol == AF_INET) { + for (i = 0; i < UIP_UDP_CONNS; i++) { + prepare_ustack(nic, nic_iface, ustack, pkt); + + uip_udp_periodic(ustack, i); + /* If the above function invocation resulted + * in data that should be sent out on the + * network, the global variable uip_len is + * set to a value > 0. */ + if (ustack->uip_len > 0) { + pkt->buf_size = ustack->uip_len; + + prepare_ipv4_packet(nic, nic_iface, ustack, + pkt); + + (*nic->ops->write) (nic, nic_iface, pkt); + ustack->uip_len = 0; + } + } + } else { + /* Added periodic poll for IPv6 NDP engine */ + if (ustack->ndpc != NULL) { /* If engine is active */ + prepare_ustack(nic, nic_iface, ustack, pkt); + + uip_ndp_periodic(ustack); + /* If the above function invocation resulted + * in data that should be sent out on the + * network, the global variable uip_len is + * set to a value > 0. */ + if (ustack->uip_len > 0) { + pkt->buf_size = ustack->uip_len; + prepare_ipv6_packet(nic, nic_iface, ustack, + pkt); + (*nic->ops->write) (nic, nic_iface, pkt); + ustack->uip_len = 0; + } + } + } + /* Call the ARP timer function every 10 seconds. */ + if (timer_expired(arp_timer)) { + timer_reset(arp_timer); + uip_arp_timer(); + } + put_packet_in_free_queue(pkt, nic); + return 0; +} + +static int check_timers(nic_t *nic, + struct timer *periodic_timer, struct timer *arp_timer) +{ + if (timer_expired(periodic_timer)) { + nic_interface_t *nic_iface, *vlan_iface; + + timer_reset(periodic_timer); + + pthread_mutex_lock(&nic->nic_mutex); + + nic_iface = nic->nic_iface; + while (nic_iface != NULL) { + do_timers_per_nic_iface(nic, nic_iface, arp_timer); + vlan_iface = nic_iface->vlan_next; + while (vlan_iface != NULL) { + do_timers_per_nic_iface(nic, vlan_iface, + arp_timer); + vlan_iface = vlan_iface->vlan_next; + } + nic_iface = nic_iface->next; + } + + pthread_mutex_unlock(&nic->nic_mutex); + } + return 0; +} + +int process_packets(nic_t *nic, + struct timer *periodic_timer, + struct timer *arp_timer, nic_interface_t *nic_iface) +{ + int rc; + packet_t *pkt; + + pkt = get_next_free_packet(nic); + if (pkt == NULL) { + LOG_DEBUG(PFX "%s: Couldn't get buffer for processing packet", + nic->log_name); + return -ENOMEM; + } + + pthread_mutex_lock(&nic->nic_mutex); + rc = (*nic->ops->read) (nic, pkt); + pthread_mutex_unlock(&nic->nic_mutex); + + if ((rc != 0) && (pkt->buf_size > 0)) { + uint16_t type = 0; + int af_type = 0; + struct uip_stack *ustack; + uint16_t vlan_id; + + pkt->data_link_layer = pkt->buf; + + vlan_id = pkt->vlan_tag & 0xFFF; + if ((vlan_id == 0) || + (NIC_VLAN_STRIP_ENABLED & nic->flags)) { + struct uip_eth_hdr *hdr = ETH_BUF(pkt->buf); + type = ntohs(hdr->type); + pkt->network_layer = pkt->data_link_layer + + sizeof(struct uip_eth_hdr); + } else { + struct uip_vlan_eth_hdr *hdr = VLAN_ETH_BUF(pkt->buf); + type = ntohs(hdr->type); + pkt->network_layer = pkt->data_link_layer + + sizeof(struct uip_vlan_eth_hdr); + } + + switch (type) { + case UIP_ETHTYPE_IPv6: + af_type = AF_INET6; + break; + case UIP_ETHTYPE_IPv4: + case UIP_ETHTYPE_ARP: + af_type = AF_INET; + LOG_DEBUG(PFX "%s: ARP or IPv4 vlan:0x%x ethertype:0x%x", + nic->log_name, vlan_id, type); + break; + default: + LOG_DEBUG(PFX "%s: Ignoring vlan:0x%x ethertype:0x%x", + nic->log_name, vlan_id, type); + goto done; + } + + pthread_mutex_lock(&nic->nic_mutex); + + /* check if we have the given VLAN interface */ + if (nic_iface != NULL) { + if (vlan_id != nic_iface->vlan_id) { + /* Matching nic_iface not found, drop */ + pthread_mutex_unlock(&nic->nic_mutex); + rc = EINVAL; /* Return the +error code */ + goto done; + } + goto nic_iface_present; + } + + /* Best effort to find the correct instance + Input: protocol and vlan_tag */ + nic_iface = nic_find_nic_iface(nic, af_type, vlan_id, + IFACE_NUM_INVALID, + IP_CONFIG_OFF); + if (nic_iface == NULL) { + /* Matching nic_iface not found */ + pthread_mutex_unlock(&nic->nic_mutex); + LOG_DEBUG(PFX "%s: Couldn't find interface for " + "VLAN: %d af_type %d", + nic->log_name, vlan_id, af_type); + rc = EINVAL; /* Return the +error code */ + goto done; + } +nic_iface_present: + pkt->nic_iface = nic_iface; + LOG_DEBUG(PFX "%s: found nic iface, type=0x%x, bufsize=%d", + nic->log_name, type, pkt->buf_size); + + ustack = &nic_iface->ustack; + + ustack->uip_buf = pkt->buf; + ustack->uip_len = pkt->buf_size; + ustack->data_link_layer = pkt->buf; + + /* Adjust the network layer pointer depending if there is a + * VLAN tag or not, or if the hardware has stripped out the + * VLAN tag */ + if ((vlan_id == 0) || + (NIC_VLAN_STRIP_ENABLED & nic->flags)) + ustack->network_layer = ustack->data_link_layer + + sizeof(struct uip_eth_hdr); + else + ustack->network_layer = ustack->data_link_layer + + sizeof(struct uip_vlan_eth_hdr); + + /* determine how we should process this packet based on the + * ethernet type */ + switch (type) { + case UIP_ETHTYPE_IPv6: + uip_input(ustack); + if (ustack->uip_len > 0) { + /* The pkt generated has already consulted + the IPv6 ARP table */ + pkt->buf_size = ustack->uip_len; + prepare_ipv6_packet(nic, nic_iface, + ustack, pkt); + + (*nic->ops->write) (nic, nic_iface, pkt); + } + break; + case UIP_ETHTYPE_IPv4: + uip_arp_ipin(ustack, pkt); + uip_input(ustack); + /* If the above function invocation resulted + * in data that should be sent out on the + * network, the global variable uip_len is + * set to a value > 0. */ + if (ustack->uip_len > 0) { + pkt->buf_size = ustack->uip_len; + prepare_ipv4_packet(nic, nic_iface, + ustack, pkt); + + LOG_DEBUG(PFX "%s: write called after arp_ipin, uip_len=%d", + nic->log_name, ustack->uip_len); + (*nic->ops->write) (nic, nic_iface, pkt); + } + + break; + case UIP_ETHTYPE_ARP: + uip_arp_arpin(nic_iface, ustack, pkt); + + /* If the above function invocation resulted + * in data that should be sent out on the + * network, the global variable uip_len + * is set to a value > 0. */ + if (pkt->buf_size > 0) { + pkt->buf_size = ustack->uip_len; + LOG_DEBUG(PFX "%s: write called after arp_arpin, bufsize=%d", + nic->log_name, pkt->buf_size); + (*nic->ops->write) (nic, nic_iface, pkt); + } + break; + } + ustack->uip_len = 0; + pthread_mutex_unlock(&nic->nic_mutex); + } + +done: + put_packet_in_free_queue(pkt, nic); + + return rc; +} + +static int process_dhcp_loop(nic_t *nic, + nic_interface_t *nic_iface, + struct timer *periodic_timer, + struct timer *arp_timer) +{ + struct dhcpc_state *s; + struct ndpc_state *n; + int rc; + struct timeval start_time; + struct timeval current_time; + struct timeval wait_time; + struct timeval total_time; + + /* 10s loop time to wait for DHCP */ + switch (nic_iface->ustack.ip_config) { + case IPV4_CONFIG_DHCP: + wait_time.tv_sec = 10; + break; + case IPV6_CONFIG_DHCP: + wait_time.tv_sec = 15; + break; + case IPV6_CONFIG_STATIC: + wait_time.tv_sec = 4; + break; + default: + wait_time.tv_sec = 2; + } + wait_time.tv_usec = 0; + + s = nic_iface->ustack.dhcpc; + n = nic_iface->ustack.ndpc; + + if (gettimeofday(&start_time, NULL)) { + LOG_ERR(PFX "%s: Couldn't get time of day to start DHCP timer", + nic->log_name); + return -EIO; + } + + timeradd(&start_time, &wait_time, &total_time); + + periodic_timer->start = periodic_timer->start - + periodic_timer->interval; + + while ((event_loop_stop == 0) && + (nic->flags & NIC_ENABLED) && !(nic->flags & NIC_GOING_DOWN)) { + + if (nic_iface->ustack.ip_config == IPV4_CONFIG_DHCP) { + if (s->state == STATE_CONFIG_RECEIVED) + break; + } + if (nic_iface->ustack.ip_config == IPV6_CONFIG_DHCP || + nic_iface->ustack.ip_config == IPV6_CONFIG_STATIC) { + if (n->state == NDPC_STATE_BACKGROUND_LOOP) + break; + } + + /* Check the periodic and ARP timer */ + check_timers(nic, periodic_timer, arp_timer); + + rc = nic_process_intr(nic, 1); + + while ((rc > 0) && (!(nic->flags & NIC_GOING_DOWN))) { + rc = process_packets(nic, + periodic_timer, + arp_timer, nic_iface); + } + + if (gettimeofday(¤t_time, NULL)) { + LOG_ERR(PFX "%s: Couldn't get current time for " + "DHCP start", nic->log_name); + return -EIO; + } + + if (timercmp(&total_time, ¤t_time, <)) { + LOG_ERR(PFX "%s: timeout waiting for DHCP/NDP", + nic->log_name); + if (nic_iface->ustack.ip_config == IPV6_CONFIG_DHCP || + nic_iface->ustack.ip_config == IPV6_CONFIG_STATIC) + n->retry_count = IPV6_MAX_ROUTER_SOL_RETRY; + return -EIO; + } + } + + if (nic->flags & NIC_GOING_DOWN) + return -EIO; + else if (nic->flags & NIC_DISABLED) + return -EINVAL; + else + return 0; +} + +/* Called with nic_mutex locked */ +static int do_acquisition(nic_t *nic, nic_interface_t *nic_iface, + struct timer *periodic_timer, struct timer *arp_timer) +{ + struct in_addr addr; + struct in6_addr addr6; + char buf[INET6_ADDRSTRLEN]; + int rc = -1; + + /* New acquisition */ + uip_init(&nic_iface->ustack, nic->flags & NIC_IPv6_ENABLED); + memcpy(&nic_iface->ustack.uip_ethaddr.addr, nic->mac_addr, ETH_ALEN); + + LOG_INFO(PFX "%s: Initialized ip stack: VLAN: %d", + nic->log_name, nic_iface->vlan_id); + + LOG_INFO(PFX "%s: mac: %02x:%02x:%02x:%02x:%02x:%02x", + nic->log_name, + nic_iface->mac_addr[0], + nic_iface->mac_addr[1], + nic_iface->mac_addr[2], + nic_iface->mac_addr[3], + nic_iface->mac_addr[4], + nic_iface->mac_addr[5]); + + switch (nic_iface->ustack.ip_config) { + case IPV4_CONFIG_STATIC: + memcpy(&addr.s_addr, nic_iface->ustack.hostaddr, + sizeof(addr.s_addr)); + + LOG_INFO(PFX "%s: Using IP address: %s", + nic->log_name, inet_ntoa(addr)); + + memcpy(&addr.s_addr, nic_iface->ustack.netmask, + sizeof(addr.s_addr)); + + LOG_INFO(PFX "%s: Using netmask: %s", + nic->log_name, inet_ntoa(addr)); + + set_uip_stack(&nic_iface->ustack, + NULL, NULL, NULL, + nic_iface->mac_addr); + break; + + case IPV4_CONFIG_DHCP: + set_uip_stack(&nic_iface->ustack, + NULL, NULL, NULL, + nic_iface->mac_addr); + if (dhcpc_init(nic, &nic_iface->ustack, + nic_iface->mac_addr, ETH_ALEN)) { + if (nic_iface->ustack.dhcpc) { + LOG_DEBUG(PFX "%s: DHCPv4 engine already " + "initialized!", nic->log_name); + goto skip; + } else { + LOG_DEBUG(PFX "%s: DHCPv4 engine failed " + "initialization!", nic->log_name); + goto error; + } + } + pthread_mutex_unlock(&nic->nic_mutex); + rc = process_dhcp_loop(nic, nic_iface, periodic_timer, + arp_timer); + pthread_mutex_lock(&nic->nic_mutex); + + if (rc) { + LOG_ERR(PFX "%s: DHCP failed", nic->log_name); + /* For DHCPv4 failure, the ustack must be cleaned so + it can re-acquire on the next iscsid request */ + uip_reset(&nic_iface->ustack); + goto error; + } + + if (nic->flags & NIC_DISABLED) { + /* Break out of this loop */ + break; + } + + LOG_INFO(PFX "%s: Initialized dhcp client", nic->log_name); + break; + + case IPV6_CONFIG_DHCP: + case IPV6_CONFIG_STATIC: + if (ndpc_init(nic, &nic_iface->ustack, nic_iface->mac_addr, + ETH_ALEN)) { + LOG_DEBUG(PFX "%s: IPv6 engine already initialized!", + nic->log_name); + goto skip; + } + pthread_mutex_unlock(&nic->nic_mutex); + rc = process_dhcp_loop(nic, nic_iface, periodic_timer, + arp_timer); + pthread_mutex_lock(&nic->nic_mutex); + if (rc) { + /* Don't reset and allow to use RA and LL */ + LOG_ERR(PFX "%s: IPv6 DHCP/NDP failed", nic->log_name); + } + if (nic_iface->ustack.ip_config == IPV6_CONFIG_STATIC) { + memcpy(&addr6.s6_addr, nic_iface->ustack.hostaddr6, + sizeof(addr6.s6_addr)); + inet_ntop(AF_INET6, addr6.s6_addr, buf, sizeof(buf)); + LOG_INFO(PFX "%s: hostaddr IP: %s", nic->log_name, buf); + memcpy(&addr6.s6_addr, nic_iface->ustack.netmask6, + sizeof(addr6.s6_addr)); + inet_ntop(AF_INET6, addr6.s6_addr, buf, sizeof(buf)); + LOG_INFO(PFX "%s: netmask IP: %s", nic->log_name, buf); + } + break; + + default: + LOG_INFO(PFX "%s: ipconfig = %d?", nic->log_name, + nic_iface->ustack.ip_config); + } +skip: + /* Mark acquisition done for this nic iface */ + nic_iface->flags &= ~NIC_IFACE_ACQUIRE; + + LOG_INFO(PFX "%s: enabled vlan %d protocol: %d", nic->log_name, + nic_iface->vlan_id, nic_iface->protocol); + return 0; + +error: + return -EIO; +} + + +void *nic_loop(void *arg) +{ + nic_t *nic = (nic_t *) arg; + int rc = -1; + sigset_t set; + struct timer periodic_timer, arp_timer; + + sigfillset(&set); + rc = pthread_sigmask(SIG_BLOCK, &set, NULL); + if (rc != 0) { + /* TODO: determine if we need to exit this thread if we fail + * to set the signal mask */ + LOG_ERR(PFX "%s: Couldn't set signal mask", nic->log_name); + } + + /* Signal the device to enable itself */ + pthread_mutex_lock(&nic->nic_mutex); + pthread_cond_signal(&nic->nic_loop_started_cond); + + /* nic_mutex must be locked */ + while ((event_loop_stop == 0) && + !(nic->flags & NIC_EXIT_MAIN_LOOP) && + !(nic->flags & NIC_GOING_DOWN)) { + nic_interface_t *nic_iface, *vlan_iface; + + if (nic->flags & NIC_DISABLED) { + LOG_DEBUG(PFX "%s: Waiting to be enabled", + nic->log_name); + + /* Wait for the device to be enabled */ + /* nic_mutex is already locked */ + pthread_cond_wait(&nic->enable_wait_cond, + &nic->nic_mutex); + + if (nic->state == NIC_EXIT) { + pthread_mutex_unlock(&nic->nic_mutex); + pthread_exit(NULL); + } + LOG_DEBUG(PFX "%s: is now enabled", nic->log_name); + } + /* initialize the device to send/rec data */ + rc = (*nic->ops->open) (nic); + if (rc != 0) { + LOG_ERR(PFX "%s: Could not initialize CNIC UIO device", + nic->log_name); + + if (rc == -ENOTSUP) + nic->flags |= NIC_EXIT_MAIN_LOOP; + else + nic->flags &= ~NIC_ENABLED; + + /* Signal that the device enable is done */ + pthread_cond_broadcast(&nic->enable_done_cond); + pthread_mutex_unlock(&nic->nic_mutex); + goto dev_close; + } + nic_set_all_nic_iface_mac_to_parent(nic); + pthread_mutex_unlock(&nic->nic_mutex); + + rc = alloc_free_queue(nic, 5); + if (rc != 5) { + if (rc != 0) { + LOG_WARN(PFX "%s: Allocated %d packets " + "instead of %d", nic->log_name, rc, 5); + } else { + LOG_ERR(PFX "%s: No packets allocated " + "instead of %d", nic->log_name, 5); + /* Signal that the device enable is done */ + pthread_cond_broadcast(&nic->enable_done_cond); + goto dev_close; + } + } + /* Indication for the nic_disable routine that the nic + has started running */ + nic->state = NIC_STARTED_RUNNING; + + /* Initialize the system clocks */ + timer_set(&periodic_timer, CLOCK_SECOND / 2); + timer_set(&arp_timer, CLOCK_SECOND * 10); + + /* Prepare the stack for each of the VLAN interfaces */ + pthread_mutex_lock(&nic->nic_mutex); + + /* If DHCP fails, exit loop and restart the engine */ + nic_iface = nic->nic_iface; + while (nic_iface != NULL) { + if (nic_iface->flags & NIC_IFACE_ACQUIRE) { + do_acquisition(nic, nic_iface, + &periodic_timer, + &arp_timer); + } + vlan_iface = nic_iface->vlan_next; + while (vlan_iface != NULL) { + if (vlan_iface->flags & NIC_IFACE_ACQUIRE) { + do_acquisition(nic, vlan_iface, + &periodic_timer, + &arp_timer); + } + vlan_iface = vlan_iface->next; + } + nic_iface = nic_iface->next; + } + if (nic->flags & NIC_DISABLED) { + LOG_WARN(PFX "%s: nic was disabled during nic loop, " + "closing flag 0x%x", + nic->log_name, nic->flags); + /* Signal that the device enable is done */ + pthread_cond_broadcast(&nic->enable_done_cond); + pthread_mutex_unlock(&nic->nic_mutex); + goto dev_close_free; + } + + /* This is when we start the processing of packets */ + nic->start_time = time(NULL); + nic->state = NIC_RUNNING; + + nic->flags &= ~NIC_ENABLED_PENDING; + + /* Signal that the device enable is done */ + pthread_cond_broadcast(&nic->enable_done_cond); + + LOG_INFO(PFX "%s: entering main nic loop", nic->log_name); + + while ((nic->state == NIC_RUNNING) && + (event_loop_stop == 0) && + !(nic->flags & NIC_GOING_DOWN)) { + pthread_mutex_unlock(&nic->nic_mutex); + /* Check the periodic and ARP timer */ + check_timers(nic, &periodic_timer, &arp_timer); + rc = nic_process_intr(nic, 0); + while ((rc > 0) && + (nic->state == NIC_RUNNING) && + !(nic->flags & NIC_GOING_DOWN)) { + rc = process_packets(nic, + &periodic_timer, + &arp_timer, NULL); + } + pthread_mutex_lock(&nic->nic_mutex); + } + + LOG_INFO(PFX "%s: exited main processing loop", nic->log_name); + +dev_close_free: + free_free_queue(nic); +dev_close: + + if (nic->flags & NIC_GOING_DOWN) { + nic_close(nic, 1, FREE_NO_STRINGS); + + nic->flags &= ~NIC_GOING_DOWN; + } else { + pthread_mutex_destroy(&nic->xmit_mutex); + pthread_mutex_init(&nic->xmit_mutex, NULL); + } + nic->pending_count = 0; + + if (!(nic->flags & NIC_EXIT_MAIN_LOOP)) { + /* Signal we are done closing CNIC/UIO device */ + pthread_cond_broadcast(&nic->disable_wait_cond); + } + } + /* clean up the nic flags */ + nic->flags &= ~NIC_ENABLED_PENDING; + + pthread_mutex_unlock(&nic->nic_mutex); + + LOG_INFO(PFX "%s: nic loop thread exited", nic->log_name); + + nic->thread = INVALID_THREAD; + + pthread_exit(NULL); +} diff --git a/iscsiuio/src/unix/nic.h b/iscsiuio/src/unix/nic.h new file mode 100644 index 0000000..2c41e6e --- /dev/null +++ b/iscsiuio/src/unix/nic.h @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * nic.h - NIC header file + * + */ + +#include + +#ifndef __NIC_H__ +#define __NIC_H__ + +#include +#include +#include +#include +#include +#include + +#include "nic_nl.h" +#include "packet.h" +#include "uip.h" +#include "timer.h" + +#include "iscsi_if.h" + +/* Foward declarations */ +struct nic_ops; +struct nic_lib_handle; +struct packet; +struct nic_op; + +extern pthread_mutex_t nic_lib_list_mutex; +extern struct nic_lib_handle *nic_lib_list; + +/* Used to store a list of active cnic devices */ +extern pthread_mutex_t nic_list_mutex; +extern struct nic *nic_list; + +extern void *nl_process_handle_thread(void *arg); + +/******************************************************************************* + * Constants + ******************************************************************************/ +#define MAX_PCI_DEVICE_ENTRIES 64 /* Maxium number of pci_device_id + entries a hw library may contain */ + +#define FREE_CONFIG_NAME 0x0001 +#define FREE_UIO_NAME 0x0002 +#define FREE_ALL_STRINGS (FREE_CONFIG_NAME | FREE_UIO_NAME) +#define FREE_NO_STRINGS 0x0000 + +/****************************************************************************** + * Enumerations + ******************************************************************************/ +typedef enum { + ALLOW_GRACEFUL_SHUTDOWN = 1, + FORCE_SHUTDOWN = 2, +} NIC_SHUTDOWN_T; + +/******************************************************************************* + * Structure used to hold PCI vendor, device, subvendor and subdevice ID's + ******************************************************************************/ +struct pci_device_id { + const uint32_t vendor, device; /* Vendor and device ID or PCI_ANY_ID */ + const uint32_t subvendor, subdevice; /* Subsystem ID's/PCI_ANY_ID */ + const char *device_name; /* Data private to the driver */ +}; + +/****************************************************************************** + * NIC statistics structure + ******************************************************************************/ +struct nic_stats { + uint64_t interrupts; + uint64_t missed_interrupts; + + struct { + uint64_t packets; + uint64_t bytes; + } tx; + + struct { + uint64_t packets; + uint64_t bytes; + } rx; +}; + +/****************************************************************************** + * NIC interface structure + ******************************************************************************/ +typedef struct nic_interface { + struct nic_interface *vlan_next; + struct nic_interface *next; + struct nic *parent; + + uint16_t protocol; + uint16_t flags; +#define NIC_IFACE_PERSIST (1<<0) +#define NIC_IFACE_ACQUIRE (1<<1) +#define NIC_IFACE_PATHREQ_WAIT1 (1<<2) +#define NIC_IFACE_PATHREQ_WAIT2 (1<<3) +#define NIC_IFACE_PATHREQ_WAIT (NIC_IFACE_PATHREQ_WAIT1 | \ + NIC_IFACE_PATHREQ_WAIT2) + uint8_t mac_addr[ETH_ALEN]; + uint8_t vlan_priority; + uint16_t vlan_id; +#define NO_VLAN 0x8000 + + uint16_t mtu; + time_t start_time; + + struct uip_stack ustack; + +#define IFACE_NUM_PRESENT (1<<0) +#define IFACE_NUM_INVALID -1 + int iface_num; + int request_type; +} nic_interface_t; + +/****************************************************************************** + * NIC lib operations structure + ******************************************************************************/ +struct nic_lib_ops { + /* Used to get the NIC library name */ + void (*get_library_name) (char **library_name, + size_t *library_name_size); + + /* Used to get to the PCI table supported by the NIC library */ + void (*get_pci_table) (struct pci_device_id **table, + uint32_t *entries); + + /* Used to get the version of this NIC library */ + void (*get_library_version) (char **version_string, + size_t *version_string_size); + + /* Used to get the NIC library build date */ + void (*get_build_date) (char **build_date_string, + size_t *build_date_string_size); + + /* Used to get the transport name assoicated with this library */ + void (*get_transport_name) (char **transport_name, + size_t *transport_name_size); + + /* Used to get the uio name assoicated with this library */ + void (*get_uio_name) (char **uio_name, size_t *uio_name_size); + +}; + +/******************************************************************************* + * NIC op table definition + ******************************************************************************/ +typedef struct nic_ops { + struct nic_lib_ops lib_ops; + + char *description; + int (*open) (struct nic *); + int (*close) (struct nic *, NIC_SHUTDOWN_T); + int (*read) (struct nic *, struct packet *); + int (*write) (struct nic *, nic_interface_t *, struct packet *); + void *(*get_tx_pkt) (struct nic *); + void (*start_xmit) (struct nic *, size_t, u16_t vlan_id); + int (*clear_tx_intr) (struct nic *); + int (*handle_iscsi_path_req) (struct nic *, + int, + struct iscsi_uevent *ev, + struct iscsi_path *path, + nic_interface_t *nic_iface); +} net_ops_t; + +typedef struct nic_lib_handle { + struct nic_lib_handle *next; + + pthread_mutex_t mutex; + struct nic_ops *ops; +} nic_lib_handle_t; + +typedef struct nic { + struct nic *next; + + uint32_t flags; +#define NIC_UNITIALIZED 0x0001 +#define NIC_INITIALIZED 0x0002 +#define NIC_ENABLED 0x0004 +#define NIC_DISABLED 0x0008 +#define NIC_IPv6_ENABLED 0x0010 +#define NIC_ADDED_MULICAST 0x0020 +#define NIC_LONG_SLEEP 0x0040 +#define NIC_PATHREQ_WAIT 0x0080 + +#define NIC_VLAN_STRIP_ENABLED 0x0100 +#define NIC_MSIX_ENABLED 0x0200 +#define NIC_TX_HAS_SENT 0x0400 +#define NIC_ENABLED_PENDING 0x0800 + +#define NIC_UIO_NAME_MALLOC 0x1000 +#define NIC_CONFIG_NAME_MALLOC 0x2000 +#define NIC_EXIT_MAIN_LOOP 0x4000 +#define NIC_GOING_DOWN 0x8000 +#define NIC_RESET_UIP 0x10000 + + uint16_t state; +#define NIC_STOPPED 0x0001 +#define NIC_STARTED_RUNNING 0x0002 +#define NIC_RUNNING 0x0004 +#define NIC_EXIT 0x0010 + + int fd; /* Holds the file descriptor to UIO */ + uint16_t uio_minor; /* Holds the UIO minor number */ + + uint32_t host_no; /* Holds the associated host number */ + + char *library_name; /* Name of the library to assoicate with */ + char *log_name; /* Human friendly name used in the log + file */ + char *config_device_name; /* Name read from the XML configuration + file */ + char eth_device_name[IFNAMSIZ]; /* Network interface name */ + char *uio_device_name; /* UIO device name */ + + uint32_t intr_count; /* Total UIO interrupt count */ + + int page_size; + + /* Held for nic ops manipulation */ + pthread_mutex_t nic_mutex; + + /* iSCSI ring ethernet MAC address */ + __u8 mac_addr[ETH_ALEN]; + + /* Used to manage the network interfaces of this device */ + __u32 num_of_nic_iface; + nic_interface_t *nic_iface; + + /* Wait for the device to be enabled */ + pthread_cond_t enable_wait_cond; + + /* Wait for the device to be finished enabled */ + pthread_cond_t enable_done_cond; + + /* Wait for the nic loop to start */ + pthread_cond_t nic_loop_started_cond; + + /* Wait for the device to be disabled */ + pthread_cond_t disable_wait_cond; + + /* Held when transmitting */ + pthread_mutex_t xmit_mutex; + + /* The thread this device is running on */ + pthread_t thread; + + /* The thread used to enable the device */ + pthread_t enable_thread; + + /* Statistical Information on this device */ + time_t start_time; + struct nic_stats stats; + + /* Number of retrys from iscsid */ + uint32_t pending_count; + uint32_t pathreq_pending_count; + +#define DEFAULT_RX_POLL_USEC 100 /* usec */ + /* options enabled by the user */ + uint32_t rx_poll_usec; + + /* Used to hold hardware specific data */ + void *priv; + + /* Used to hold the TX packets that are needed to be sent */ + struct packet *tx_packet_queue; + + /* Mutex to protect the list of free packets */ + pthread_mutex_t free_packet_queue_mutex; + + /* Used to hold the free packets that are needed to be sent */ + struct packet *free_packet_queue; + + /* Points to the NIC library */ + nic_lib_handle_t *nic_library; + + /* Points to the PCI table entry */ + struct pci_device_id *pci_id; + + /* Used to process the interrupt */ + int (*process_intr) (struct nic *nic); + + struct nic_ops *ops; + + /* NL processing parameters */ + pthread_t nl_process_thread; + pthread_cond_t nl_process_cond; + pthread_cond_t nl_process_if_down_cond; + pthread_mutex_t nl_process_mutex; + int nl_process_if_down; + int nl_process_head; + int nl_process_tail; +#define NIC_NL_PROCESS_MAX_RING_SIZE 128 +#define NIC_NL_PROCESS_LAST_ENTRY (NIC_NL_PROCESS_MAX_RING_SIZE - 1) +#define NIC_NL_PROCESS_NEXT_ENTRY(x) ((x + 1) & NIC_NL_PROCESS_MAX_RING_SIZE) + void *nl_process_ring[NIC_NL_PROCESS_MAX_RING_SIZE]; + + /* The thread used to perform ping */ + pthread_t ping_thread; + uint64_t transport_handle; +} nic_t; + +/****************************************************************************** + * Function Prototypes + *****************************************************************************/ +int load_all_nic_libraries(); + +nic_t *nic_init(); +void nic_add(nic_t *nic); +int nic_remove(nic_t *nic); + +int nic_add_nic_iface(nic_t *nic, nic_interface_t *nic_iface); +int nic_process_intr(nic_t *nic, int discard_check); + +nic_interface_t *nic_iface_init(); + +typedef enum { + NIC_LIBRARY_EXSITS = 1, + NIC_LIBRARY_DOESNT_EXIST = 2, +} NIC_LIBRARY_EXIST_T; + +NIC_LIBRARY_EXIST_T does_nic_uio_name_exist(char *name, + nic_lib_handle_t **handle); +NIC_LIBRARY_EXIST_T does_nic_library_exist(char *name, + nic_lib_handle_t **handle); + +/******************************************************************************* + * Packet management utility functions + ******************************************************************************/ +struct packet *get_next_tx_packet(nic_t *nic); +struct packet *get_next_free_packet(nic_t *nic); +void put_packet_in_tx_queue(struct packet *pkt, nic_t *nic); +void put_packet_in_free_queue(struct packet *pkt, nic_t *nic); + +int unload_all_nic_libraries(); +void nic_close(nic_t *nic, NIC_SHUTDOWN_T graceful, int clean); + +/* Use this function to fill in minor number and uio, and eth names */ +int nic_fill_name(nic_t *nic); + +int enable_multicast(nic_t *nic); +int disable_multicast(nic_t *nic); + +void nic_set_all_nic_iface_mac_to_parent(nic_t *nic); +int find_nic_lib_using_pci_id(uint32_t vendor, uint32_t device, + uint32_t subvendor, uint32_t subdevice, + nic_lib_handle_t **handle, + struct pci_device_id **pci_entry); + +void *nic_loop(void *arg); + +int nic_packet_capture(struct nic *, struct packet *pkt); + +int process_packets(nic_t *nic, + struct timer *periodic_timer, + struct timer *arp_timer, nic_interface_t *nic_iface); + +void prepare_ustack(nic_t *nic, + nic_interface_t *nic_iface, + struct uip_stack *ustack, struct packet *pkt); + +void prepare_ipv4_packet(nic_t *nic, + nic_interface_t *nic_iface, + struct uip_stack *ustack, struct packet *pkt); + +void prepare_ipv6_packet(nic_t *nic, + nic_interface_t *nic_iface, + struct uip_stack *ustack, struct packet *pkt); + +#endif /* __NIC_H__ */ diff --git a/iscsiuio/src/unix/nic_id.c b/iscsiuio/src/unix/nic_id.c new file mode 100644 index 0000000..6da0a38 --- /dev/null +++ b/iscsiuio/src/unix/nic_id.c @@ -0,0 +1,364 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * nic_id.c - Using sysfs to determine the PCI vendor, device, subvendor and + * subdevice ID's + * + */ +#include +#include +#include +#include +#include +#include + +#include "logger.h" +#include "nic.h" + +#define PFX "nic_id " + +/******************************************************************************* + * Sysfs constant strings used to get PCI vendor, and device ID's + ******************************************************************************/ +const char uio_vendor_id_template[] = "/sys/class/uio/uio%d/device/vendor"; +const char uio_subvendor_id_template[] = + "/sys/class/uio/uio%d/device/subsystem_vendor"; +const char uio_device_id_template[] = "/sys/class/uio/uio%d/device/device"; +const char uio_subdevice_id_template[] = + "/sys/class/uio/uio%d/device/subsystem_device"; +const char uio_device_symlink_template[] = "/sys/class/uio/uio%d/device"; + +/** + * get_id() - Utility function to read hex values from sysfs + * @param nic - NIC device to use + * @param sysfs_template - sysfs path template to use + * @param sysfs_template_size - sysfs path template size in bytes + * @parm id - this is the value returned from the sysfs entry + * @return 0 on success <0 on failure + */ +static int get_id(nic_t *nic, + const char *sysfs_template, + const size_t sysfs_template_size, uint32_t *id) +{ + int rc = 0; + FILE *fp; + size_t chars_read; + char buf[7]; + char *path; + size_t path_size; + + path_size = sysfs_template_size + 4; + path = malloc(path_size); + if (path == NULL) { + LOG_ERR("Could not allocate memory for %s", sysfs_template); + return -ENOMEM; + } + + snprintf(path, path_size, sysfs_template, nic->uio_minor); + + fp = fopen(path, "r"); + if (fp == NULL) { + LOG_ERR(PFX "%s: Could not open path: %s [%s]", + nic->log_name, path, strerror(errno)); + rc = -EIO; + goto error_fopen; + } + + chars_read = fread(buf, sizeof(buf), 1, fp); + if (chars_read != 1) { + LOG_ERR(PFX "%s: Could not read from: %s [%s]", + nic->log_name, path, strerror(ferror(fp))); + rc = -EIO; + goto error; + } + + chars_read = sscanf(buf, "%x", id); + if (chars_read != 1) { + LOG_ERR(PFX "%s: Could interpret value: %s from: %s [%s]", + nic->log_name, buf, path, strerror(errno)); + rc = -EIO; + goto error; + } + +error: + fclose(fp); + +error_fopen: + free(path); + + return rc; +} + +static int get_vendor(nic_t *nic, uint32_t *id) +{ + return get_id(nic, + uio_vendor_id_template, sizeof(uio_vendor_id_template), + id); +} + +static int get_subvendor(nic_t *nic, uint32_t *id) +{ + return get_id(nic, + uio_subvendor_id_template, + sizeof(uio_subvendor_id_template), id); +} + +static int get_device(nic_t *nic, uint32_t *id) +{ + return get_id(nic, + uio_device_id_template, + sizeof(uio_device_id_template), id); +} + +static int get_subdevice(nic_t *nic, uint32_t *id) +{ + return get_id(nic, + uio_subdevice_id_template, + sizeof(uio_subdevice_id_template), id); +} + +int get_bus_slot_func_num(nic_t *nic, + uint32_t *bus, uint32_t *slot, uint32_t *func) +{ + size_t size; + char *path, *tok, *tok2; + int path_tokens, i; + size_t path_size; + char *read_pci_bus_slot_func_str; + char pci_bus_slot_func_str[32]; + int rc; + char *saveptr; + + path_size = sizeof(uio_device_symlink_template) + 4; + path = malloc(path_size); + if (path == NULL) { + LOG_ERR(PFX "%s: Could not allocate path memory for %s", + nic->log_name, uio_device_symlink_template); + rc = -ENOMEM; + goto error_alloc_path; + } + + read_pci_bus_slot_func_str = malloc(128); + if (read_pci_bus_slot_func_str == NULL) { + LOG_ERR(PFX "%s: Could not allocate read pci bus memory for %s", + nic->log_name, uio_device_symlink_template); + rc = -ENOMEM; + goto error_alloc_read_pci_bus; + } + + snprintf(path, path_size, uio_device_symlink_template, nic->uio_minor); + + size = readlink(path, read_pci_bus_slot_func_str, 128); + if (size == -1) { + LOG_ERR(PFX "%s: Error with %s: %s", + nic->log_name, path, strerror(errno)); + rc = errno; + goto error; + } + + if (size > ((128) - 1)) { + read_pci_bus_slot_func_str[128 - 1] = '\0'; + LOG_ERR(PFX "%s: not enough space (%d) for reading PCI " + "slot:bus.func %s: %s", + nic->log_name, size, path, strerror(errno)); + rc = -EIO; + goto error; + } + + /* readlink() doesn't NULL terminate the string */ + read_pci_bus_slot_func_str[size] = '\0'; + + path_tokens = 0; + tok = strtok_r(read_pci_bus_slot_func_str, "/", &saveptr); + while (tok != NULL) { + path_tokens++; + tok = strtok_r(NULL, "/", &saveptr); + } + + size = readlink(path, read_pci_bus_slot_func_str, 128); + if (size == -1) { + LOG_ERR(PFX "%s: Error with %s: %s", + nic->log_name, path, strerror(errno)); + rc = errno; + goto error; + } + + if (size > ((128) - 1)) { + read_pci_bus_slot_func_str[128 - 1] = '\0'; + LOG_ERR(PFX "%s: not enough space for reading PCI " + "slot:bus.func %s: %s", + nic->log_name, path, strerror(errno)); + rc = -EIO; + goto error; + } + + /* readlink() doesn't NULL terminate the string */ + read_pci_bus_slot_func_str[size] = '\0'; + + tok = strtok_r(read_pci_bus_slot_func_str, "/", &saveptr); + for (i = 0; i < path_tokens - 1; i++) + tok = strtok_r(NULL, "/", &saveptr); + strcpy(pci_bus_slot_func_str, tok); + + tok = strtok_r(pci_bus_slot_func_str, ":", &saveptr); + if (tok == NULL) { + LOG_ERR(PFX "%s: Error with slot string: %s", + nic->log_name, pci_bus_slot_func_str); + rc = -EIO; + goto error; + } + + tok = strtok_r(NULL, ":", &saveptr); + if (tok == NULL) { + LOG_ERR(PFX "%s: Error parsing slot: %s", + nic->log_name, pci_bus_slot_func_str); + rc = -EIO; + goto error; + } + + sscanf(tok, "%x", bus); + + /* Need to extract the next token "xx.x" */ + tok = strtok_r(NULL, ":", &saveptr); + if (tok == NULL) { + LOG_ERR(PFX "%s: Error extracing bus.func: %s", + nic->log_name, pci_bus_slot_func_str); + rc = -EIO; + goto error; + } + + tok2 = strtok_r(tok, ".", &saveptr); + if (tok2 == NULL) { + LOG_ERR(PFX "%s: Error parsing bus: %s", + nic->log_name, pci_bus_slot_func_str); + rc = -EIO; + goto error; + } + + sscanf(tok2, "%x", slot); + + tok2 = strtok_r(NULL, ".", &saveptr); + if (tok2 == NULL) { + LOG_ERR(PFX "%s: Error parsing func: %s", + nic->log_name, pci_bus_slot_func_str); + rc = -EIO; + goto error; + } + + sscanf(tok2, "%x", func); + LOG_INFO(PFX "%s: is found at %02x:%02x.%02x", nic->log_name, + *bus, *slot, *func); + rc = 0; +error: + free(read_pci_bus_slot_func_str); +error_alloc_read_pci_bus: + free(path); +error_alloc_path: + return rc; +} + +/** + * find_set_nic_lib() - Match the NIC library to the NIC + * @param nic - NIC device to determine which NIC library to use + * @return 0 on success <0 on failure + */ +int find_set_nic_lib(nic_t *nic) +{ + uint32_t vendor; + uint32_t subvendor; + uint32_t device; + uint32_t subdevice; + + uint32_t pci_bus; + uint32_t pci_slot; + uint32_t pci_func; + int rc = 0; + + nic_lib_handle_t *handle; + struct pci_device_id *pci_entry; + size_t name_size; + + rc = get_vendor(nic, &vendor); + if (rc != 0) { + LOG_ERR(PFX "%s: Could not get vendor id [0x%x]", + nic->log_name, rc); + return rc; + } + + rc = get_subvendor(nic, &subvendor); + if (rc != 0) { + LOG_ERR(PFX "%s: Could not get subvendor id [0x%x]", + nic->log_name, rc); + return rc; + } + + rc = get_device(nic, &device); + if (rc != 0) { + LOG_ERR(PFX "%s: Could not get device id [0x%x]", + nic->log_name, rc); + return rc; + } + + rc = get_subdevice(nic, &subdevice); + if (rc != 0) { + LOG_ERR(PFX "%s: Could not get subdevice id [0x%x]", + nic->log_name, rc); + return rc; + } + + get_bus_slot_func_num(nic, &pci_bus, &pci_slot, &pci_func); + + LOG_DEBUG(PFX "%s: Looking for device vendor: " + "0x%x subvendor: 0x%x device: 0x%x subdevice: 0x%x", + nic->log_name, vendor, subvendor, device, subdevice); + + rc = find_nic_lib_using_pci_id(vendor, device, subvendor, subdevice, + &handle, &pci_entry); + + if (rc != 0) { + LOG_WARN(PFX "%s: Couldn't find proper NIC library", + nic->log_name); + return rc; + } + + nic->nic_library = handle; + nic->pci_id = pci_entry; + + /* Prepare the NIC library op table */ + nic->ops = handle->ops; + (*nic->ops->lib_ops.get_library_name) (&nic->library_name, &name_size); + + return 0; +} diff --git a/iscsiuio/src/unix/nic_id.h b/iscsiuio/src/unix/nic_id.h new file mode 100644 index 0000000..340580f --- /dev/null +++ b/iscsiuio/src/unix/nic_id.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * nic_id.h - NIC uIP NetLink user space stack + * + */ +#ifndef __NIC_ID_H__ +#define __NIC_ID_H__ + +int find_set_nic_lib(nic_t *nic); + +int get_bus_slot_func_num(nic_t *nic, + uint32_t *bus, uint32_t *slot, uint32_t *func); + +#endif /* __NIC_ID_H__ */ diff --git a/iscsiuio/src/unix/nic_nl.c b/iscsiuio/src/unix/nic_nl.c new file mode 100644 index 0000000..f830656 --- /dev/null +++ b/iscsiuio/src/unix/nic_nl.c @@ -0,0 +1,680 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * nic_nl.c - NIC uIP NetLink user space stack + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "uip_arp.h" +#include "logger.h" +#include "options.h" + +#include "nic.h" +#include "nic_nl.h" +#include "nic_utils.h" + +/******************************************************************************* + * Constants + ******************************************************************************/ +#define PFX "NIC_NL " + +static u8_t nlm_sendbuf[NLM_BUF_DEFAULT_MAX]; + +static struct sockaddr_nl src_addr; + +static const struct sockaddr_nl dest_addr = { + .nl_family = AF_NETLINK, + .nl_pid = 0, /* kernel */ + .nl_groups = 0, /* unicast */ +}; + +#define POLL_NL 0 +#define POLL_MAX 1 + +/* Netlink */ +int nl_sock = INVALID_FD; + +static int nl_read(int ctrl_fd, char *data, int size, int flags) +{ + int rc; + struct iovec iov; + struct msghdr msg; + + iov.iov_base = data; + iov.iov_len = size; + + memset(&src_addr, 0, sizeof(src_addr)); + src_addr.nl_family = AF_NETLINK; + src_addr.nl_pid = getpid(); + src_addr.nl_groups = 1; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = (void *)&src_addr; + msg.msg_namelen = sizeof(src_addr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + rc = recvmsg(ctrl_fd, &msg, flags); + + return rc; +} + +static int +kwritev(int fd, enum iscsi_uevent_e type, struct iovec *iovp, int count) +{ + int i, rc; + struct nlmsghdr *nlh; + struct msghdr msg; + struct iovec iov; + int datalen = 0; + + for (i = 0; i < count; i++) + datalen += iovp[i].iov_len; + + nlh = (struct nlmsghdr *)nlm_sendbuf; + memset(nlh, 0, NLMSG_SPACE(datalen)); + + nlh->nlmsg_len = NLMSG_SPACE(datalen); + nlh->nlmsg_pid = getpid(); + nlh->nlmsg_flags = 0; + nlh->nlmsg_type = type; + + datalen = 0; + for (i = 0; i < count; i++) { + memcpy(NLMSG_DATA(nlh) + datalen, iovp[i].iov_base, + iovp[i].iov_len); + datalen += iovp[i].iov_len; + } + iov.iov_base = (void *)nlh; + iov.iov_len = nlh->nlmsg_len; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = (void *)&dest_addr; + msg.msg_namelen = sizeof(dest_addr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + do { + rc = sendmsg(fd, &msg, 0); + if (rc == -ENOMEM) { + LOG_ERR(PFX "sendmsg: alloc_skb() failed"); + sleep(1); + } else if (rc < 0) { + LOG_ERR(PFX "sendmsg: bug?: on %d %s[0x%x]", + fd, strerror(errno), errno); + sleep(1); + } + } while ((rc < 0) && (event_loop_stop == 0)); + + return rc; +} + +/* + * __kipc_call() should never block. Therefore + * Netlink's xmit logic is serialized. This means we do not allocate on + * xmit path. Instead we reuse nlm_sendbuf buffer. + * + * Transport must assure non-blocking operations for: + * + * - session_create() + * - conn_create() + * - conn_bind() + * _ set_param() + * - conn_start() + * - conn_stop() + * + * Its OK to block for cleanup for short period of time in operatations for: + * + * - conn_destroy() + * - session_destroy() + * + * FIXME: interface needs to be extended to allow longer blocking on + * cleanup. (Dima) + */ +int __kipc_call(int fd, void *iov_base, int iov_len) +{ + int rc; + struct iovec iov; + struct iscsi_uevent *ev = iov_base; + enum iscsi_uevent_e type = ev->type; + + /* Sanity check */ + if (iov_base == NULL) + return -EINVAL; + + iov.iov_base = iov_base; + iov.iov_len = iov_len; + + rc = kwritev(fd, type, &iov, 1); + + return rc; +} + +static int pull_from_nl(char **buf) +{ + int rc; + size_t ev_size, payload_size, alloc_size; + char nlm_ev[NLMSG_SPACE(sizeof(struct iscsi_uevent))]; + struct nlmsghdr *nlh; + char *data = NULL; + struct iscsi_uevent *ev; + + /* Take a quick peek at what how much uIP will need to read */ + rc = nl_read(nl_sock, nlm_ev, + NLMSG_SPACE(sizeof(struct iscsi_uevent)), + MSG_PEEK | MSG_WAITALL); + if (rc <= 0) { + LOG_ERR("can not read nlm_ev, error %s[%d]", + strerror(errno), rc); + if (rc == 0) + return -EIO; + else + return errno; + } + nlh = (struct nlmsghdr *)nlm_ev; + + if (unlikely(nlh->nlmsg_len < NLMSG_ALIGN(sizeof(struct nlmsghdr)))) { + LOG_ERR(PFX "Invalid nlh->nlmsg_len length: " + "nlh->nlmsg_len(%d) < " + "NLMSG_ALIGN(sizeof(struct nlmsghdr))(%d)", + nlh->nlmsg_len, NLMSG_ALIGN(sizeof(struct nlmsghdr))); + return -EINVAL; + } + + ev = (struct iscsi_uevent *)NLMSG_DATA(nlh); + if (ev->type == ISCSI_KEVENT_PATH_REQ) { + ev_size = nlh->nlmsg_len - NLMSG_ALIGN(sizeof(struct nlmsghdr)); + payload_size = ev_size - sizeof(struct iscsi_uevent); + if (payload_size < sizeof(struct iscsi_path)) + alloc_size = nlh->nlmsg_len + (payload_size - + sizeof(struct iscsi_path)); + else + alloc_size = nlh->nlmsg_len; + } else { + alloc_size = nlh->nlmsg_len; + } + data = (char *)malloc(alloc_size); + if (unlikely(data == NULL)) { + LOG_ERR(PFX "Couldn't allocate %d bytes for Netlink " + "iSCSI message", alloc_size); + return -ENOMEM; + } + + memset(data, 0, alloc_size); + rc = nl_read(nl_sock, data, (int)nlh->nlmsg_len, MSG_WAITALL); + if (rc <= 0) { + LOG_ERR("can not read nlm_ev, error %s[%d]", + strerror(errno), rc); + if (rc == 0) + rc = -EIO; + else + rc = errno; + + goto error; + } + *buf = data; + return 0; +error: + if (data != NULL) + free(data); + + return rc; +} + +static const struct timespec ctldev_sleep_req = { + .tv_sec = 0, + .tv_nsec = 250000000, +}; + +static int ctldev_handle(char *data, nic_t *nic) +{ + int rc = 0; + struct iscsi_uevent *ev; + uint8_t *payload; + struct iscsi_path *path; + char *msg_type_str; + int i; + nic_interface_t *nic_iface = NULL; + + ev = (struct iscsi_uevent *)NLMSG_DATA(data); + switch (ev->type) { + case ISCSI_KEVENT_PATH_REQ: + msg_type_str = "path_req"; + break; + default: + /* We don't care about other iSCSI Netlink messages */ + LOG_DEBUG(PFX "Received ev->type: 0x%x", ev->type); + rc = 0; + goto error; + } + + /* This is a message that drivers should be interested in */ + LOG_INFO(PFX "%s: Processing '%s'", nic->log_name, msg_type_str); + + payload = (uint8_t *) ((uint8_t *) ev) + sizeof(*ev); + path = (struct iscsi_path *)payload; + + if (ev->type == ISCSI_KEVENT_PATH_REQ) { + struct timespec sleep_rem; + nic_interface_t *vlan_iface; + uint16_t ip_type; + int iface_num, vlan_id; + + if (path->ip_addr_len == 4) + ip_type = AF_INET; + else if (path->ip_addr_len == 16) + ip_type = AF_INET6; + else + ip_type = 0; +#ifdef REQ_PATH_IFACE_NUM + /* Find the nic_iface to use */ + iface_num = ev->r.req_path.iface_num ? + ev->r.req_path.iface_num : IFACE_NUM_INVALID; +#else + iface_num = IFACE_NUM_INVALID; +#endif + vlan_id = path->vlan_id ? path->vlan_id : NO_VLAN; + + LOG_DEBUG(PFX "%s: PATH_REQ with iface_num %d VLAN %d", + nic->log_name, iface_num, vlan_id); + + pthread_mutex_lock(&nic->nic_mutex); + + nic_iface = nic_find_nic_iface(nic, ip_type, vlan_id, + iface_num, IP_CONFIG_OFF); + if (nic_iface == NULL) { + nic_iface = nic_find_nic_iface(nic, ip_type, + NO_VLAN, + IFACE_NUM_INVALID, + IP_CONFIG_OFF); + if (nic_iface == NULL) { + pthread_mutex_unlock(&nic->nic_mutex); + LOG_ERR(PFX "%s: Couldn't find nic iface parent" + " vlan: %d ip_type: %d " + "ip_addr_len: %d to clone", + nic->log_name, path->vlan_id, ip_type, + path->ip_addr_len); + goto error; + } + if (nic_iface->iface_num != IFACE_NUM_INVALID) { + /* New VLAN support: + Use the nic_iface found from the top + of the protocol family and ignore + the VLAN id from the path_req */ + if (!(nic_iface->iface_num == 0 && + nic_iface->vlan_id == 0 && + path->vlan_id)) { + pthread_mutex_unlock(&nic->nic_mutex); + goto nic_iface_done; + } + /* If iface_num == 0 and vlan_id == 0 but + the vlan_id from path_req is > 0, + then fallthru to the legacy support since + this is most likely from an older iscsid + (RHEL6.2/6.3 but has iface_num support) + */ + } + /* Legacy VLAN support: + This newly created nic_iface must inherit the + network parameters from the parent nic_iface + */ + LOG_DEBUG(PFX "%s: Created the nic_iface for vlan: %d " + "ip_type: %d", nic->log_name, path->vlan_id, + ip_type); + vlan_iface = nic_iface_init(); + if (vlan_iface == NULL) { + pthread_mutex_unlock(&nic->nic_mutex); + LOG_ERR(PFX "%s: Couldn't allocate " + "space for vlan: %d ip_type: " + "%d", nic->log_name, path->vlan_id, + ip_type); + goto error; + } + vlan_iface->protocol = ip_type; + vlan_iface->vlan_id = path->vlan_id; + nic_add_nic_iface(nic, vlan_iface); + + vlan_iface->ustack.ip_config = + nic_iface->ustack.ip_config; + memcpy(vlan_iface->ustack.hostaddr, + nic_iface->ustack.hostaddr, + sizeof(nic_iface->ustack.hostaddr)); + memcpy(vlan_iface->ustack.netmask, + nic_iface->ustack.netmask, + sizeof(nic_iface->ustack.netmask)); + memcpy(vlan_iface->ustack.netmask6, + nic_iface->ustack.netmask6, + sizeof(nic_iface->ustack.netmask6)); + memcpy(vlan_iface->ustack.hostaddr6, + nic_iface->ustack.hostaddr6, + sizeof(nic_iface->ustack.hostaddr6)); + + /* Persist so when nic_close won't call uip_reset + to nullify nic_iface->ustack */ + persist_all_nic_iface(nic); + + nic_iface = vlan_iface; + nic_iface->flags |= NIC_IFACE_ACQUIRE; + + pthread_mutex_unlock(&nic->nic_mutex); + + /* nic_disable but not going down */ + nic_disable(nic, 0); + } else { + pthread_mutex_unlock(&nic->nic_mutex); + } +nic_iface_done: + /* Force enable the NIC */ + if (nic->state == NIC_STOPPED) + nic_enable(nic); + + /* Ensure that the NIC is RUNNING */ + rc = -EIO; + for (i = 0; i < 10; i++) { + if (nic->state == NIC_RUNNING) { + rc = 0; + break; + } + + nanosleep(&ctldev_sleep_req, &sleep_rem); + } + + if (rc != 0) { + LOG_WARN(PFX "%s[vlan: %d protocol: %d]: not running, " + "cmd: 0x%x nic state: 0x%x flags: 0x%x", + nic->log_name, + nic_iface->vlan_id, nic_iface->protocol, + ev->type, nic->state, nic->flags); + goto error; + } + } + + if (nic->ops) { + switch (ev->type) { + case ISCSI_KEVENT_PATH_REQ: + /* pass the request up to the user space + * library driver */ + nic_iface->flags |= NIC_IFACE_PATHREQ_WAIT2; + nic_iface->flags &= ~NIC_IFACE_PATHREQ_WAIT1; + if (nic->ops->handle_iscsi_path_req) + nic->ops->handle_iscsi_path_req(nic, + nl_sock, ev, + path, + nic_iface); + nic_iface->flags &= ~NIC_IFACE_PATHREQ_WAIT; + pthread_mutex_lock(&nic->nic_mutex); + nic->flags &= ~NIC_PATHREQ_WAIT; + pthread_mutex_unlock(&nic->nic_mutex); + LOG_INFO(PFX "%s: 'path_req' operation finished", + nic->log_name); + + rc = 0; + break; + default: + rc = -EAGAIN; + break; + } + } + +error: + + return rc; +} + +/* NIC specific nl processing thread */ +void *nl_process_handle_thread(void *arg) +{ + int rc; + nic_t *nic = (nic_t *)arg; + + if (nic == NULL) + goto error; + + while (!event_loop_stop) { + char *data = NULL; + + pthread_mutex_lock(&nic->nl_process_mutex); + rc = pthread_cond_wait(&nic->nl_process_cond, + &nic->nl_process_mutex); + if (rc != 0) { + pthread_mutex_unlock(&nic->nl_process_mutex); + LOG_ERR("Fatal error in NL processing thread " + "during wait[%s]", strerror(rc)); + break; + } + + data = nic->nl_process_ring[nic->nl_process_head]; + nic->nl_process_ring[nic->nl_process_head] = NULL; + nic->nl_process_tail = + NIC_NL_PROCESS_NEXT_ENTRY(nic->nl_process_tail); + + pthread_mutex_unlock(&nic->nl_process_mutex); + + if (data) { + ctldev_handle(data, nic); + free(data); + } + } +error: + return NULL; +} + +static void flush_nic_nl_process_ring(nic_t *nic) +{ + int i; + + for (i = 0; i < NIC_NL_PROCESS_MAX_RING_SIZE; i++) { + if (nic->nl_process_ring[i] != NULL) { + free(nic->nl_process_ring[i]); + nic->nl_process_ring[i] = NULL; + } + } + + nic->nl_process_head = 0; + nic->nl_process_tail = 0; + + LOG_DEBUG(PFX "%s: Flushed NIC NL ring", nic->log_name); +} + +/** + * nic_nl_open() - This is called when opening/creating the Netlink listening + * thread + * @param dev - CNIC UIO device to create a NetLink listener on + * @return 0 on success, <0 on failure + */ +int nic_nl_open() +{ + int rc = 0; + char *msg_type_str; + + /* Prepare the thread to issue the ARP's */ + nl_sock = socket(PF_NETLINK, SOCK_RAW, NETLINK_ISCSI); + if (nl_sock < 0) { + LOG_ERR(PFX "can not create NETLINK_ISCSI socket [%s]", + strerror(errno)); + rc = -ENOMEM; + goto error; + } + + memset(&src_addr, 0, sizeof(src_addr)); + src_addr.nl_family = AF_NETLINK; + src_addr.nl_pid = getpid(); + src_addr.nl_groups = ISCSI_NL_GRP_UIP; + + while ((!event_loop_stop)) { + rc = bind(nl_sock, + (struct sockaddr *)&src_addr, sizeof(src_addr)); + if (rc == 0) + break; + + LOG_ERR(PFX "waiting binding to NETLINK_ISCSI socket"); + + sleep(1); + } + + if (event_loop_stop) { + rc = -EINVAL; + goto error; + } + + LOG_INFO(PFX "Netlink to CNIC on pid %d is ready", src_addr.nl_pid); + + while (!event_loop_stop) { + struct iscsi_uevent *ev; + char *buf = NULL; + uint32_t host_no; + nic_t *nic; + + rc = pull_from_nl(&buf); + if (rc != 0) + continue; + + /* Try to abort ARP'ing if a if_down was received */ + ev = (struct iscsi_uevent *)NLMSG_DATA(buf); + switch (ev->type) { + case ISCSI_KEVENT_IF_DOWN: + host_no = ev->r.notify_if_down.host_no; + msg_type_str = "if_down"; + break; + case ISCSI_KEVENT_PATH_REQ: + host_no = ev->r.req_path.host_no; + msg_type_str = "path_req"; + break; + default: + /* We don't care about other iSCSI Netlink messages */ + continue; + } + LOG_INFO(PFX "Received %s for host %d", msg_type_str, host_no); + + /* Make sure the nic list doesn't get yanked */ + pthread_mutex_lock(&nic_list_mutex); + + rc = from_host_no_find_associated_eth_device(host_no, &nic); + if (rc != 0) { + pthread_mutex_unlock(&nic_list_mutex); + LOG_ERR(PFX "Dropping msg, couldn't find nic with host " + "no: %d", host_no); + continue; + } + + /* Found the nic */ + if (nic->nl_process_thread == INVALID_THREAD) { + /* If thread is not valid, just drop it */ + pthread_mutex_unlock(&nic_list_mutex); + LOG_ERR(PFX "Dropping msg, nic nl process thread " + "not ready for host no: %d", host_no); + continue; + } + + if (ev->type == ISCSI_KEVENT_IF_DOWN) { + char eth_device_name[IFNAMSIZ]; + + pthread_mutex_lock(&nic->nl_process_mutex); + nic->nl_process_if_down = 1; + flush_nic_nl_process_ring(nic); + pthread_cond_broadcast(&nic->nl_process_if_down_cond); + pthread_mutex_unlock(&nic->nl_process_mutex); + + memcpy(eth_device_name, nic->eth_device_name, + sizeof(eth_device_name)); + + pthread_mutex_lock(&nic->nic_mutex); + nic->flags &= ~NIC_PATHREQ_WAIT; + nic->flags |= NIC_EXIT_MAIN_LOOP; + pthread_cond_broadcast(&nic->enable_done_cond); + pthread_mutex_unlock(&nic->nic_mutex); + + pthread_mutex_lock(&nic->nl_process_mutex); + nic->nl_process_if_down = 0; + pthread_mutex_unlock(&nic->nl_process_mutex); + + nic_disable(nic, 1); + + nic_remove(nic); + pthread_mutex_unlock(&nic_list_mutex); + + LOG_INFO(PFX "%s: 'if_down' operation finished", + eth_device_name); + continue; + } + + /* Place msg into the nic specific queue */ + pthread_mutex_lock(&nic->nl_process_mutex); + if ((nic->nl_process_head + 1 == nic->nl_process_tail) || + (nic->nl_process_tail == 0 && + nic->nl_process_head == NIC_NL_PROCESS_LAST_ENTRY)) { + pthread_mutex_unlock(&nic->nl_process_mutex); + pthread_mutex_unlock(&nic_list_mutex); + LOG_WARN(PFX "%s: No space on Netlink ring", + nic->log_name); + continue; + } + + nic->nl_process_ring[nic->nl_process_head] = buf; + nic->nl_process_head = + NIC_NL_PROCESS_NEXT_ENTRY(nic->nl_process_head); + pthread_cond_signal(&nic->nl_process_cond); + + pthread_mutex_unlock(&nic->nl_process_mutex); + + pthread_mutex_unlock(&nic_list_mutex); + + LOG_DEBUG(PFX "Pulled nl event"); + } + + LOG_INFO(PFX "Netlink thread exit'ing"); + rc = 0; + +error: + return rc; +} diff --git a/iscsiuio/src/unix/nic_nl.h b/iscsiuio/src/unix/nic_nl.h new file mode 100644 index 0000000..c68d81c --- /dev/null +++ b/iscsiuio/src/unix/nic_nl.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * nic_nl.h - NIC uIP NetLink user space stack + * + */ + +#ifndef __NIC_NL_H__ +#define __NIC_NL_H__ + +#include + +int nic_nl_open(); +void nic_nl_close(); + +int __kipc_call(int fd, void *iov_base, int iov_len); + +extern pthread_cond_t nl_process_if_down_cond; +extern pthread_mutex_t nl_process_mutex; +extern int nl_process_if_down; + +#endif /* __NIC_NL_H__ */ diff --git a/iscsiuio/src/unix/nic_utils.c b/iscsiuio/src/unix/nic_utils.c new file mode 100644 index 0000000..84ffc5c --- /dev/null +++ b/iscsiuio/src/unix/nic_utils.c @@ -0,0 +1,1803 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * nic_util.c - shared NIC utility functions + * + */ +#include +#include +#include +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "logger.h" +#include "nic.h" +#include "nic_id.h" +#include "nic_vlan.h" +#include "nic_utils.h" +#include "options.h" + +#define PFX "nic_utils " + +/****************************************************************************** + * String constants + *****************************************************************************/ +static const char nic_uio_sysfs_name_tempate[] = "/sys/class/uio/uio%i/name"; +static const char cnic_sysfs_uio_event_template[] = + "/sys/class/uio/uio%d/event"; +static const char base_uio_sysfs_name[] = "/sys/class/uio/"; +static const char uio_name[] = "uio"; + +static const char uio_base_dir[] = "/dev/uio"; +static const char uio_udev_path_template[] = "/dev/uio%hd"; +static const char uio_uevent_path_template[] = "/sys/class/uio/uio%d/uevent"; + +static const char base_iscsi_host_name[] = "/sys/class/iscsi_host/"; +static const char host_template[] = "host%d"; +static const char iscsi_host_path_netdev_template[] = + "/sys/class/iscsi_host/host%d/netdev"; +static const char cnic_uio_sysfs_resc_template[] = + "/sys/class/uio/uio%i/device/resource%i"; +static const char iscsi_transport_handle_template[] = + "/sys/class/iscsi_transport/%s/handle"; +static const char host_pfx[] = "host"; + +/** + * manually_trigger_uio_event() - If the uio file node doesn't exist then + * try to retrigger udev to create the file + * node by touch the uevent file in sysfs + * @param nic - the nic to trigger on + * @param uio_minor - UIO the minor number to use + * @return 0 on success + */ +int manually_trigger_uio_event(nic_t *nic, int uio_minor) +{ + int fd; + char uio_uevent_path[sizeof(uio_uevent_path_template) + 10]; + char enable_str[] = "online"; + int rc; + size_t bytes_wrote; + + rc = sprintf(uio_uevent_path, uio_uevent_path_template, uio_minor); + if (rc < 0) { + LOG_ERR(PFX "%s: Could not build uio uevent path", + nic->log_name); + return -EIO; + } + + LOG_DEBUG(PFX "%s: triggering UIO uevent path: %s", + nic->log_name, uio_uevent_path); + + fd = open(uio_uevent_path, O_WRONLY); + if (fd == -1) { + LOG_ERR(PFX "%s: Could not open uio uevent path: %s [%s]", + nic->log_name, uio_uevent_path, strerror(errno)); + return -EIO; + } + + bytes_wrote = write(fd, enable_str, sizeof(enable_str)); + if (bytes_wrote != sizeof(enable_str)) { + LOG_ERR(PFX "%s: Could write to uio uevent path: %s [%s]", + nic->log_name, uio_uevent_path, strerror(errno)); + rc = -EIO; + } else + rc = 0; + + close(fd); + return rc; +} + +static int wait_for_file_node_timed(nic_t *nic, char *filepath, int seconds) +{ + struct timeval start_time; + struct timeval wait_time; + struct timeval total_time; + struct timespec sleep_req, sleep_rem; + + sleep_req.tv_sec = 0; + sleep_req.tv_nsec = 250000000; + + wait_time.tv_sec = seconds; + wait_time.tv_usec = 0; + + if (gettimeofday(&start_time, NULL)) { + LOG_ERR(PFX "%s: Couldn't gettimeofday() during watch file: %s" + "[%s]", nic->log_name, filepath, strerror(errno)); + return -EIO; + } + + timeradd(&start_time, &wait_time, &total_time); + + while (1) { + struct timeval current_time; + struct stat file_stat; + + /* Check if the file node exists */ + if (stat(filepath, &file_stat) == 0) + return 0; + + if (gettimeofday(¤t_time, NULL)) { + LOG_ERR(PFX "%s: Couldn't get current time for " + "watching file: %s [%s]", + nic->log_name, filepath, strerror(errno)); + return -EIO; + } + + /* Timeout has excceded return -ETIME */ + if (timercmp(&total_time, ¤t_time, <)) { + LOG_ERR(PFX "%s: timeout waiting %d secs for file: %s", + nic->log_name, seconds, filepath); + return -ETIME; + } + + nanosleep(&sleep_req, &sleep_rem); + } +} + +/****************************************************************************** + * Autodiscovery of iscsi_hosts + *****************************************************************************/ +static int filter_host_name(const struct dirent *entry) +{ + if ((memcmp(entry->d_name, "host", 4) == 0)) + return 1; + else + return 0; +} + +int nic_discover_iscsi_hosts() +{ + struct dirent **files; + int count; + int i; + int rc; + + count = scandir(base_iscsi_host_name, &files, filter_host_name, + alphasort); + + switch (count) { + case 0: + /* Currently there are no iSCSI hosts */ + rc = 0; + break; + + case -1: + LOG_WARN(PFX "Error when scanning path: %s[%s]", + base_iscsi_host_name, strerror(errno)); + rc = -EINVAL; + break; + + default: + /* There are iSCSI hosts */ + pthread_mutex_lock(&nic_list_mutex); + for (i = 0; i < count; i++) { + int host_no; + char *raw = NULL; + uint32_t raw_size = 0; + char temp_path[sizeof(iscsi_host_path_netdev_template) + + 8]; + rc = sscanf(files[i]->d_name, host_template, &host_no); + nic_t *nic; + + LOG_INFO(PFX "Found host[%d]: %s", + host_no, files[i]->d_name); + + /* Build the path to determine netdev name */ + snprintf(temp_path, sizeof(temp_path), + iscsi_host_path_netdev_template, host_no); + + rc = capture_file(&raw, &raw_size, temp_path); + if (rc != 0) + continue; + + rc = from_host_no_find_associated_eth_device(host_no, + &nic); + if (rc != 0) { + /* Normalize the string */ + if (raw[raw_size - 1] == '\n') + raw[raw_size - 1] = '\0'; + + nic = nic_init(); + if (nic == NULL) { + LOG_ERR(PFX "Couldn't allocate " + "space for NIC %s " + "during scan", raw); + + free(raw); + rc = -ENOMEM; + break; + } + + strncpy(nic->eth_device_name, raw, raw_size); + nic->config_device_name = nic->eth_device_name; + nic->log_name = nic->eth_device_name; + nic->host_no = host_no; + + if (nic_fill_name(nic) != 0) { + free(nic); + free(raw); + rc = -EIO; + continue; + } + + nic_add(nic); + + LOG_INFO(PFX "NIC not found creating an " + "instance for host_no: %d %s", + host_no, nic->eth_device_name); + } else + LOG_INFO(PFX "%s: NIC found host_no: %d", + nic->log_name, host_no); + + free(raw); + } + pthread_mutex_unlock(&nic_list_mutex); + + /* Cleanup the scandir() call */ + for (i = 0; i < count; i++) + free(files[i]); + free(files); + + rc = 0; + break; + } + + return rc; +} + +/****************************************************************************** + * Enable/Disable Multicast on physical interface + *****************************************************************************/ +static int nic_util_enable_disable_multicast(nic_t *nic, uint32_t cmd) +{ + int rc = 0; + struct uip_eth_addr multicast_addr; + int fd; + struct ifreq ifr; + + /* adding ethernet multicast address for IPv6 */ + memcpy(&multicast_addr, nic->mac_addr, ETH_ALEN); + multicast_addr.addr[0] = 0x33; + multicast_addr.addr[1] = 0x33; + multicast_addr.addr[2] = 0xff; + + /* Prepare the request */ + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, nic->eth_device_name, + sizeof(ifr.ifr_name)); + memcpy(ifr.ifr_hwaddr.sa_data, multicast_addr.addr, ETH_ALEN); + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + LOG_ERR(PFX "%s: Couldn't create socket to %s " + "multicast address: %s", + nic->log_name, + cmd == SIOCADDMULTI ? "added" : "delete", + strerror(errno)); + return errno; + } + + rc = fcntl(fd, F_SETFL, O_NONBLOCK); + if (rc != 0) { + LOG_WARN("%s: Couldn't set to ethtool IOCTL to " + "non-blocking [%s]", nic->log_name, strerror(errno)); + } + + if (ioctl(fd, cmd, (char *)&ifr) != 0) { + LOG_ERR("%s: Couldn't issue ioctl socket to %s " + "multicast address: %s", + nic->log_name, + cmd == SIOCADDMULTI ? "add" : "delete", + strerror(errno)); + rc = errno; + goto error; + } + + LOG_INFO(PFX "%s: %s address %02x:%02x:%02x:%02x:%02x:%02x " + "to multicast list", + nic->log_name, + cmd == SIOCADDMULTI ? "Added" : "Deleted", + multicast_addr.addr[0], multicast_addr.addr[1], + multicast_addr.addr[2], multicast_addr.addr[3], + multicast_addr.addr[4], multicast_addr.addr[5]); + + if (cmd == SIOCADDMULTI) + nic->flags |= NIC_ADDED_MULICAST; + else + nic->flags &= ~NIC_ADDED_MULICAST; + +error: + close(fd); + + return rc; +} + +/** + * enable_multicast() - This fuction is used to enable + * the listening of multicast addresses for a given network interface + * @param nic - NIC device to enable multicast on + * @return 0 for success or <0 for failure + */ +int enable_multicast(nic_t *nic) +{ + return nic_util_enable_disable_multicast(nic, SIOCADDMULTI); +} + +/** + * disable_multicast() - This fuction is used to disable + * the listening of multicast addresses for a given network interface + * @param dev - NIC device to disable multicast on + * @return 0 for success or <0 for failure + */ +int disable_multicast(nic_t *nic) +{ + return nic_util_enable_disable_multicast(nic, SIOCDELMULTI); +} + +/******************************************************************************* + * Finding associated UIO/physical network interfaces + ******************************************************************************/ +static int filter_net_name(const struct dirent *entry) +{ + if ((memcmp(entry->d_name, "net:", 4) == 0)) + return 1; + else + return 0; +} + +static char *extract_net_name(struct dirent **files) +{ + return strstr(files[0]->d_name, ":"); +} + +static int filter_dot_out(const struct dirent *entry) +{ + if ((memcmp(entry->d_name, ".", 1) == 0)) + return 0; + else + return 1; +} + +static char *extract_none(struct dirent **files) +{ + return files[0]->d_name; +} + +/** + * from_host_no_find_nic() - Given the host number + * this function will try to find the assoicated nic interface + * Must be called with nic_list_mutex lock + * @param host_no - minor number of the UIO device + * @param nic - pointer to the NIC will set if successful + * @return 0 on success, <0 on error + */ +int from_host_no_find_associated_eth_device(int host_no, nic_t **nic) +{ + nic_t *current_nic = nic_list; + char *raw = NULL, *raw_tmp; + uint32_t raw_size = 0; + + char temp_path[sizeof(iscsi_host_path_netdev_template) + 8]; + int rc = -EIO; + + /* Build the path to determine uio name */ + snprintf(temp_path, sizeof(temp_path), + iscsi_host_path_netdev_template, host_no); + + rc = capture_file(&raw, &raw_size, temp_path); + if (rc != 0) + goto error; + + /* sanitize name string by replacing newline with null termination */ + raw_tmp = raw; + while (*raw_tmp != '\n' && raw_size--) + raw_tmp++; + *raw_tmp = '\0'; + + rc = -EIO; + + current_nic = nic_list; + while (current_nic != NULL) { + if (strcmp(raw, current_nic->eth_device_name) == 0) { + *nic = current_nic; + rc = 0; + break; + } + + current_nic = current_nic->next; + } + + free(raw); + +error: + return rc; +} + +/******************************************************************************* + * NIC packet handling functions + ******************************************************************************/ +/** + * from_uio_find_associated_eth_device() - Given the uio minor number + * this function will try to find the assoicated phyisical network + * interface + * @param uio_minor - minor number of the UIO device + * @param name - char buffer which will be filled if successful + * @param name_size - size of the name buffer + * @return >0 minor number <0 an error + */ +static int from_uio_find_associated_eth_device(nic_t *nic, + int uio_minor, + char *name, size_t name_size) +{ + char *path; + int rc; + int count; + struct dirent **files; + char *parsed_name; + int i; + int path_iterator; + char *search_paths[] = { "/sys/class/uio/uio%i/device/", + "/sys/class/uio/uio%i/device/net" + }; + int path_to[] = { 5, 1 }; + int (*search_filters[]) (const struct dirent *) = { + filter_net_name, filter_dot_out,}; + char *(*extract_name[]) (struct dirent **files) = { + extract_net_name, extract_none,}; + int extract_name_offset[] = { 1, 0 }; + + path = malloc(PATH_MAX); + if (path == NULL) { + LOG_ERR(PFX "Could not allocate memory for path"); + rc = -ENOMEM; + goto error; + } + + for (path_iterator = 0; + path_iterator < sizeof(search_paths) / sizeof(search_paths[0]); + path_iterator++) { + /* Build the path to determine uio name */ + rc = sprintf(path, search_paths[path_iterator], uio_minor); + + wait_for_file_node_timed(nic, path, path_to[path_iterator]); + + count = scandir(path, &files, + search_filters[path_iterator], alphasort); + + switch (count) { + case 1: + parsed_name = (*extract_name[path_iterator]) (files); + if (parsed_name == NULL) { + LOG_WARN(PFX "Couldn't find delimiter in: %s", + files[0]->d_name); + + break; + } + + strncpy(name, + parsed_name + + extract_name_offset[path_iterator], name_size); + + free(files[0]); + free(files); + + rc = 0; + break; + + case 0: + rc = -EINVAL; + break; + + case -1: + LOG_WARN(PFX "Error when scanning path: %s[%s]", + path, strerror(errno)); + rc = -EINVAL; + break; + + default: + LOG_WARN(PFX + "Too many entries when looking for device: %s", + path); + + /* Cleanup the scandir() call */ + for (i = 0; i < count; i++) + free(files[i]); + free(files); + + rc = -EINVAL; + break; + } + + if (rc == 0) + break; + } + +error: + free(path); + + return rc; +} + +/** + * from_uio_find_associated_host() - Given the uio minor number + * this function will try to find the assoicated iscsi host + * @param uio_minor - minor number of the UIO device + * @param name - char buffer which will be filled if successful + * @param name_size - size of the name buffer + * @return >0 minor number <0 an error + */ +static int from_uio_find_associated_host(nic_t *nic, int uio_minor, + char *name, size_t name_size) +{ + char *path; + int rc; + int count; + struct dirent **files; + char *parsed_name; + int i; + int path_iterator; + char *search_paths[] = { "/sys/class/uio/uio%i/device/" }; + int path_to[] = { 5, 1 }; + int (*search_filters[]) (const struct dirent *) = { filter_host_name, }; + char *(*extract_name[]) (struct dirent **files) = { extract_none, }; + int extract_name_offset[] = { 0 }; + + path = malloc(PATH_MAX); + if (!path) { + LOG_ERR(PFX "Could not allocate memory for path"); + rc = -ENOMEM; + goto error; + } + + for (path_iterator = 0; + path_iterator < sizeof(search_paths) / sizeof(search_paths[0]); + path_iterator++) { + /* Build the path to determine uio name */ + rc = sprintf(path, search_paths[path_iterator], uio_minor); + + wait_for_file_node_timed(nic, path, path_to[path_iterator]); + + count = scandir(path, &files, + search_filters[path_iterator], alphasort); + + switch (count) { + case 1: + parsed_name = (*extract_name[path_iterator]) (files); + if (!parsed_name) { + LOG_WARN(PFX "Couldn't find delimiter in: %s", + files[0]->d_name); + + break; + } + + strncpy(name, + parsed_name + + extract_name_offset[path_iterator], name_size); + + free(files[0]); + free(files); + + rc = 0; + break; + + case 0: + rc = -EINVAL; + break; + + case -1: + LOG_WARN(PFX "Error when scanning path: %s[%s]", + path, strerror(errno)); + rc = -EINVAL; + break; + + default: + LOG_WARN(PFX + "Too many entries when looking for device: %s", + path); + + /* Cleanup the scandir() call */ + for (i = 0; i < count; i++) + free(files[i]); + free(files); + + rc = -EINVAL; + break; + } + + if (rc == 0) + break; + } + +error: + free(path); + + return rc; +} + +/** + * filter_uio_name() - This is the callback used by scandir when looking for + * the number of uio entries + */ +static int filter_uio_name(const struct dirent *entry) +{ + /* Only return if the name of the file begins with 'uio' */ + if ((memcmp(entry->d_name, uio_name, sizeof(uio_name) - 1) == 0)) + return 1; + else + return 0; +} + +/** + * from_netdev_name_find_nic() - This is used to find the NIC device given + * the netdev name + * @param interface_name - name of the interface to search on + * @param nic - pointer of the pointer to the NIC + * @return 0 on success, <0 on failure + */ +int from_netdev_name_find_nic(char *interface_name, nic_t **nic) +{ + nic_t *current_nic; + + current_nic = nic_list; + while (current_nic != NULL) { + if (strcmp(interface_name, current_nic->eth_device_name) == 0) + break; + + current_nic = current_nic->next; + } + + if (current_nic == NULL) + return -EINVAL; + + *nic = current_nic; + return 0; +} + +/** + * from_phys_name_find_assoicated_uio_device() - This is used to find the + * uio minor + * when given a network interface name + * @param interface_name - network interface name to search for + * @return >0 minor number <0 an error + */ +int from_phys_name_find_assoicated_uio_device(nic_t *nic) +{ + char *path = NULL; + int count; + struct dirent **files; + int i; + int rc; + char *interface_name = nic->config_device_name; + + if (interface_name == NULL) + interface_name = nic->eth_device_name; + + /* Wait at least 10 seconds for uio sysfs entries to appear */ + rc = wait_for_file_node_timed(nic, (char *)base_uio_sysfs_name, 10); + if (rc != 0) + return rc; + + count = scandir(base_uio_sysfs_name, + &files, filter_uio_name, alphasort); + + switch (count) { + case 0: + LOG_WARN(PFX "Couldn't find %s to determine uio minor", + interface_name); + return -EINVAL; + + case -1: + LOG_WARN(PFX "Error when scanning for %s in path: %s [%s]", + interface_name, base_uio_sysfs_name, strerror(errno)); + return -EINVAL; + } + + path = malloc(PATH_MAX); + if (path == NULL) { + LOG_ERR(PFX "Could not allocate memory for path"); + return -ENOMEM; + } + + /* Run through the contents of the filtered files to see if the + * network interface name matches that of the uio device */ + for (i = 0; i < count; i++) { + int uio_minor; + char eth_name[IFNAMSIZ]; + + rc = sscanf(files[i]->d_name, "uio%d", &uio_minor); + if (rc != 1) { + LOG_WARN("Could not parse: %s", files[i]->d_name); + continue; + } + + if (!memcmp(host_pfx, nic->config_device_name, + strlen(host_pfx))) { + rc = from_uio_find_associated_host(nic, uio_minor, + eth_name, + sizeof(eth_name)); + } else { + rc = from_uio_find_associated_eth_device(nic, uio_minor, + eth_name, + sizeof(eth_name)); + } + if (rc != 0) { + LOG_WARN("uio minor: %d not valid [%D]", uio_minor, rc); + continue; + } + + if (strncmp(eth_name, interface_name, sizeof(eth_name)) == 0) { + memcpy(nic->eth_device_name, + eth_name, sizeof(nic->eth_device_name)); + + LOG_INFO(PFX "%s associated with uio%d", + nic->eth_device_name, uio_minor); + + rc = uio_minor; + goto done; + } + } + + LOG_WARN("Could not find assoicate uio device with %s", interface_name); + + rc = -EINVAL; +done: + if (path != NULL) + free(path); + + for (i = 0; i < count; i++) + free(files[i]); + free(files); + + return rc; + +} + +/** + * nic_verify_uio_sysfs_name() - Using the name entry in sysfs it will try to + * match the NIC library name + * @param nic - The NIC hardware to check + * + */ +int nic_verify_uio_sysfs_name(nic_t *nic) +{ + char *raw = NULL, *raw_tmp; + uint32_t raw_size = 0; + char temp_path[sizeof(nic_uio_sysfs_name_tempate) + 8]; + int rc = 0; + nic_lib_handle_t *handle = NULL; + size_t name_size; + + + /* Build the path to determine uio name */ + snprintf(temp_path, sizeof(temp_path), + nic_uio_sysfs_name_tempate, nic->uio_minor); + + rc = capture_file(&raw, &raw_size, temp_path); + if (rc != 0) + goto error; + + /* sanitize name string by replacing newline with null termination */ + raw_tmp = raw; + while (*raw_tmp != '\n' && raw_size--) + raw_tmp++; + *raw_tmp = '\0'; + + /* If the nic library is not set then check if there is a library + * which matches the uio sysfs name */ + if (nic->nic_library == NULL) { + NIC_LIBRARY_EXIST_T exist; + + exist = does_nic_uio_name_exist(raw, &handle); + if (exist == NIC_LIBRARY_DOESNT_EXIST) { + LOG_ERR(PFX "%s: could not find library for uio name: %s", + nic->log_name, raw); + rc = -EINVAL; + goto error; + } + + /* fill the lib info */ + nic->nic_library = handle; + nic->ops = handle->ops; + (*nic->ops->lib_ops.get_library_name) (&nic->library_name, + &name_size); + } else { + /* Get the uio sysfs name from the NIC library */ + (*nic->ops->lib_ops.get_uio_name) (&raw_tmp, &name_size); + + if (strncmp(raw, raw_tmp, name_size) != 0) { + LOG_ERR(PFX "%s: uio names not equal: " + "expecting %s got %s from %s", + nic->log_name, raw, raw_tmp, temp_path); + rc = -EINVAL; + goto error; + } + } + + LOG_INFO(PFX "%s: Verified uio name %s with library %s", + nic->log_name, raw, nic->library_name); + +error: + if (raw) + free(raw); + + return rc; +} + +/** + * nic_fill_name() - This will initialize all the hardware resources underneath + * a struct cnic_uio device + * @param nic - The nic device to attach the hardware with + * @return 0 on success, on failure a errno will be returned + */ +int nic_fill_name(nic_t *nic) +{ + int rc; + + if ((nic->config_device_name != NULL) && + (memcmp(uio_base_dir, nic->config_device_name, + sizeof(uio_base_dir) - 1) == 0)) { + uint16_t uio_minor; + char eth_name[sizeof(nic->eth_device_name)]; + + wait_for_file_node_timed(nic, nic->config_device_name, 5); + + /* Determine the minor number for the UIO device */ + rc = sscanf(nic->config_device_name, uio_udev_path_template, + &uio_minor); + if (rc != 1) { + LOG_WARN(PFX "%s: Could not parse for minor number", + nic->uio_device_name); + return -EINVAL; + } else + nic->uio_minor = uio_minor; + + nic->uio_device_name = nic->config_device_name; + + /* Determine the assoicated physical network interface */ + rc = from_uio_find_associated_eth_device(nic, + nic->uio_minor, + eth_name, + sizeof(eth_name)); + if (rc != 0) { + LOG_WARN(PFX "%s: Couldn't find associated eth device", + nic->uio_device_name); + } else { + memcpy(nic->eth_device_name, + eth_name, sizeof(eth_name)); + } + + LOG_INFO(PFX "%s: configured for uio device for %s", + nic->log_name, nic->uio_device_name); + + } else { + LOG_INFO(PFX "looking for uio device for %s", + nic->config_device_name); + + rc = from_phys_name_find_assoicated_uio_device(nic); + if (rc < 0) { + LOG_ERR(PFX "Could not determine UIO name for %s", + nic->config_device_name); + + return -rc; + } + + nic->uio_minor = rc; + + if (nic->flags & NIC_UIO_NAME_MALLOC) + free(nic->uio_device_name); + + nic->uio_device_name = + malloc(sizeof(uio_udev_path_template) + 8); + if (nic->uio_device_name == NULL) { + LOG_INFO(PFX "%s: Couldn't malloc space for uio name", + nic->log_name); + return -ENOMEM; + } + + snprintf(nic->uio_device_name, + sizeof(uio_udev_path_template) + 8, + uio_udev_path_template, nic->uio_minor); + + nic->flags |= NIC_UIO_NAME_MALLOC; + } + + return 0; +} + +void cnic_get_sysfs_pci_resource_path(nic_t *nic, int resc_no, + char *sys_path, size_t size) +{ + /* Build the path to sysfs pci resource */ + snprintf(sys_path, size, + cnic_uio_sysfs_resc_template, nic->uio_minor, resc_no); + +} + +void prepare_library(nic_t *nic) +{ + int rc; + NIC_LIBRARY_EXIST_T exist; + nic_lib_handle_t *handle = NULL; + + nic_fill_name(nic); + + /* No assoicated library, we can skip it */ + if (nic->library_name != NULL) { + /* Check that we have the proper NIC library loaded */ + exist = does_nic_library_exist(nic->library_name, &handle); + if (exist == NIC_LIBRARY_DOESNT_EXIST) { + LOG_ERR(PFX "NIC library doesn't exists: %s", + nic->library_name); + goto error; + } else if (handle && (nic->nic_library == handle) && + (nic->ops == handle->ops)) { + LOG_INFO("%s: Have NIC library '%s'", + nic->log_name, nic->library_name); + } + } + + /* Verify the NIC library to use */ + rc = nic_verify_uio_sysfs_name(nic); + if (rc != 0) { + /* Determine the NIC library to use based on the PCI Id */ + rc = find_set_nic_lib(nic); + if (rc != 0) { + LOG_ERR(PFX "%s: Couldn't find NIC library", + nic->log_name); + goto error; + } + + } + + LOG_INFO("%s: found NIC with library '%s'", + nic->log_name, nic->library_name); +error: + return; +} + +void prepare_nic_thread(nic_t *nic) +{ + pthread_attr_t attr; + int rc; + + pthread_mutex_lock(&nic->nic_mutex); + if (nic->thread == INVALID_THREAD) { + struct timespec ts; + struct timeval tp; + + LOG_INFO(PFX "%s: spinning up thread for nic", nic->log_name); + + /* Try to spin up the nic thread */ + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + rc = pthread_create(&nic->thread, &attr, nic_loop, nic); + if (rc != 0) { + LOG_ERR(PFX "%s: Couldn't create thread for nic", + nic->log_name); + goto error; + } + + /* Convert from timeval to timespec */ + rc = gettimeofday(&tp, NULL); + ts.tv_sec = tp.tv_sec; + ts.tv_nsec = tp.tv_usec * 1000; + ts.tv_sec += 5; /* TODO: hardcoded wait for 5 seconds */ + + /* Wait for the nic loop thread to to running */ + rc = pthread_cond_timedwait(&nic->nic_loop_started_cond, + &nic->nic_mutex, &ts); + + LOG_INFO("Created nic thread: %s", nic->log_name); + } + + pthread_mutex_unlock(&nic->nic_mutex); + +error: + return; +} + +/******************************************************************************* + * Functions used to enable/disable the NIC + ******************************************************************************/ +/** + * nic_enable() - Function used to enable the NIC + * @param nic - NIC to enable + * @return 0 on success, <0 on failure + */ +int nic_enable(nic_t *nic) +{ + if (nic->flags & NIC_GOING_DOWN) { + LOG_INFO(PFX "%s: NIC device is going down, " + "flag: 0x%x state: 0x%x", + nic->log_name, nic->flags, nic->state); + return -EINVAL; + } + if (nic->state == NIC_STOPPED) { + struct timespec ts; + struct timeval tp; + int rc; + + pthread_mutex_lock(&nic->nic_mutex); + /* Signal the device to enable itself */ + pthread_cond_broadcast(&nic->enable_wait_cond); + + nic->flags &= ~NIC_DISABLED; + nic->flags |= NIC_ENABLED; + nic->flags |= NIC_ENABLED_PENDING; + + /* Convert from timeval to timespec */ + rc = gettimeofday(&tp, NULL); + ts.tv_sec = tp.tv_sec; + ts.tv_nsec = tp.tv_usec * 1000; + ts.tv_sec += 100; + + /* Wait for the device to be enabled */ + rc = pthread_cond_timedwait(&nic->enable_done_cond, + &nic->nic_mutex, &ts); + if (rc == 0 && nic->flags & NIC_ENABLED) { + LOG_DEBUG(PFX "%s: device enabled", nic->log_name); + } else { + nic->flags &= ~NIC_ENABLED; + nic->flags |= NIC_DISABLED; + nic->flags &= ~NIC_ENABLED_PENDING; + + LOG_ERR(PFX "%s: waiting to finish nic_enable err: %s", + nic->log_name, strerror(rc)); + } + pthread_mutex_unlock(&nic->nic_mutex); + + return rc; + } else { + LOG_INFO(PFX "%s: device already enabled: " + "flag: 0x%x state: 0x%x", + nic->log_name, nic->flags, nic->state); + return -EALREADY; + } +} + +/** + * nic_disable() - Function used to disable the NIC + * @param nic - NIC to disble + * @return void + */ +void nic_disable(nic_t *nic, int going_down) +{ + if (nic->state == NIC_STARTED_RUNNING || + nic->state == NIC_RUNNING) { + struct timespec ts; + struct timeval tp; + int rc; + + /* Wait for the device to be disabled */ + pthread_mutex_lock(&nic->nic_mutex); + + nic->flags &= ~NIC_ENABLED; + nic->flags |= NIC_DISABLED; + nic->flags &= ~NIC_STARTED_RUNNING; + nic->state = NIC_STOPPED; + + if (going_down) + nic->flags |= NIC_GOING_DOWN; + + /* Convert from timeval to timespec */ + rc = gettimeofday(&tp, NULL); + if (rc) { + LOG_ERR("gettimeofday failed, should never happen: %d\n", errno); + pthread_mutex_unlock(&nic->nic_mutex); + return; + } + + ts.tv_sec = tp.tv_sec; + ts.tv_nsec = tp.tv_usec * 1000; + ts.tv_sec += 5; /* TODO: hardcoded wait for 5 seconds */ + + /* Wait for the device to be disabled */ + rc = pthread_cond_timedwait(&nic->disable_wait_cond, + &nic->nic_mutex, &ts); + if (rc) { + LOG_ERR("cond_timedwait failed, should never happen: %d\n", errno); + } + + pthread_mutex_unlock(&nic->nic_mutex); + + LOG_DEBUG(PFX "%s: device disabled", nic->log_name); + + } else { + LOG_WARN(PFX "%s: device already disabled: " + "flag: 0x%x state: 0x%x", + nic->log_name, nic->flags, nic->state); + } +} + +void nic_close_all() +{ + nic_t *nic; + + pthread_mutex_lock(&nic_list_mutex); + + /* Start the shutdown process */ + nic = nic_list; + while (nic != NULL) { + pthread_mutex_lock(&nic->nic_mutex); + nic_close(nic, 1, FREE_ALL_STRINGS); + pthread_mutex_unlock(&nic->nic_mutex); + + nic = nic->next; + } + pthread_mutex_unlock(&nic_list_mutex); + + LOG_INFO(PFX "All NICs closed"); +} + +void nic_remove_all() +{ + nic_t *nic, *nic_next; + + pthread_mutex_lock(&nic_list_mutex); + + /* Start the shutdown process */ + nic = nic_list; + while (nic != NULL) { + nic_next = nic->next; + pthread_mutex_lock(&nic->nic_mutex); + nic_close(nic, 1, FREE_ALL_STRINGS); + pthread_mutex_unlock(&nic->nic_mutex); + nic_remove(nic); + nic = nic_next; + } + pthread_mutex_unlock(&nic_list_mutex); + + LOG_INFO(PFX "All NICs removed"); +} + + +/****************************************************************************** + * Routines to read initialized UIO values from sysfs + *****************************************************************************/ +/** + * determine_initial_uio_events() - This utility function will + * determine the number of uio events that have occured on the + * given device. This value is read from the UIO sysfs entry + * @param dev - device to read from + * @param num_of_event - number of UIO events + * @return 0 is success, <0 failure + */ +int detemine_initial_uio_events(nic_t *nic, uint32_t *num_of_events) +{ + char *raw = NULL; + uint32_t raw_size = 0; + ssize_t elements_read; + char temp_path[sizeof(cnic_sysfs_uio_event_template) + 8]; + int rc; + + /* Capture RX buffer size */ + snprintf(temp_path, sizeof(temp_path), + cnic_sysfs_uio_event_template, nic->uio_minor); + + rc = capture_file(&raw, &raw_size, temp_path); + if (rc != 0) + goto error; + + elements_read = sscanf(raw, "%d", num_of_events); + if (elements_read != 1) { + LOG_ERR(PFX "%s: Couldn't parse UIO events size from %s", + nic->log_name, temp_path); + rc = -EIO; + goto error; + } + + rc = 0; +error: + if (raw) + free(raw); + + return rc; +} + +int get_iscsi_transport_handle(nic_t *nic, uint64_t *handle) +{ + char *raw = NULL; + uint32_t raw_size = 0; + ssize_t elements_read; + char temp_path[sizeof(iscsi_transport_handle_template) + 8]; + int rc; + + /* Capture RX buffer size */ + snprintf(temp_path, sizeof(temp_path), + iscsi_transport_handle_template, nic->library_name); + + rc = capture_file(&raw, &raw_size, temp_path); + if (rc != 0) + goto error; + + elements_read = sscanf(raw, "%lu", handle); + if (elements_read != 1) { + LOG_ERR(PFX "%s: Couldn't parse transport handle from %s", + nic->log_name, temp_path); + rc = -EIO; + goto error; + } + + rc = 0; +error: + if (raw != NULL) + free(raw); + + return rc; +} + +/** + * nic_set_all_nic_iface_mac_to_parent() - This is a utility function used to + * intialize all the MAC addresses of the network interfaces for a given + * CNIC UIO device + * Call with nic mutex held + * @param dev - CNIC UIO device to initialize + */ +void nic_set_all_nic_iface_mac_to_parent(nic_t *nic) +{ + nic_interface_t *current, *vlan_current; + + current = nic->nic_iface; + while (current != NULL) { + /* Set the initial MAC address of this interface to the parent + * adapter */ + memcpy(current->mac_addr, nic->mac_addr, 6); + + vlan_current = current->vlan_next; + while (vlan_current != NULL) { + memcpy(vlan_current->mac_addr, nic->mac_addr, 6); + vlan_current = vlan_current->vlan_next; + } + current = current->next; + } +} + +/******************************************************************************* + * NIC packet handling functions + ******************************************************************************/ +/** + * nic_alloc_packet_buffer() - Used to allocate a packet buffer used to + * send a TX packet later + * @param nic - nic device to send the packet on + * @param nic_iface - nic interface to send out on + * @param buf - pointer to the buffer to send + * @param buf_size - size in bytes of the buffer to send + * @return pointer to the allocated packet buffer + * NULL if memory could not be allocated + */ +static packet_t *nic_alloc_packet_buffer(nic_t *nic, + nic_interface_t *nic_iface, + uint8_t *buf, size_t buf_size) +{ + packet_t *pkt; + + pkt = malloc(sizeof(*pkt) + buf_size); + if (pkt == NULL) { + LOG_ERR(PFX "%s: Couldn't allocate space for packet buffer", + nic->log_name); + return NULL; + } + + pkt->next = NULL; + pkt->nic = nic; + pkt->nic_iface = nic_iface; + pkt->buf_size = buf_size; + memcpy(pkt->buf, buf, buf_size); + + return pkt; +} + +/** + * nic_queue_tx_packet() - Used to queue a TX packet buffer to send later + * @param nic - NIC device to send the packet on + * @param nic_iface - NIC interface to send on the packet on + * @param pkt - packet to queue + * @return 0 if successful or <0 if unsuccessful + */ +int nic_queue_tx_packet(nic_t *nic, + nic_interface_t *nic_iface, packet_t *pkt) +{ + packet_t *queued_pkt; + + queued_pkt = nic_alloc_packet_buffer(nic, nic_iface, + pkt->buf, pkt->buf_size); + if (queued_pkt == NULL) { + LOG_ERR(PFX "%s: Couldn't allocate tx packet to queue", + nic->log_name); + return -ENOMEM; + } + + if (nic->tx_packet_queue == NULL) { + nic->tx_packet_queue = queued_pkt; + } else { + packet_t *current_pkt; + + current_pkt = nic->tx_packet_queue; + while (current_pkt->next != NULL) + current_pkt = current_pkt->next; + + current_pkt->next = queued_pkt; + } + + LOG_DEBUG(PFX "%s: tx packet queued", nic->log_name); + + return 0; +} + +/** + * nic_dequeue_tx_packet() - Used pop a TX packet buffer of the TX + * @param dev - cnic_uio device to send the packet on + * @param buf - pointer to the buffer to send + * @param buf_size - size in bytes of the buffer to send + * @return NULL if there are no more TX packet buffers to send + * pointer to the packet buffer which is detached from the device + */ +packet_t *nic_dequeue_tx_packet(nic_t *nic) +{ + packet_t *pkt; + + pkt = nic->tx_packet_queue; + + /* There is a packet buffer to send, time to detach it from the + * cnic_uio device */ + if (pkt != NULL) { + nic->tx_packet_queue = pkt->next; + pkt->next = NULL; + } + + return pkt; +} + +void nic_fill_ethernet_header(nic_interface_t *nic_iface, + void *data, + void *src_addr, void *dest_addr, + int *pkt_size, void **start_addr, + uint16_t ether_type) +{ + struct ether_header *eth; + uint16_t *vlan_hdr; + + eth = data; + + memcpy(eth->ether_shost, src_addr, ETH_ALEN); + memcpy(eth->ether_dhost, dest_addr, ETH_ALEN); + + vlan_hdr = (uint16_t *) (eth + 1); + eth->ether_type = htons(ether_type); + + *start_addr = vlan_hdr; +} + +/******************************************************************************* + * NIC interface management utility functions + ******************************************************************************/ +/** + * nic_find_nic_iface() - This function is used to find an interface + * from the NIC + * @param nic - NIC to look for network interfaces + * @param vlan_id - VLAN id to look for + * @param protocol - either AF_INET or AF_INET6 + * @param iface_num - iface num to use if present + * @param request_type - IPV4/6 DHCP/STATIC + * @return nic_iface - if found network interface with the given VLAN ID + * if not found a NULL is returned + */ +nic_interface_t *nic_find_nic_iface(nic_t *nic, + uint16_t protocol, + uint16_t vlan_id, + int iface_num, + int request_type) +{ + nic_interface_t *current = nic->nic_iface; + nic_interface_t *current_vlan = NULL; + + while (current != NULL) { + LOG_DEBUG(PFX "%s: incoming protocol: %d, vlan_id:%d iface_num: %d, request_type: %d", + nic->log_name, protocol, vlan_id, iface_num, request_type); + LOG_DEBUG(PFX "%s: host:%d iface_num: 0x%x VLAN: %d protocol: %d", + nic->log_name, nic->host_no, current->iface_num, current->vlan_id, current->protocol); + if (current->protocol != protocol) + goto next; + + /* Check for iface_num first */ + if (iface_num != IFACE_NUM_INVALID) { + if (current->iface_num == iface_num) { + /* Exception is when iface_num == 0, need to + check for request_type also if != + IP_CONFIG_OFF */ + if (!iface_num && request_type != + IP_CONFIG_OFF) { + if (current->request_type == + request_type) + goto found; + } else { + goto found; + } + } + } else if (vlan_id == NO_VLAN) { + /* Just return the top of the family */ + goto found; + } else { + if ((current->vlan_id == vlan_id) && + ((request_type == IP_CONFIG_OFF) || + (current->request_type == request_type))) + goto found; + } + /* vlan_next loop */ + current_vlan = current->vlan_next; + while (current_vlan != NULL) { + if (iface_num != IFACE_NUM_INVALID) { + if (current_vlan->iface_num == iface_num) { + if (!iface_num && request_type != + IP_CONFIG_OFF) { + if (current_vlan->request_type + == request_type) + goto vlan_found; + } else { + goto vlan_found; + } + } + } + if ((current_vlan->vlan_id == vlan_id) && + ((request_type == IP_CONFIG_OFF) || + (current_vlan->request_type == request_type))) + goto vlan_found; + + current_vlan = current_vlan->vlan_next; + } +next: + current = current->next; + } +vlan_found: + current = current_vlan; +found: + return current; +} + +/* Called with nic mutex held */ +void persist_all_nic_iface(nic_t *nic) +{ + nic_interface_t *current_vlan, *current; + + current = nic->nic_iface; + while (current != NULL) { + current->flags |= NIC_IFACE_PERSIST; + current_vlan = current->vlan_next; + while (current_vlan != NULL) { + current_vlan->flags |= NIC_IFACE_PERSIST; + current_vlan = current_vlan->vlan_next; + } + current = current->next; + } +} + +/* Sets the nic_iface to the front of the AF */ +void set_nic_iface(nic_t *nic, nic_interface_t *nic_iface) +{ + nic_interface_t *current, *prev; + nic_interface_t *current_vlan, *prev_vlan; + + prev = NULL; + current = nic->nic_iface; + while (current != NULL) { + if (current->protocol != nic_iface->protocol) + goto next; + /* If its already on top of the list, exit */ + if (current == nic_iface) + goto done; + + prev_vlan = current; + current_vlan = current->vlan_next; + + while (current_vlan != NULL) { + if (current_vlan == nic_iface) { + /* Found inside the vlan list */ + /* For vlan == 0, place on top of + the AF list */ + prev_vlan->vlan_next = + current_vlan->vlan_next; + current_vlan->vlan_next = current; + if (prev) + prev->next = current_vlan; + else + nic->nic_iface = current_vlan; + goto done; + } + prev_vlan = current_vlan; + current_vlan = current_vlan->vlan_next; + } +next: + prev = current; + current = current->next; + } +done: + return; +} + +/******************************************************************************* + * Packet management utility functions + ******************************************************************************/ +/** + * get_next_packet_in_queue() - This function will return the next packet in + * the queue + * @param queue - the queue to pull the packet from + * @return the packet in the queue + */ +static packet_t *get_next_packet_in_queue(packet_t **queue) +{ + packet_t *pkt; + + if (*queue == NULL) + return NULL; + + pkt = *queue; + *queue = pkt->next; + + return pkt; +} + +/** + * get_next_tx_packet() - This function will return the next packet in + * the TX queue + * @param nic - NIC to pull the TX packet from + * @return the packet in hte queue + */ +packet_t *get_next_tx_packet(nic_t *nic) +{ + return get_next_packet_in_queue(&nic->tx_packet_queue); +} + +/** + * get_next_free_packet() - This function will return the next packet in + * the free queue + * @param nic - NIC to pull the RX packet from + * @return the packet in hte queue + */ +packet_t *get_next_free_packet(nic_t *nic) +{ + packet_t *pkt; + pthread_mutex_lock(&nic->free_packet_queue_mutex); + pkt = get_next_packet_in_queue(&nic->free_packet_queue); + pthread_mutex_unlock(&nic->free_packet_queue_mutex); + + if (pkt != NULL) + reset_packet(pkt); + + return pkt; +} + +/** + * put_packet_in_queue() - This function will place the packet in the given + * queue + * @param pkt - the packet to place + * @param queue - the queue to place the packet + * @return the packet in the queue + */ +static void put_packet_in_queue(packet_t *pkt, packet_t **queue) +{ + if (*queue == NULL) + *queue = pkt; + else { + pkt->next = *queue; + *queue = pkt; + } +} + +/** + * put_packet_in_tx_queue() - This function will place the packet in + * the TX queue + * @param pkt - packet to place + * @param nic - NIC to pull the TX packet from + * @return the packet in hte queue + */ +void put_packet_in_tx_queue(packet_t *pkt, nic_t *nic) +{ + return put_packet_in_queue(pkt, &nic->tx_packet_queue); +} + +/** + * put_packet_in_free_queue() - This function will place the packet in + * the RX queue + * @param pkt - packet to place + * @param nic - NIC to pull the RX packet from + * @return the packet in hte queue + */ +void put_packet_in_free_queue(packet_t *pkt, nic_t *nic) +{ + pthread_mutex_lock(&nic->free_packet_queue_mutex); + put_packet_in_queue(pkt, &nic->free_packet_queue); + pthread_mutex_unlock(&nic->free_packet_queue_mutex); +} + +uint32_t calculate_default_netmask(uint32_t ip_addr) +{ + uint32_t netmask; + + if (IN_CLASSA(ntohl(ip_addr))) + netmask = htonl(IN_CLASSA_NET); + else if (IN_CLASSB(ntohl(ip_addr))) + netmask = htonl(IN_CLASSB_NET); + else if (IN_CLASSC(ntohl(ip_addr))) + netmask = htonl(IN_CLASSC_NET); + else { + LOG_ERR("Unable to guess netmask for address %x\n", &ip_addr); + return -1; + } + + return netmask; +} + +void dump_packet_to_log(struct nic_interface *iface, + uint8_t *buf, uint16_t buf_len) +{ + + FILE *file; + char str[80]; + int i, count; + + file = fmemopen(str, sizeof(str), "w+"); + if (file == NULL) { + LOG_ERR(PFX "Could not create logging file stream for packet " + "logging: [%d: %s]", errno, strerror(errno)); + return; + } + + LOG_PACKET(PFX "%s: Start packet dump len: %d", iface->parent->log_name, + buf_len); + + for (i = 0; i < buf_len; i++) { + rewind(file); + fprintf(file, "%03x: ", i); + + for (count = 0; (count < 8) && i < buf_len; count++, i++) + fprintf(file, " %02x", buf[i]); + fflush(file); + + LOG_PACKET(PFX "%s: %s", iface->parent->log_name, str); + } + + LOG_PACKET(PFX "%s: end packet dump", iface->parent->log_name); + + fclose(file); +} + +/******************************************************************************* + * File Management + ******************************************************************************/ + /** + * determine_file_size_read() - when fstat doesn't work on filepath + * within the /proc filesytem, we need to read/count the size of the file + * until we hit a EOF + * @parm filepath - path of the file in which to determine the filesize in + * bytes + * @return file size in bytes, <0 on failure + */ +int determine_file_size_read(const char *filepath) +{ + size_t total_size = 0; + ssize_t size = 1; + int fd; + char buf[1024]; + + fd = open(filepath, O_RDONLY); + if (fd == -1) { + LOG_ERR("Could not open file: %s [%s]", + filepath, strerror(errno)); + return -1; + } + + while (size > 0) { + size = read(fd, buf, sizeof(buf)); + + switch (size) { + case 0: + break; + case -1: + LOG_ERR("Error reading file: %s [%s]", + filepath, strerror(errno)); + total_size = -1; + break; + default: + total_size += size; + break; + } + } + + close(fd); + + return total_size; +} + +/** + * capture_file() - Used to capture a file into a buffer + * @param raw - This pointer will be set to the buffer which will hold the + * file contents + * @param raw_size - This is the size of the buffer returned + * @param path - The file path to capture the data from + * @return 0 is returned on success, <0 is returned on failure + */ +int capture_file(char **raw, uint32_t *raw_size, const char *path) +{ + FILE *fp; + size_t read_size; + int rc = 0; + int file_size; + + file_size = determine_file_size_read(path); + if (file_size < 0) { + LOG_ERR("Could not determine size %s", path); + return -EIO; + } + + fp = fopen(path, "r"); + if (fp == NULL) { + LOG_ERR("Could not open path %s [%s]", path, strerror(errno)); + return -EIO; + } + + *raw = malloc(file_size); + if (*raw == NULL) { + LOG_ERR("Could not malloc space for capture %s", path); + rc = -ENOMEM; + goto error; + } + + read_size = fread(*raw, file_size, 1, fp); + if (!read_size) { + LOG_ERR("Could not read capture, path: %s len: %d [%s]", + path, file_size, strerror(ferror(fp))); + free(*raw); + *raw = NULL; + rc = errno; + } else + *raw_size = file_size; + +error: + fclose(fp); + + LOG_INFO("Done capturing %s", path); + + return rc; +} diff --git a/iscsiuio/src/unix/nic_utils.h b/iscsiuio/src/unix/nic_utils.h new file mode 100644 index 0000000..e4cf5c1 --- /dev/null +++ b/iscsiuio/src/unix/nic_utils.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * nic_util.h - NIC utility functions + * + */ +#ifndef __NIC_UTILS_H__ +#define __NIC_UTILS_H__ + +#include "nic.h" + +/****************************************************************************** + * Function Prototype + ******************************************************************************/ +int manually_trigger_uio_event(nic_t *nic, int uio_minor); + +int nic_discover_iscsi_hosts(); + +int enable_mutlicast(nic_t *nic); +int disable_mutlicast(nic_t *nic); + +int from_netdev_name_find_nic(char *interface_name, nic_t **nic); + +int from_host_no_find_associated_eth_device(int host_no, nic_t **nic); + +int from_phys_name_find_assoicated_uio_device(nic_t *nic); + +int nic_queue_tx_packet(nic_t *nic, + nic_interface_t *nic_iface, packet_t *pkt); + +packet_t *nic_dequeue_tx_packet(nic_t *nic); + +void nic_fill_ethernet_header(nic_interface_t *nic_iface, + void *data, + void *src_addr, void *dest_addr, + int *pkt_size, void **start_addr, + uint16_t ether_type); + +struct nic_interface *nic_find_nic_iface(nic_t *nic, uint16_t protocol, + uint16_t vlan_id, int iface_num, + int request_type); +void set_nic_iface(nic_t *nic, nic_interface_t *nic_iface); + +void persist_all_nic_iface(nic_t *nic); + +int add_vlan_interfaces(nic_t *nic); + +int nic_verify_uio_sysfs_name(nic_t *nic); +void cnic_get_sysfs_pci_resource_path(nic_t *nic, int resc_no, + char *sys_path, size_t size); +void nic_close_all(); +void nic_remove_all(); + +int detemine_initial_uio_events(nic_t *nic, uint32_t *num_of_events); + +uint32_t calculate_default_netmask(uint32_t ip_addr); + +void prepare_nic_thread(nic_t *nic); +void prepare_library(nic_t *nic); + +int nic_enable(nic_t *nic); +void nic_disable(nic_t *nic, int going_down); + +void dump_packet_to_log(struct nic_interface *iface, + uint8_t *buf, uint16_t buf_len); + +int determine_file_size_read(const char *filepath); +int capture_file(char **raw, uint32_t *raw_size, const char *path); + +int get_iscsi_transport_handle(nic_t *nic, uint64_t *handle); + +#endif /* __NIC_UTILS_H__ */ diff --git a/iscsiuio/src/unix/nic_vlan.c b/iscsiuio/src/unix/nic_vlan.c new file mode 100644 index 0000000..eb33381 --- /dev/null +++ b/iscsiuio/src/unix/nic_vlan.c @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * nic_vlan.c - uIP user space stack VLAN utilities + * + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "logger.h" +#include "nic.h" +#include "nic_utils.h" +#include "nic_vlan.h" + +/******************************************************************************* + * Constants + ******************************************************************************/ +#define PFX "vlan" + +static const char proc_vlan_config_path[] = "/proc/net/vlan/config"; + +/******************************************************************************* + * Resolving Found VLAN's for CNIC + ******************************************************************************/ +int init_vlan_found_handle(struct vlan_found_handle *found_handle, + struct vlan_handle *handle) +{ + memset(found_handle, 0, sizeof(*found_handle)); + + found_handle->entries = malloc(found_handle->num_of_entries * + sizeof(struct vlan_found_entry)); + if (found_handle->entries == NULL) { + LOG_ERR("Could not allocate space for found entries"); + return -ENOMEM; + } + + found_handle->handle = handle; + found_handle->num_of_entries = handle->num_of_entries; + + memset(found_handle->entries, 0, found_handle->num_of_entries * + sizeof(struct vlan_found_entry)); + + handle->outstanding_found_handles++; + + return 0; +} + +void release_vlan_found_handle(struct vlan_found_handle *found_handle) +{ + if (found_handle->entries != NULL) { + free(found_handle->entries); + found_handle->entries = NULL; + } + + found_handle->num_of_entries = 0; + + found_handle->handle->outstanding_found_handles--; + + found_handle->handle = NULL; + +} + +/******************************************************************************* + * Resolving VLAN's for CNIC + ******************************************************************************/ +/** + * init_vlan_handle() - Used to initialize struct ipv4_route_handle so + * that is can be used + * @param handle - Pointer to struct ipv4_route_handle to initialize + * @return 0 on success and <0 on failure + */ +void init_vlan_table(struct vlan_handle *handle) +{ + handle->entries = NULL; + handle->num_of_entries = 0; +} + +/** + * parse_vlan_table() - Given the raw dump of a Linux vlan table, this + * function will parse the into entries held by + * struct vlan_handle + * @param handle - struct vlan_handle used to hold the parsed contents + * @param raw - buffer to parse the contents from + * @param raw_size - size of the buffer in bytes + * @return 0 on success, <0 on failure + */ +int parse_vlan_table(struct vlan_handle *handle, char *raw, uint32_t raw_size) +{ + FILE *fp; + int i; + char *token; + size_t size; + int rc; + + token = raw; + + /* determine the number of entries */ + while (*token != '\0') { + if (*token == '\n') + handle->num_of_entries++; + + token++; + } + + /* There are 2 lines which describe the vlan table + * This lines need to be skipped with counting */ + handle->num_of_entries -= 2; + + LOG_INFO("Number of vlan entries: %d", handle->num_of_entries); + + size = handle->num_of_entries * sizeof(struct vlan_entry); + handle->entries = malloc(size); + if (handle->entries == NULL) { + LOG_ERR + ("Couldn't malloc space to parse vlan table. entires: %d " + "size: %d", + handle->num_of_entries, size); + return -ENOMEM; + } + + fp = fmemopen(raw, raw_size, "r"); + if (fp == NULL) { + LOG_ERR("Could not open raw dump of vlan table"); + rc = errno; + goto fmemopen_error; + } + + if (fscanf(fp, "%*[^\n]\n") < 0) { /* Skip the first line. */ + LOG_ERR("Empty or missing line, or read error"); + rc = -EIO; + goto error; + } + + if (fscanf(fp, "%*[^\n]\n") < 0) { /* Skip the second line. */ + LOG_ERR("Empty or missing line, or read error"); + rc = -EIO; + goto error; + } + + i = 0; + /* Time to parse the routing table */ + while (1) { + struct vlan_entry *entry = &handle->entries[i]; + int r; + + r = fscanf(fp, "%15s |%hu |%15s", + entry->vlan_iface_name, + &entry->vlan_id, entry->phy_iface_name); + if (r != 3) { + if (feof(fp)) { /* EOF with no (nonspace) chars read. */ + break; + } + + LOG_WARN("Parsing error: parsed %d elements", r); + break; + } + + i++; + + LOG_DEBUG("Vlan %d: vlan iface:%s vlan id:%d phys iface:%s", + i, + entry->vlan_iface_name, + entry->vlan_id, entry->phy_iface_name); + } + + fclose(fp); + + return 0; + +error: + fclose(fp); + +fmemopen_error: + if (handle->entries != NULL) + free(handle->entries); + + return rc; +} + +/** + * capture_vlan_table() - This function will snapshot the Linux vlan + * routing table for further processing + * @param handle - struct vlan_handle used to hold the routing context + * @return 0 on success, <0 on failure + */ +int capture_vlan_table(struct vlan_handle *handle) +{ + char *raw = NULL; + uint32_t raw_size = 0; + int rc; + + rc = capture_file(&raw, &raw_size, proc_vlan_config_path); + if (rc != 0) + goto error; + + rc = parse_vlan_table(handle, raw, raw_size); + if (rc != 0) + goto error; + +error: + if (raw != NULL) + free(raw); + + return rc; +} + +/** + * release_vlan_table() - This function will free all resources used by + * the handle + * @param handle - struct vlan_handle used to hold the routing context + */ +void release_vlan_table(struct vlan_handle *handle) +{ + if (handle->entries != NULL) { + free(handle->entries); + handle->entries = NULL; + } + + handle->num_of_entries = 0; +} + +/** + * find_phy_using_vlan_interface() - Given the interface name determine VLAN + * tag ID to match either the physical or VLAN interface name + * @param vlan_iface_name - VLAN interface used to find the physical + * interface + * @param phy_iface_name - returned value is the physical interface name + * @param vlan_id - returned value is the VLAN id + * @return 1 is returned if the interface is a VLAN, 0 if the interface is not + * <0 is returned if there is an error + */ +int find_phy_using_vlan_interface(struct vlan_handle *handle, + char *vlan_iface_name, + char **phy_iface_name, uint16_t *vlan_id) +{ + int i, rc = 0; + + for (i = 0; i < handle->num_of_entries; i++) { + struct vlan_entry *entry = &handle->entries[i]; + + /* Compare VLAN interface names to find a match */ + if (strcmp(entry->vlan_iface_name, vlan_iface_name) == 0) { + *phy_iface_name = entry->phy_iface_name; + *vlan_id = entry->vlan_id; + rc = 1; + break; + } + } + + return rc; +} + +/** + * find_vlans_using_phy_interface() - Given the physical interface name this + * function will determine the VLAN interface name and VLAN ID + * @param iface_name - physical interface used to find the vlan interface + * @param vlan_iface_name - returned value is the VLAN interface name + * @return The number of VLAN interfaces found + */ +int find_vlans_using_phy_interface(struct vlan_handle *handle, + struct vlan_found_handle *found_handle, + char *phy_iface_name) +{ + int i, num_found = 0; + + for (i = 0; i < handle->num_of_entries; i++) { + struct vlan_entry *entry = &handle->entries[i]; + + /* Compare interface names to find a match */ + if (strcmp(entry->phy_iface_name, phy_iface_name) == 0) { + found_handle->entries[i].found = VLAN_ENTRY_FOUND; + num_found++; + } + } + + return num_found; +} + +/** + * valid_vlan() - determine if the vlan value which is passed is valid + * @param vlan - vlan value to test + * @return 0 - not valid, 1 - valid + */ +int valid_vlan(short int vlan) +{ + /* Allow vlan 1 to connect */ + if (vlan > 0 && vlan < 4095) + return 1; + + return 0; +} diff --git a/iscsiuio/src/unix/nic_vlan.h b/iscsiuio/src/unix/nic_vlan.h new file mode 100644 index 0000000..610f721 --- /dev/null +++ b/iscsiuio/src/unix/nic_vlan.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * nic_vlan.h - uIP user space stack VLAN utilities + * + */ +#ifndef __NIC_VLAN_H__ +#define __NIC_VLAN_H__ + +#include + +/* Used to hold entries in the vlan table */ +struct vlan_entry { + char vlan_iface_name[16]; + char phy_iface_name[16]; + uint16_t vlan_id; +}; + +struct vlan_handle { + struct vlan_entry *entries; + uint32_t num_of_entries; + + uint32_t outstanding_found_handles; +}; + +struct vlan_found_entry { +#define VLAN_ENTRY_FOUND 1 +#define VLAN_ENTRY_NOT_FOUND 0 + uint8_t found; +}; + +struct vlan_found_handle { + struct vlan_handle *handle; + uint32_t num_of_entries; + struct vlan_found_entry *entries; +}; + +/******************************************************************************* + * Function Prototypes + ******************************************************************************/ +void init_vlan_table(struct vlan_handle *handle); +int capture_vlan_table(struct vlan_handle *handle); +void release_vlan_table(struct vlan_handle *handle); + +int find_phy_using_vlan_interface(struct vlan_handle *handle, + char *vlan_iface_name, + char **phy_iface_name, uint16_t *vlan_id); +int find_vlans_using_phy_interface(struct vlan_handle *handle, + struct vlan_found_handle *found_handle, + char *phy_iface_name); + +int init_vlan_found_handle(struct vlan_found_handle *found_handle, + struct vlan_handle *handle); +void release_vlan_found_handle(struct vlan_found_handle *found_handle); + +int valid_vlan(short int vlan); +#endif /* __NIC_VLAN_H__ */ diff --git a/iscsiuio/src/unix/options.h b/iscsiuio/src/unix/options.h new file mode 100644 index 0000000..63b8635 --- /dev/null +++ b/iscsiuio/src/unix/options.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * options.h - CNIC UIO uIP user space stack + * + */ +#ifndef __OPTIONS_H__ +#define __OPTIONS_H__ + +#include +#include +#include + +/****************************************************************************** + * Constants which are tuned at compile time by the user + *****************************************************************************/ + +/** + * MAX_COUNT_NIC_NL_RESP - This is the maximum number of polls uIP will + * try for a kernel response after a PATH_REQ + */ +#define MAX_COUNT_NIC_NL_RESP 128 + +/** + * NLM_BUF_DEFAULT_MAX - This is the buffer size allocated for the send/receive + * buffers used by the uIP Netlink subsystem. This + * value is in bytes. + */ +#define NLM_BUF_DEFAULT_MAX 8192 /* bytes */ + +/****************************************************************************** + * Non adjustable constants + *****************************************************************************/ +#ifndef ETHERTYPE_IP +#define ETHERTYPE_IP 0x0800 /* IP */ +#endif /* ETHERTYPE_IP */ + +#ifndef ETHERTYPE_IPV6 +#define ETHERTYPE_IPV6 0x86dd /* IP protocol version 6 */ +#endif /* ETHERTYPE_IPV6 */ + +#ifndef ETHERTYPE_ARP +#define ETHERTYPE_ARP 0x0806 /* Address resolution */ +#endif /* ETHERTYPE_ARP */ + +#ifndef ETHERTYPE_VLAN +#define ETHERTYPE_VLAN 0x8100 /* IEEE 802.1Q VLAN tagging */ +#endif /* ETHERTYPE_VLAN */ + +#define APP_NAME "iscsiuio" +/* BUILD_DATE is automatically generated from the Makefile */ + +#define DEBUG_OFF 0x1 +#define DEBUG_ON 0x2 + +#define INVALID_FD -1 +#define INVALID_THREAD -1 +#define INVALID_HOST_NO -1 + +struct options { + char debug; + + /* Time the userspace daemon was started */ + time_t start_time; +}; + +extern int event_loop_stop; +extern struct options opt; + +#ifdef WORDS_BIGENDIAN +#define ntohll(x) (x) +#define htonll(x) (x) +#else +#define ntohll(x) bswap_64(x) +#define htonll(x) bswap_64(x) +#endif + +# define likely(x) __builtin_expect(!!(x), 1) +# define unlikely(x) __builtin_expect(!!(x), 0) + +/* taken from Linux kernel, include/linux/compiler-gcc.h */ +/* Optimization barrier */ +/* The "volatile" is due to gcc bugs */ +#define barrier() __asm__ __volatile__("": : :"memory") + +#endif diff --git a/iscsiuio/src/unix/packet.c b/iscsiuio/src/unix/packet.c new file mode 100644 index 0000000..3ce2c6b --- /dev/null +++ b/iscsiuio/src/unix/packet.c @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * packet.c - packet management + * + */ +#include +#include +#include + +#include "logger.h" +#include "packet.h" +#include "nic.h" + +/** + * alloc_packet() - Function used to allocate memory for a packet + * @param max_buf_size - max packet size + * @param priv_size - size of the assoicated private data + * @return NULL if failed, on success return a pointer to the packet + */ +struct packet *alloc_packet(size_t max_buf_size, size_t priv_size) +{ + struct packet *pkt; + void *priv; + + pkt = malloc(max_buf_size + sizeof(struct packet)); + if (pkt == NULL) { + LOG_ERR("Could not allocate any memory for packet"); + return NULL; + } + memset(pkt, 0, max_buf_size + sizeof(struct packet)); + + priv = malloc(priv_size); + if (priv == NULL) { + LOG_ERR("Could not allocate any memory for private structure"); + goto free_pkt; + } + memset(priv, 0, priv_size); + pkt->max_buf_size = max_buf_size; + pkt->priv = priv; + + return pkt; + +free_pkt: + free(pkt); + + return NULL; +} + +void free_packet(struct packet *pkt) +{ + if (pkt->priv != NULL) + free(pkt->priv); + + free(pkt); +} + +/** + * reset_packet() - This will reset the packet fields to default values + * @param pkt - the packet to reset + */ +void reset_packet(packet_t *pkt) +{ + pkt->next = NULL; + + pkt->flags = 0; + pkt->vlan_tag = 0; + + pkt->buf_size = 0; + + pkt->data_link_layer = NULL; + pkt->network_layer = NULL; +} + +int alloc_free_queue(nic_t *nic, size_t num_of_packets) +{ + int i; + + pthread_mutex_lock(&nic->free_packet_queue_mutex); + for (i = 0; i < num_of_packets; i++) { + packet_t *pkt; + + pkt = alloc_packet(STD_MTU_SIZE, STD_MTU_SIZE); + if (pkt == NULL) { + goto done; + } + + reset_packet(pkt); + + pkt->next = nic->free_packet_queue; + nic->free_packet_queue = pkt; + } + +done: + pthread_mutex_unlock(&nic->free_packet_queue_mutex); + + return i; +} + +void free_free_queue(nic_t *nic) +{ + packet_t *pkt, *pkt_next; + + pthread_mutex_lock(&nic->free_packet_queue_mutex); + pkt = nic->free_packet_queue; + while (pkt) { + pkt_next = pkt->next; + free_packet(pkt); + pkt = pkt_next; + } + nic->free_packet_queue = NULL; + pthread_mutex_unlock(&nic->free_packet_queue_mutex); +} diff --git a/iscsiuio/src/unix/packet.h b/iscsiuio/src/unix/packet.h new file mode 100644 index 0000000..19d1db9 --- /dev/null +++ b/iscsiuio/src/unix/packet.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2009-2011, Broadcom Corporation + * Copyright (c) 2014, QLogic Corporation + * + * Written by: Benjamin Li (benli@broadcom.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * packet.h - packet definitions + * + */ +#include + +#ifndef __PACKET_H__ +#define __PACKET_H__ + +#include "nic.h" + +#define STD_MTU_SIZE 1500 + +struct nic; +struct nic_interface; + +typedef struct packet { + struct packet *next; + + uint32_t flags; +#define VLAN_TAGGED 0x0001 + uint16_t vlan_tag; + + size_t max_buf_size; + size_t buf_size; + + uint8_t *data_link_layer; + uint8_t *network_layer; + + struct nic *nic; + struct nic_interface *nic_iface; + + void *priv; + uint8_t buf[]; +} packet_t; + +/****************************************************************************** + * Packet Function Declarations + *****************************************************************************/ +int alloc_free_queue(struct nic *, size_t num_of_packets); +void free_free_queue(struct nic *); +void reset_packet(packet_t *pkt); + +#endif /* __PACKET_H__ */ diff --git a/iscsiuio/src/unix/ping.c b/iscsiuio/src/unix/ping.c new file mode 100644 index 0000000..58a6d14 --- /dev/null +++ b/iscsiuio/src/unix/ping.c @@ -0,0 +1,518 @@ +/* + * Copyright (c) 2015, QLogic Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ping.c - Ping implementation for iscsiuio using ICMP/ICMPv6 + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "iscsi_if.h" + +#include "uip.h" +#include "uip_arp.h" +#include "uip_eth.h" +#include "dhcpc.h" +#include "ipv6_ndpc.h" +#include "ipv6.h" + +#include "logger.h" +#include "nic.h" +#include "nic_utils.h" +#include "options.h" +#include "packet.h" +#include "bnx2.h" +#include "bnx2x.h" +#include "cnic.h" +#include "ping.h" + +#define PFX "ping " + +static void fill_payload_data(struct uip_stack *ustack) +{ + if (ustack->uip_slen) + memset(ustack->uip_appdata, 'A', ustack->uip_slen); +} + +static int prepare_icmpv4_req_pkt(struct ping_conf *png_c, struct packet *pkt, + uip_ip4addr_t *dst_addr) +{ + nic_interface_t *nic_iface = png_c->nic_iface; + struct uip_stack *ustack = &nic_iface->ustack; + struct uip_ipv4_hdr *ipv4_hdr = NULL; + struct uip_icmpv4_hdr *icmpv4_hdr = NULL; + u16_t uip_iph_len = 0; + u16_t icmpv4_hdr_len = 0; + u16_t uip_ip_icmph_len = 0; + int mtu = 1500; + int rc = 0; + + uip_iph_len = UIP_IPv4_H_LEN; + icmpv4_hdr_len = sizeof(*icmpv4_hdr); + uip_ip_icmph_len = uip_iph_len + icmpv4_hdr_len; + + ipv4_hdr = (struct uip_ipv4_hdr *)ustack->network_layer; + + icmpv4_hdr = (struct uip_icmpv4_hdr *) (ustack->network_layer + + sizeof(struct uip_ipv4_hdr)); + + /* fill IP header */ + ipv4_hdr->vhl = 0x45; + ipv4_hdr->tos = 0; + ++ustack->ipid; + ipv4_hdr->ipid[0] = ustack->ipid >> 8; + ipv4_hdr->ipid[1] = ustack->ipid & 0xff; + ipv4_hdr->ipoffset[0] = 0; + ipv4_hdr->ipoffset[1] = 0; + ipv4_hdr->ttl = UIP_TTL; + ipv4_hdr->proto = UIP_PROTO_ICMP; + uip_ip4addr_copy(ipv4_hdr->srcipaddr, ustack->hostaddr); + uip_ip4addr_copy(ipv4_hdr->destipaddr, dst_addr); + + LOG_INFO(PFX "src ipaddr: %d.%d.%d.%d", + uip_ipaddr1(ipv4_hdr->srcipaddr), + uip_ipaddr2(ipv4_hdr->srcipaddr), + uip_ipaddr3(ipv4_hdr->srcipaddr), + uip_ipaddr4(ipv4_hdr->srcipaddr)); + + LOG_INFO(PFX "dest ipaddr: %d.%d.%d.%d", + uip_ipaddr1(ipv4_hdr->destipaddr), + uip_ipaddr2(ipv4_hdr->destipaddr), + uip_ipaddr3(ipv4_hdr->destipaddr), + uip_ipaddr4(ipv4_hdr->destipaddr)); + + /* fill ICMP header */ + icmpv4_hdr->type = ICMP_ECHO; + icmpv4_hdr->icode = 0; + icmpv4_hdr->id = getpid() & 0xffff; + png_c->id = icmpv4_hdr->id; + icmpv4_hdr->seqno = ustack->ipid; + png_c->seqno =icmpv4_hdr->seqno; + + /* appdata and sappdata point to the icmp payload */ + ustack->uip_appdata = ustack->network_layer + uip_ip_icmph_len; + ustack->uip_sappdata = ustack->uip_appdata; + + if (nic_iface->mtu) + mtu = nic_iface->mtu; + + if ((mtu - uip_ip_icmph_len) > png_c->datalen) { + ustack->uip_slen = png_c->datalen; + } else { + png_c->state = ISCSI_PING_OVERSIZE_PACKET; + LOG_ERR(PFX "MTU=%d, payload=%d\n", + mtu, png_c->datalen); + rc = -EINVAL; + goto done; + } + + fill_payload_data(ustack); + + /* Calculate ICMP checksum. */ + icmpv4_hdr->icmpchksum = 0; + icmpv4_hdr->icmpchksum = ~(uip_chksum((u16_t *)icmpv4_hdr, + icmpv4_hdr_len + + ustack->uip_slen)); + if (icmpv4_hdr->icmpchksum == 0) + icmpv4_hdr->icmpchksum = 0xffff; + + /* IPv4 total length = IPv4 HLEN + ICMP HLEN + Payload len */ + ustack->uip_len = uip_ip_icmph_len + ustack->uip_slen; + ipv4_hdr->len[0] = (ustack->uip_len >> 8); + ipv4_hdr->len[1] = (ustack->uip_len & 0xff); + + /* Calculate IP checksum. */ + ipv4_hdr->ipchksum = 0; + ipv4_hdr->ipchksum = ~(uip_ipchksum(ustack)); + if (ipv4_hdr->ipchksum == 0) + ipv4_hdr->ipchksum = 0xffff; + + ++ustack->stats.ip.sent; + /* Return and let the caller do the actual transmission. */ + ustack->uip_flags = 0; + +done: + return rc; +} + +static void prepare_icmpv6_req_pkt(struct ping_conf *png_c, struct packet *pkt, + uip_ip6addr_t *dst_addr, + uip_ip6addr_t *src_addr) +{ + nic_interface_t *nic_iface = png_c->nic_iface; + struct uip_stack *ustack = &nic_iface->ustack; + struct uip_ipv6_hdr *ipv6_hdr = NULL; + uip_icmp_echo_hdr_t *icmp_echo_hdr = NULL; + u16_t uip_iph_len = 0; + u16_t icmp_echo_hdr_len = 0; + u16_t uip_ip_icmph_len = 0; + char ipbuf[INET6_ADDRSTRLEN] = {0}; + + uip_iph_len = UIP_IPv6_H_LEN; + icmp_echo_hdr_len = sizeof(*icmp_echo_hdr); + uip_ip_icmph_len = uip_iph_len + icmp_echo_hdr_len; + + ipv6_hdr = (struct uip_ipv6_hdr *)ustack->network_layer; + + icmp_echo_hdr = (uip_icmp_echo_hdr_t *) (ustack->network_layer + + sizeof(struct uip_ipv6_hdr)); + + /* fill IPv6 header */ + ipv6_hdr->vtc = 0x60; + ipv6_hdr->tcflow = 0; + ipv6_hdr->flow = 0; + ipv6_hdr->proto = UIP_PROTO_ICMP6; + ipv6_hdr->ttl = UIP_TTL; + uip_ip6addr_copy(ipv6_hdr->srcipaddr, src_addr); + uip_ip6addr_copy(ipv6_hdr->destipaddr, dst_addr); + + memset(ipbuf, 0, sizeof(ipbuf)); + if (inet_ntop(AF_INET6, &ipv6_hdr->srcipaddr, ipbuf, INET6_ADDRSTRLEN)) + LOG_INFO(PFX "src ipaddr=%s", ipbuf); + + memset(ipbuf, 0, sizeof(ipbuf)); + if (inet_ntop(AF_INET6, &ipv6_hdr->destipaddr, ipbuf, INET6_ADDRSTRLEN)) + LOG_INFO(PFX "dest ipaddr=%s", ipbuf); + + /* fill ICMP header */ + icmp_echo_hdr->type = ICMPV6_ECHO_REQ; + icmp_echo_hdr->icode = 0; + icmp_echo_hdr->id = HOST_TO_NET16(getpid() & 0xffff); + png_c->id = icmp_echo_hdr->id; + ++ustack->ipid; + icmp_echo_hdr->seqno = HOST_TO_NET16(ustack->ipid); + png_c->seqno = icmp_echo_hdr->seqno; + + /* appdata and sappdata point to the icmp payload */ + ustack->uip_appdata = ustack->network_layer + uip_ip_icmph_len; + ustack->uip_sappdata = ustack->uip_appdata; + ustack->uip_slen = png_c->datalen; + + fill_payload_data(ustack); + + /* Total length = ETH HLEN + IPv6 HLEN + ICMP HLEN + Data len */ + ustack->uip_len = UIP_LLH_LEN + uip_ip_icmph_len + ustack->uip_slen; + /* IPv6 payload len */ + ipv6_hdr->len = HOST_TO_NET16(icmp_echo_hdr_len + ustack->uip_slen); + + /* Calculate ICMP checksum. */ + icmp_echo_hdr->icmpchksum = 0; + icmp_echo_hdr->icmpchksum = ~(uip_icmp6chksum(ustack)); + + ++ustack->stats.ip.sent; + /* Return and let the caller do the actual transmission. */ + ustack->uip_flags = 0; + return; +} + +static int chk_arp_entry_for_dst_addr(nic_t *nic, nic_interface_t *nic_iface, + void *addr) +{ + struct iscsi_path path; + uip_ip4addr_t dst_addr4; + uip_ip6addr_t dst_addr6; + + if (nic_iface->protocol == AF_INET) { + memcpy(dst_addr4, addr, sizeof(uip_ip4addr_t)); + memcpy(&path.dst.v4_addr, dst_addr4, sizeof(struct in_addr)); + path.ip_addr_len = 4; + } else { + memcpy(dst_addr6, addr, sizeof(uip_ip6addr_t)); + memcpy(&path.dst.v6_addr, dst_addr6, sizeof(struct in6_addr)); + path.ip_addr_len = 16; + } + + return cnic_handle_iscsi_path_req(nic, 0, NULL, &path, nic_iface); +} + +static int fill_icmpv6_eth_hdr(struct uip_stack *ustack, + uip_ip6addr_t *dst_addr6) +{ + struct uip_eth_hdr *eth; + __u8 mac_addr[6]; + struct ndpc_reqptr req_ptr; + int rc = 0; + int ret = 0; + + eth = (struct uip_eth_hdr *)ustack->data_link_layer; + memcpy(eth->src.addr, ustack->uip_ethaddr.addr, sizeof(eth->src.addr)); + + memset(mac_addr, 0, sizeof(mac_addr)); + req_ptr.eth = (void *)mac_addr; + req_ptr.ipv6 = (void *)dst_addr6; + + ret = ndpc_request(ustack, &req_ptr, &rc, CHECK_ARP_TABLE); + if (ret) { + LOG_DEBUG(PFX "ndpc request failed"); + rc = ret; + } else if (rc) { + memcpy(eth->dest.addr, mac_addr, sizeof(eth->dest.addr)); + LOG_DEBUG(PFX "ipv6 arp entry present"); + rc = 0; + } else { + LOG_DEBUG(PFX "ipv6 arp entry not present"); + rc = -EAGAIN; + } + + return rc; +} + +static int determine_src_ipv6_addr(nic_interface_t *nic_iface, + uip_ip6addr_t *dst_addr6, + uip_ip6addr_t *src_addr6) +{ + struct in6_addr *addr; + int rc = 0; + int ret = 0; + + if (nic_iface->ustack.ip_config == IPV6_CONFIG_STATIC) { + memcpy(src_addr6, &nic_iface->ustack.hostaddr6, + sizeof(uip_ip6addr_t)); + goto done; + } + + ret = ndpc_request(&nic_iface->ustack, dst_addr6, + &rc, CHECK_LINK_LOCAL_ADDR); + if (ret) { + LOG_DEBUG(PFX "Check LL failed"); + rc = ret; + goto done; + } + + if (rc) { + LOG_DEBUG(PFX "Use LL"); + /* Get link local IPv6 address */ + addr = (struct in6_addr *)&nic_iface->ustack.linklocal6; + rc = 0; + } else { + LOG_DEBUG(PFX "Use Best matched"); + ret = ndpc_request(&nic_iface->ustack, + dst_addr6, + &addr, GET_HOST_ADDR); + if (ret) { + LOG_DEBUG(PFX "Use Best matched failed"); + rc = ret; + goto done; + } + if (addr == NULL) { + LOG_DEBUG(PFX "No Best matched found"); + rc = -EINVAL; + goto done; + } + } + + /* Got the best matched src IP address */ + memcpy(src_addr6, addr, sizeof(struct in6_addr)); + +done: + return rc; +} + +void ping_init(struct ping_conf *png_c, void *addr, u16_t type, int datalen) +{ + png_c->dst_addr = addr; + png_c->proto = type; + png_c->state = PING_INIT_STATE; + png_c->datalen = datalen; + return; +} + +int do_ping_from_nic_iface(struct ping_conf *png_c) +{ + packet_t *pkt; + nic_interface_t *nic_iface = png_c->nic_iface; + nic_t *nic = nic_iface->parent; + struct uip_stack *ustack = &nic_iface->ustack; + uip_ip4addr_t dst_addr4; + uip_ip6addr_t dst_addr6; + uip_ip6addr_t src_addr6; + struct timer ping_timer; + int rc = 0; + + memset(dst_addr4, 0, sizeof(uip_ip4addr_t)); + memset(dst_addr6, 0, sizeof(uip_ip6addr_t)); + memset(src_addr6, 0, sizeof(uip_ip6addr_t)); + + if (nic_iface->protocol == AF_INET) + memcpy(dst_addr4, png_c->dst_addr, sizeof(uip_ip4addr_t)); + else + memcpy(dst_addr6, png_c->dst_addr, sizeof(uip_ip6addr_t)); + + rc = chk_arp_entry_for_dst_addr(nic, nic_iface, png_c->dst_addr); + + if (rc && (nic_iface->protocol == AF_INET)) { + png_c->state = ISCSI_PING_NO_ARP_RECEIVED; + LOG_ERR(PFX "ARP failure for IPv4 dest addr"); + goto done; + } else if ((rc < 1) && (nic_iface->protocol == AF_INET6)) { + png_c->state = ISCSI_PING_NO_ARP_RECEIVED; + LOG_ERR(PFX "ARP failure for IPv6 dest addr"); + goto done; + } else if (rc < 0) { + LOG_ERR(PFX "ARP failure"); + goto done; + } + + pthread_mutex_lock(&nic->nic_mutex); + pkt = get_next_free_packet(nic); + if (pkt == NULL) { + pthread_mutex_unlock(&nic->nic_mutex); + LOG_ERR(PFX "Unable to get a free packet buffer"); + rc = -EIO; + goto done; + } + + prepare_ustack(nic, nic_iface, ustack, pkt); + + if (nic_iface->protocol == AF_INET) { + rc = prepare_icmpv4_req_pkt(png_c, pkt, &dst_addr4); + if (rc) + goto put_pkt; + + /* If the above function invocation resulted + * in data that should be sent out on the + * network, the global variable uip_len is + * set to a value > 0. */ + if (ustack->uip_len > 0) { + pkt->buf_size = ustack->uip_len; + + prepare_ipv4_packet(nic, nic_iface, ustack, pkt); + + LOG_DEBUG(PFX "Send ICMP echo request"); + (*nic->ops->write) (nic, nic_iface, pkt); + ustack->uip_len = 0; + } + } else { + rc = determine_src_ipv6_addr(nic_iface, &dst_addr6, &src_addr6); + if (rc) + goto put_pkt; + + prepare_icmpv6_req_pkt(png_c, pkt, &dst_addr6, &src_addr6); + + /* If the above function invocation resulted + * in data that should be sent out on the + * network, the global variable uip_len is + * set to a value > 0. */ + if (ustack->uip_len > 0) { + pkt->buf_size = ustack->uip_len; + + prepare_ipv6_packet(nic, nic_iface, ustack, pkt); + rc = fill_icmpv6_eth_hdr(ustack, &dst_addr6); + if (rc) { + ustack->uip_len = 0; + goto put_pkt; + } + + LOG_DEBUG(PFX "Send ICMPv6 echo request"); + (*nic->ops->write) (nic, nic_iface, pkt); + ustack->uip_len = 0; + } + } + +put_pkt: + put_packet_in_free_queue(pkt, nic); + pthread_mutex_unlock(&nic->nic_mutex); + + if (rc) { + LOG_DEBUG(PFX "Ping request not transmitted"); + goto done; + } + + timer_set(&ping_timer, CLOCK_SECOND * 10); + + while ((event_loop_stop == 0) && + (nic->flags & NIC_ENABLED) && !(nic->flags & NIC_GOING_DOWN)) { + + rc = nic_process_intr(nic, 1); + + while ((rc > 0) && (!(nic->flags & NIC_GOING_DOWN))) { + rc = process_packets(nic, NULL, NULL, nic_iface); + } + + if (!rc && (png_c->state == ISCSI_PING_SUCCESS)) { + LOG_INFO(PFX "PING successful!"); + break; + } + + if (timer_expired(&ping_timer)) { + png_c->state = ISCSI_PING_TIMEOUT; + LOG_ERR(PFX "PING timeout"); + rc = -EIO; + break; + } + } + +done: + return rc; +} + +int process_icmp_packet(uip_icmp_echo_hdr_t *icmp_hdr, + struct uip_stack *ustack) +{ + struct ping_conf *png_c = (struct ping_conf *)ustack->ping_conf; + int rc = 0; + + LOG_INFO(PFX "Verify ICMP echo reply"); + + if ((icmp_hdr->type == ICMPV6_ECHO_REPLY && + png_c->proto == AF_INET6) || + (icmp_hdr->type == ICMP_ECHO_REPLY && + png_c->proto == AF_INET)) { + + if ((icmp_hdr->icode == 0) && + (icmp_hdr->id == png_c->id) && + (icmp_hdr->seqno == png_c->seqno)) { + png_c->state = ISCSI_PING_SUCCESS; + } else { + rc = 1; + } + } else { + rc = 1; + } + + if (rc) { + LOG_INFO(PFX "ICMP echo reply verification failed!"); + } else { + LOG_INFO(PFX "ICMP echo reply OK"); + } + + return rc; +} diff --git a/iscsiuio/src/unix/ping.h b/iscsiuio/src/unix/ping.h new file mode 100644 index 0000000..82ace6f --- /dev/null +++ b/iscsiuio/src/unix/ping.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2015, QLogic Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Dunkels. + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ping.h - PING header file + * + */ + +#ifndef __PING_H__ +#define __PING_H__ + +#include "nic_nl.h" +#include "uip.h" + +#define ICMP_ECHO_REPLY 0 +#define ICMP_ECHO 8 + +#define ICMPV6_ECHO_REQ 128 +#define ICMPV6_ECHO_REPLY 129 + +#define DEF_ICMP_PAYLOAD 32 +#define DEF_ICMPV6_PAYLOAD 16 + +#define PING_INIT_STATE (-1) + +struct ping_conf +{ + nic_t *nic; + nic_interface_t *nic_iface; + void *data; + int state; + void *dst_addr; + u16_t proto; + u16_t id; + u16_t seqno; + u16_t datalen; +}; + +void ping_init(struct ping_conf *png_c, void *addr, u16_t type, int datalen); + +int do_ping_from_nic_iface(struct ping_conf *png_c); + +int process_icmp_packet(uip_icmp_echo_hdr_t *icmp_hdr, + struct uip_stack *ustack); + +#endif /* __PING_H__ */ diff --git a/iscsiuio/src/unix/uip-conf.h b/iscsiuio/src/unix/uip-conf.h new file mode 100644 index 0000000..e6e11a5 --- /dev/null +++ b/iscsiuio/src/unix/uip-conf.h @@ -0,0 +1,160 @@ +/** + * \addtogroup uipopt + * @{ + */ + +/** + * \name Project-specific configuration options + * @{ + * + * uIP has a number of configuration options that can be overridden + * for each project. These are kept in a project-specific uip-conf.h + * file and all configuration names have the prefix UIP_CONF. + */ + +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the uIP TCP/IP stack + * + */ + +/** + * \file + * An example uIP configuration file + * \author + * Adam Dunkels + */ + +#ifndef __UIP_CONF_H__ +#define __UIP_CONF_H__ + +#include + +/** + * 8 bit datatype + * + * This typedef defines the 8-bit type used throughout uIP. + * + * \hideinitializer + */ +typedef uint8_t u8_t; + +/** + * 16 bit datatype + * + * This typedef defines the 16-bit type used throughout uIP. + * + * \hideinitializer + */ +typedef uint16_t u16_t; + +/** + * 32 bit datatype + * + * This typedef defines the 16-bit type used throughout uIP. + * + * \hideinitializer + */ +typedef uint32_t u32_t; + +/** + * Statistics datatype + * + * This typedef defines the dataype used for keeping statistics in + * uIP. + * + * \hideinitializer + */ +typedef uint64_t uip_stats_t; + +/** + * Maximum number of TCP connections. + * + * \hideinitializer + */ +#define UIP_CONF_MAX_CONNECTIONS 40 + +/** + * Maximum number of listening TCP ports. + * + * \hideinitializer + */ +#define UIP_CONF_MAX_LISTENPORTS 40 + +/** + * uIP buffer size. + * + * \hideinitializer + */ +#define UIP_CONF_BUFFER_SIZE 420 + +/** + * CPU byte order. + * + * \hideinitializer + */ +#define UIP_CONF_BYTE_ORDER LITTLE_ENDIAN + +/** + * Logging on or off + * + * \hideinitializer + */ +#define UIP_CONF_LOGGING 1 + +/** + * UDP support on or off + * + * \hideinitializer + */ +#define UIP_CONF_UDP 1 + +/** + * UDP checksums on or off + * + * \hideinitializer + */ +#define UIP_CONF_UDP_CHECKSUMS 1 + +/** + * uIP statistics on or off + * + * \hideinitializer + */ +#define UIP_CONF_STATISTICS 1 + +#define UIP_CONF_IPV6 0 + +#define INET_ADDRSTRLEN 16 +#define INET6_ADDRSTRLEN 46 + +#endif /* __UIP_CONF_H__ */ + +/** @} */ +/** @} */ diff --git a/libopeniscsiusr/Makefile b/libopeniscsiusr/Makefile new file mode 100644 index 0000000..a045a45 --- /dev/null +++ b/libopeniscsiusr/Makefile @@ -0,0 +1,107 @@ +# Makefile +# +# Copyright (C) 2017 Red Hat, Inc. +# Gris Ge +# + +ifeq ($(TOPDIR),) + TOPDIR = .. +endif + +DESTDIR ?= +prefix ?= /usr +INSTALL ?= install + +ifndef LIB_DIR + ifeq ($(shell test -d /lib64 && echo 1),1) + LIB_DIR=$(prefix)/lib64 + else + LIB_DIR=$(prefix)/lib + endif +endif + +INCLUDE_DIR ?= $(prefix)/include +PKGCONF_DIR ?= $(LIB_DIR)/pkgconfig + +PKG_CONFIG = /usr/bin/pkg-config + +LIBISCSI_USR_DIR=$(TOPDIR)/libopeniscsiusr + +LIBISCSI_USR_VERSION_MAJOR=0 +LIBISCSI_USR_VERSION=0.2.0 +SONAME=$(LIBISCSI_USR_VERSION) +DEVLIB = libopeniscsiusr.so +LIBS = $(DEVLIB).$(SONAME) +LIBS_MAJOR = $(DEVLIB).$(LIBISCSI_USR_VERSION_MAJOR) +PKGFILE = libopeniscsiusr.pc +HEADERS = libopeniscsiusr/libopeniscsiusr.h \ + libopeniscsiusr/libopeniscsiusr_common.h \ + libopeniscsiusr/libopeniscsiusr_session.h \ + libopeniscsiusr/libopeniscsiusr_iface.h +TESTS = tests/test_context tests/test_session tests/test_iface tests/test_node +EXTRA_MAN_FILES = libopeniscsiusr.h.3 + +OBJS = context.o misc.o session.o sysfs.o iface.o idbm.o node.o default.o + +CFLAGS ?= -O2 -g +CFLAGS += -Wall -Werror -Wextra -fvisibility=hidden -fPIC +CFLAGS += $(shell $(PKG_CONFIG) --cflags libkmod) + +LDFLAGS += $(shell $(PKG_CONFIG) --libs libkmod) + +LIBADD = + +all: $(LIBS) $(LIBS_MAJOR) $(TESTS) doc + +$(LIBS): $(OBJS) + @echo CFLAGS= $(CFLAGS) + $(CC) $(CFLAGS) -shared -Wl,-soname=$@ -o $@ $(OBJS) $(LDFLAGS) $(LIBADD) + ln -sf $@ $(DEVLIB) + +$(LIBS_MAJOR): $(LIBS) + ln -sf $(LIBS) $@ + +clean: + $(RM) vgcore* core *.a *.o *.gz *.so *.so.* $(TESTS) + $(RM) -r docs/man + +$(TESTS): $(LIBS) +$(TESTS): CFLAGS += -I$(TOPDIR)/libopeniscsiusr -g +$(TESTS): LDFLAGS += $(LIBADD) -L$(TOPDIR)/libopeniscsiusr -lopeniscsiusr + +check: $(LIBS) $(TESTS) + sudo env LD_LIBRARY_PATH=$(LIBISCSI_USR_DIR) TESTS="$(TESTS)" \ + tests/runtest.sh || exit 1; + +install: $(LIBS) $(LIBS_MAJOR) + $(INSTALL) -d $(DESTDIR)/$(LIB_DIR)/ + $(INSTALL) -d $(DESTDIR)/$(INCLUDE_DIR)/ + $(INSTALL) $(LIBS) $(DESTDIR)$(LIB_DIR)/ + ln -sf $(LIBS) $(DESTDIR)/$(LIB_DIR)/$(DEVLIB) + ln -sf $(LIBS) $(DESTDIR)/$(LIB_DIR)/$(LIBS_MAJOR) + $(INSTALL) $(HEADERS) $(DESTDIR)$(INCLUDE_DIR)/ + $(INSTALL) -m 644 -D $(PKGFILE).in $(DESTDIR)$(PKGCONF_DIR)/$(PKGFILE) + perl -i -pe 's|__VERSION__|$(LIBNVME_VERSION)|g' \ + $(DESTDIR)$(PKGCONF_DIR)/$(PKGFILE) + perl -i -pe 's|__LIB_DIR__|$(LIB_DIR)|g' \ + $(DESTDIR)$(PKGCONF_DIR)/$(PKGFILE) + perl -i -pe 's|__INCLUDE_DIR__|$(INCLUDE_DIR)|g' \ + $(DESTDIR)$(PKGCONF_DIR)/$(PKGFILE) + +doc: docs/man/$(EXTRA_MAN_FILES).gz + +TEMPFILE := $(shell mktemp) + +docs/man/$(EXTRA_MAN_FILES).gz: $(HEADERS) + @for file in $(EXTRA_MAN_FILES); do \ + $(INSTALL) -v -m 644 -D docs/$$file docs/man/$$file; \ + done + cat $(HEADERS) | \ + perl docs/doc-preclean.pl > "$(TEMPFILE)" + perl docs/kernel-doc -man "$(TEMPFILE)" | \ + perl docs/split-man.pl docs/man + -rm -f "$(TEMPFILE)" + @for file in docs/man/*.3; do \ + gzip -f $$file; \ + done + find docs/man -type f -name \*[0-9].gz diff --git a/libopeniscsiusr/TODO b/libopeniscsiusr/TODO new file mode 100644 index 0000000..21f3893 --- /dev/null +++ b/libopeniscsiusr/TODO @@ -0,0 +1,3 @@ +TODO: + * Add more debug message. + * Support of SYSFS_PATH environment. diff --git a/libopeniscsiusr/context.c b/libopeniscsiusr/context.c new file mode 100644 index 0000000..fe92155 --- /dev/null +++ b/libopeniscsiusr/context.c @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ + +#include +#include +#include +#include +#include +#include + +#include "libopeniscsiusr/libopeniscsiusr.h" +#include "misc.h" +#include "context.h" + +_iscsi_getter_func_gen(iscsi_context, log_priority, int); + +_iscsi_getter_func_gen(iscsi_context, userdata, void *); + +struct iscsi_context *iscsi_context_new(void) +{ + struct iscsi_context *ctx = NULL; + + ctx = (struct iscsi_context *) malloc(sizeof(struct iscsi_context)); + + if (ctx == NULL) + return NULL; + + ctx->log_func = _iscsi_log_stderr; + ctx->log_priority = LIBISCSI_LOG_PRIORITY_DEFAULT; + ctx->userdata = NULL; + ctx->db = _idbm_new(); + if (ctx->db == NULL) { + free(ctx); + return NULL; + } + + return ctx; +} + +void iscsi_context_free(struct iscsi_context *ctx) +{ + if (ctx != NULL) + _idbm_free(ctx->db); + free(ctx); +} + +void iscsi_context_log_priority_set(struct iscsi_context *ctx, int priority) +{ + assert(ctx != NULL); + ctx->log_priority = priority; +} + +void iscsi_context_log_func_set + (struct iscsi_context *ctx, + void (*log_func)(struct iscsi_context *ctx, int priority, + const char *file, int line, const char *func_name, + const char *format, va_list args)) +{ + assert(ctx != NULL); + ctx->log_func = log_func; +} + +void iscsi_context_userdata_set(struct iscsi_context *ctx, void *userdata) +{ + assert(ctx != NULL); + ctx->userdata = userdata; +} diff --git a/libopeniscsiusr/context.h b/libopeniscsiusr/context.h new file mode 100644 index 0000000..a7093a8 --- /dev/null +++ b/libopeniscsiusr/context.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017-2018 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ +#ifndef __ISCSI_USR_CONTEXT_H__ +#define __ISCSI_USR_CONTEXT_H__ + +#include "idbm.h" +#include + +struct iscsi_context { + void (*log_func)(struct iscsi_context *ctx, int priority, + const char *file, int line, const char *func_name, + const char *format, va_list args); + int log_priority; + void *userdata; + struct idbm *db; +}; + + +#endif /* End of __ISCSI_USR_CONTEXT_H__ */ diff --git a/libopeniscsiusr/default.c b/libopeniscsiusr/default.c new file mode 100644 index 0000000..d01d892 --- /dev/null +++ b/libopeniscsiusr/default.c @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017-2018 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ + +#include + +#include "libopeniscsiusr/libopeniscsiusr.h" +#include "default.h" +#include "iface.h" +#include "node.h" + +#define CONFIG_DIGEST_NEVER 0 + +static void default_session_op_cfg(struct iscsi_session_op_cfg *op_cfg) +{ + op_cfg->InitialR2T = 0; + op_cfg->ImmediateData = 1; + op_cfg->FirstBurstLength = DEF_INI_FIRST_BURST_LEN; + op_cfg->MaxBurstLength = DEF_INI_MAX_BURST_LEN; + op_cfg->DefaultTime2Wait = ISCSI_DEF_TIME2WAIT; + op_cfg->DefaultTime2Retain = 0; + op_cfg->MaxConnections = 1; + op_cfg->MaxOutstandingR2T = 1; + op_cfg->ERL = 0; + op_cfg->FastAbort = 1; +} + +static void default_conn_op_cfg(struct iscsi_conn_op_cfg *op_cfg) +{ + op_cfg->MaxXmitDataSegmentLength = 0; + op_cfg->MaxRecvDataSegmentLength = DEF_INI_MAX_RECV_SEG_LEN; + op_cfg->HeaderDigest = DIGEST_NEVER; + op_cfg->DataDigest = DIGEST_NEVER; + op_cfg->IFMarker = 0; + op_cfg->OFMarker = 0; +} + +/* + * default is to use tcp through whatever the network layer + * selects for us with the /etc/iscsi/initiatorname.iscsi iname. + */ +static void default_iface(struct iscsi_iface *iface) +{ + snprintf(iface->transport_name, + sizeof(iface->transport_name)/sizeof(char), + DEFAULT_TRANSPORT); + + if (!strlen(iface->name)) + snprintf(iface->name, sizeof(iface->name)/sizeof(char), + DEFAULT_IFACENAME); +} + +void _default_node(struct iscsi_node *node) +{ + node->tpgt = PORTAL_GROUP_TAG_UNKNOWN; + node->disc_type = DISCOVERY_TYPE_STATIC; + node->leading_login = 0; + node->session.cmds_max = CMDS_MAX; + node->session.xmit_thread_priority = XMIT_THREAD_PRIORITY; + node->session.initial_cmdsn = 0; + node->session.queue_depth = QUEUE_DEPTH; + node->session.nr_sessions = 1; + node->session.initial_login_retry_max = DEF_INITIAL_LOGIN_RETRIES_MAX; + node->session.reopen_max = DEF_SESSION_REOPEN_MAX; + node->session.auth.authmethod = 0; + node->session.auth.password_length = 0; + node->session.auth.password_in_length = 0; + node->session.err_tmo.abort_timeout = DEF_ABORT_TIMEO; + node->session.err_tmo.lu_reset_timeout = DEF_LU_RESET_TIMEO; + node->session.err_tmo.tgt_reset_timeout = DEF_TGT_RESET_TIMEO; + node->session.err_tmo.host_reset_timeout = DEF_HOST_RESET_TIMEO; + node->session.tmo.replacement_timeout = DEF_REPLACEMENT_TIMEO; + node->session.se = NULL; + node->session.sid = 0; + node->session.multiple = 0; + node->session.scan = DEF_INITIAL_SCAN; + + default_session_op_cfg(&node->session.op_cfg); + + node->conn.startup = ISCSI_STARTUP_MANUAL; + node->conn.port = ISCSI_DEFAULT_PORT; + node->conn.tcp.window_size = TCP_WINDOW_SIZE; + node->conn.tcp.type_of_service = 0; + node->conn.tmo.login_timeout= DEF_LOGIN_TIMEO; + node->conn.tmo.logout_timeout= DEF_LOGOUT_TIMEO; + node->conn.tmo.auth_timeout = 45; + node->conn.tmo.noop_out_interval = DEF_NOOP_OUT_INTERVAL; + node->conn.tmo.noop_out_timeout = DEF_NOOP_OUT_TIMEO; + + default_conn_op_cfg(&node->conn.op_cfg); + + default_iface(&node->iface); +} diff --git a/libopeniscsiusr/default.h b/libopeniscsiusr/default.h new file mode 100644 index 0000000..ba49bf1 --- /dev/null +++ b/libopeniscsiusr/default.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017-2018 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ + +#ifndef __ISCSI_USR_DEFAULT_H__ +#define __ISCSI_USR_DEFAULT_H__ + +#include "libopeniscsiusr/libopeniscsiusr_common.h" +#include "rfc.h" +#include "idbm.h" + +#define PORTAL_GROUP_TAG_UNKNOWN -1 +/* q depths */ +#define CMDS_MAX 128 +#define QUEUE_DEPTH 32 + +/* system */ +#define XMIT_THREAD_PRIORITY -20 + +/* login retries */ +#define DEF_INITIAL_LOGIN_RETRIES_MAX 4 + +/* autoscan enabled */ +#define DEF_INITIAL_SCAN INIT_SCAN_AUTO + +/* + * Default initiator settings. These may not be the same as + * in the RFC. See libopeniscsiusr/libopeniscsiusr_rfc.h for those. + */ +/* timeouts in seconds */ +#define DEF_LOGIN_TIMEO 30 +#define DEF_LOGOUT_TIMEO 15 +#define DEF_NOOP_OUT_INTERVAL 5 +#define DEF_NOOP_OUT_TIMEO 5 +#define DEF_REPLACEMENT_TIMEO 120 + +#define DEF_ABORT_TIMEO 15 +#define DEF_LU_RESET_TIMEO 30 +#define DEF_TGT_RESET_TIMEO 30 +#define DEF_HOST_RESET_TIMEO 60 + +/* session reopen max retries */ +#define DEF_SESSION_REOPEN_MAX 0 + +/* default window size */ +#define TCP_WINDOW_SIZE (512 * 1024) + +/* data and segment lengths in bytes */ +#define DEF_INI_FIRST_BURST_LEN 262144 +#define DEF_INI_MAX_BURST_LEN 16776192 +#define DEF_INI_MAX_RECV_SEG_LEN 262144 + +#define DEFAULT_TRANSPORT "tcp" +#define DEFAULT_IFACENAME "default" +#define DEFAULT_NETDEV "default" +#define DEFAULT_IPADDRESS "default" +#define DEFAULT_HWADDRESS "default" + +void __DLL_LOCAL _default_node(struct iscsi_node *node); + +#endif /* End of __ISCSI_USR_DEFAULT_H__ */ diff --git a/libopeniscsiusr/docs/doc-preclean.pl b/libopeniscsiusr/docs/doc-preclean.pl new file mode 100644 index 0000000..bdffe95 --- /dev/null +++ b/libopeniscsiusr/docs/doc-preclean.pl @@ -0,0 +1,28 @@ +#!/usr/bin/perl +# Copyright (C) 2016 Red Hat, Inc. +# +# 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 3 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, see . +# +# Author: Gris Ge + +use strict; + +my @REMOVE_KEY_LIST=("__DLL_EXPORT"); + +while (<>) { + for my $key (@REMOVE_KEY_LIST) { + (s/$key//g); + } + print; +} diff --git a/libopeniscsiusr/docs/kernel-doc b/libopeniscsiusr/docs/kernel-doc new file mode 100644 index 0000000..7bd52b8 --- /dev/null +++ b/libopeniscsiusr/docs/kernel-doc @@ -0,0 +1,3228 @@ +#!/usr/bin/env perl + +use warnings; +use strict; + +## Copyright (c) 1998 Michael Zucchi, All Rights Reserved ## +## Copyright (C) 2000, 1 Tim Waugh ## +## Copyright (C) 2001 Simon Huggins ## +## Copyright (C) 2005-2012 Randy Dunlap ## +## Copyright (C) 2012 Dan Luedtke ## +## ## +## #define enhancements by Armin Kuster ## +## Copyright (c) 2000 MontaVista Software, Inc. ## +## ## +## This software falls under the GNU General Public License. ## +## Please read the COPYING file for more information ## + +# 18/01/2001 - Cleanups +# Functions prototyped as foo(void) same as foo() +# Stop eval'ing where we don't need to. +# -- huggie@earth.li + +# 27/06/2001 - Allowed whitespace after initial "/**" and +# allowed comments before function declarations. +# -- Christian Kreibich + +# Still to do: +# - add perldoc documentation +# - Look more closely at some of the scarier bits :) + +# 26/05/2001 - Support for separate source and object trees. +# Return error code. +# Keith Owens + +# 23/09/2001 - Added support for typedefs, structs, enums and unions +# Support for Context section; can be terminated using empty line +# Small fixes (like spaces vs. \s in regex) +# -- Tim Jansen + +# 25/07/2012 - Added support for HTML5 +# -- Dan Luedtke + +sub usage { + my $message = <<"EOF"; +Usage: $0 [OPTION ...] FILE ... + +Read C language source or header FILEs, extract embedded documentation comments, +and print formatted documentation to standard output. + +The documentation comments are identified by "/**" opening comment mark. See +Documentation/kernel-doc-nano-HOWTO.txt for the documentation comment syntax. + +Output format selection (mutually exclusive): + -docbook Output DocBook format. + -html Output HTML format. + -html5 Output HTML5 format. + -list Output symbol list format. This is for use by docproc. + -man Output troff manual page format. This is the default. + -rst Output reStructuredText format. + -text Output plain text format. + +Output selection (mutually exclusive): + -export Only output documentation for symbols that have been + exported using EXPORT_SYMBOL() or EXPORT_SYMBOL_GPL() + in any input FILE or -export-file FILE. + -internal Only output documentation for symbols that have NOT been + exported using EXPORT_SYMBOL() or EXPORT_SYMBOL_GPL() + in any input FILE or -export-file FILE. + -function NAME Only output documentation for the given function(s) + or DOC: section title(s). All other functions and DOC: + sections are ignored. May be specified multiple times. + -nofunction NAME Do NOT output documentation for the given function(s); + only output documentation for the other functions and + DOC: sections. May be specified multiple times. + +Output selection modifiers: + -no-doc-sections Do not output DOC: sections. + -enable-lineno Enable output of #define LINENO lines. Only works with + reStructuredText format. + -export-file FILE Specify an additional FILE in which to look for + EXPORT_SYMBOL() and EXPORT_SYMBOL_GPL(). To be used with + -export or -internal. May be specified multiple times. + +Other parameters: + -v Verbose output, more warnings and other information. + -h Print this help. + +EOF + print $message; + exit 1; +} + +# +# format of comments. +# In the following table, (...)? signifies optional structure. +# (...)* signifies 0 or more structure elements +# /** +# * function_name(:)? (- short description)? +# (* @parameterx: (description of parameter x)?)* +# (* a blank line)? +# * (Description:)? (Description of function)? +# * (section header: (section description)? )* +# (*)?*/ +# +# So .. the trivial example would be: +# +# /** +# * my_function +# */ +# +# If the Description: header tag is omitted, then there must be a blank line +# after the last parameter specification. +# e.g. +# /** +# * my_function - does my stuff +# * @my_arg: its mine damnit +# * +# * Does my stuff explained. +# */ +# +# or, could also use: +# /** +# * my_function - does my stuff +# * @my_arg: its mine damnit +# * Description: Does my stuff explained. +# */ +# etc. +# +# Besides functions you can also write documentation for structs, unions, +# enums and typedefs. Instead of the function name you must write the name +# of the declaration; the struct/union/enum/typedef must always precede +# the name. Nesting of declarations is not supported. +# Use the argument mechanism to document members or constants. +# e.g. +# /** +# * struct my_struct - short description +# * @a: first member +# * @b: second member +# * +# * Longer description +# */ +# struct my_struct { +# int a; +# int b; +# /* private: */ +# int c; +# }; +# +# All descriptions can be multiline, except the short function description. +# +# For really longs structs, you can also describe arguments inside the +# body of the struct. +# eg. +# /** +# * struct my_struct - short description +# * @a: first member +# * @b: second member +# * +# * Longer description +# */ +# struct my_struct { +# int a; +# int b; +# /** +# * @c: This is longer description of C +# * +# * You can use paragraphs to describe arguments +# * using this method. +# */ +# int c; +# }; +# +# This should be use only for struct/enum members. +# +# You can also add additional sections. When documenting kernel functions you +# should document the "Context:" of the function, e.g. whether the functions +# can be called form interrupts. Unlike other sections you can end it with an +# empty line. +# A non-void function should have a "Return:" section describing the return +# value(s). +# Example-sections should contain the string EXAMPLE so that they are marked +# appropriately in DocBook. +# +# Example: +# /** +# * user_function - function that can only be called in user context +# * @a: some argument +# * Context: !in_interrupt() +# * +# * Some description +# * Example: +# * user_function(22); +# */ +# ... +# +# +# All descriptive text is further processed, scanning for the following special +# patterns, which are highlighted appropriately. +# +# 'funcname()' - function +# '$ENVVAR' - environmental variable +# '&struct_name' - name of a structure (up to two words including 'struct') +# '&struct_name.member' - name of a structure member +# '@parameter' - name of a parameter +# '%CONST' - name of a constant. +# '``LITERAL``' - literal string without any spaces on it. + +## init lots of data + +my $errors = 0; +my $warnings = 0; +my $anon_struct_union = 0; + +# match expressions used to find embedded type information +my $type_constant = '\b``([^\`]+)``\b'; +my $type_constant2 = '\%([-_\w]+)'; +my $type_func = '(\w+)\(\)'; +my $type_param = '\@(\w+(\.\.\.)?)'; +my $type_fp_param = '\@(\w+)\(\)'; # Special RST handling for func ptr params +my $type_env = '(\$\w+)'; +my $type_enum = '\&(enum\s*([_\w]+))'; +my $type_struct = '\&(struct\s*([_\w]+))'; +my $type_typedef = '\&(typedef\s*([_\w]+))'; +my $type_union = '\&(union\s*([_\w]+))'; +my $type_member = '\&([_\w]+)(\.|->)([_\w]+)'; +my $type_fallback = '\&([_\w]+)'; +my $type_enum_xml = '\&(enum\s*([_\w]+))'; +my $type_struct_xml = '\&(struct\s*([_\w]+))'; +my $type_typedef_xml = '\&(typedef\s*([_\w]+))'; +my $type_union_xml = '\&(union\s*([_\w]+))'; +my $type_member_xml = '\&([_\w]+)(\.|-\>)([_\w]+)'; +my $type_fallback_xml = '\&([_\w]+)'; +my $type_member_func = $type_member . '\(\)'; + +# Output conversion substitutions. +# One for each output format + +# these work fairly well +my @highlights_html = ( + [$type_constant, "\$1"], + [$type_constant2, "\$1"], + [$type_func, "\$1"], + [$type_enum_xml, "\$1"], + [$type_struct_xml, "\$1"], + [$type_typedef_xml, "\$1"], + [$type_union_xml, "\$1"], + [$type_env, "\$1"], + [$type_param, "\$1"], + [$type_member_xml, "\$1\$2\$3"], + [$type_fallback_xml, "\$1"] + ); +my $local_lt = "\\\\\\\\lt:"; +my $local_gt = "\\\\\\\\gt:"; +my $blankline_html = $local_lt . "p" . $local_gt; # was "

" + +# html version 5 +my @highlights_html5 = ( + [$type_constant, "\$1"], + [$type_constant2, "\$1"], + [$type_func, "\$1"], + [$type_enum_xml, "\$1"], + [$type_struct_xml, "\$1"], + [$type_typedef_xml, "\$1"], + [$type_union_xml, "\$1"], + [$type_env, "\$1"], + [$type_param, "\$1]"], + [$type_member_xml, "\$1\$2\$3"], + [$type_fallback_xml, "\$1"] + ); +my $blankline_html5 = $local_lt . "br /" . $local_gt; + +# XML, docbook format +my @highlights_xml = ( + ["([^=])\\\"([^\\\"<]+)\\\"", "\$1\$2"], + [$type_constant, "\$1"], + [$type_constant2, "\$1"], + [$type_enum_xml, "\$1"], + [$type_struct_xml, "\$1"], + [$type_typedef_xml, "\$1"], + [$type_union_xml, "\$1"], + [$type_param, "\$1"], + [$type_func, "\$1"], + [$type_env, "\$1"], + [$type_member_xml, "\$1\$2\$3"], + [$type_fallback_xml, "\$1"] + ); +my $blankline_xml = $local_lt . "/para" . $local_gt . $local_lt . "para" . $local_gt . "\n"; + +# gnome, docbook format +my @highlights_gnome = ( + [$type_constant, "\$1"], + [$type_constant2, "\$1"], + [$type_func, "\$1"], + [$type_enum, "\$1"], + [$type_struct, "\$1"], + [$type_typedef, "\$1"], + [$type_union, "\$1"], + [$type_env, "\$1"], + [$type_param, "\$1" ], + [$type_member, "\$1\$2\$3"], + [$type_fallback, "\$1"] + ); +my $blankline_gnome = "\n"; + +# these are pretty rough +my @highlights_man = ( + [$type_constant, "\$1"], + [$type_constant2, "\$1"], + [$type_func, "\\\\fB\$1\\\\fP"], + [$type_enum, "\\\\fI\$1\\\\fP"], + [$type_struct, "\\\\fI\$1\\\\fP"], + [$type_typedef, "\\\\fI\$1\\\\fP"], + [$type_union, "\\\\fI\$1\\\\fP"], + [$type_param, "\\\\fI\$1\\\\fP"], + [$type_member, "\\\\fI\$1\$2\$3\\\\fP"], + [$type_fallback, "\\\\fI\$1\\\\fP"] + ); +my $blankline_man = ""; + +# text-mode +my @highlights_text = ( + [$type_constant, "\$1"], + [$type_constant2, "\$1"], + [$type_func, "\$1"], + [$type_enum, "\$1"], + [$type_struct, "\$1"], + [$type_typedef, "\$1"], + [$type_union, "\$1"], + [$type_param, "\$1"], + [$type_member, "\$1\$2\$3"], + [$type_fallback, "\$1"] + ); +my $blankline_text = ""; + +# rst-mode +my @highlights_rst = ( + [$type_constant, "``\$1``"], + [$type_constant2, "``\$1``"], + # Note: need to escape () to avoid func matching later + [$type_member_func, "\\:c\\:type\\:`\$1\$2\$3\\\\(\\\\) <\$1>`"], + [$type_member, "\\:c\\:type\\:`\$1\$2\$3 <\$1>`"], + [$type_fp_param, "**\$1\\\\(\\\\)**"], + [$type_func, "\\:c\\:func\\:`\$1()`"], + [$type_enum, "\\:c\\:type\\:`\$1 <\$2>`"], + [$type_struct, "\\:c\\:type\\:`\$1 <\$2>`"], + [$type_typedef, "\\:c\\:type\\:`\$1 <\$2>`"], + [$type_union, "\\:c\\:type\\:`\$1 <\$2>`"], + # in rst this can refer to any type + [$type_fallback, "\\:c\\:type\\:`\$1`"], + [$type_param, "**\$1**"] + ); +my $blankline_rst = "\n"; + +# list mode +my @highlights_list = ( + [$type_constant, "\$1"], + [$type_constant2, "\$1"], + [$type_func, "\$1"], + [$type_enum, "\$1"], + [$type_struct, "\$1"], + [$type_typedef, "\$1"], + [$type_union, "\$1"], + [$type_param, "\$1"], + [$type_member, "\$1"], + [$type_fallback, "\$1"] + ); +my $blankline_list = ""; + +# read arguments +if ($#ARGV == -1) { + usage(); +} + +my $kernelversion; +my $dohighlight = ""; + +my $verbose = 0; +my $output_mode = "man"; +my $output_preformatted = 0; +my $no_doc_sections = 0; +my $enable_lineno = 0; +my @highlights = @highlights_man; +my $blankline = $blankline_man; +my $modulename = "Kernel API"; + +use constant { + OUTPUT_ALL => 0, # output all symbols and doc sections + OUTPUT_INCLUDE => 1, # output only specified symbols + OUTPUT_EXCLUDE => 2, # output everything except specified symbols + OUTPUT_EXPORTED => 3, # output exported symbols + OUTPUT_INTERNAL => 4, # output non-exported symbols +}; +my $output_selection = OUTPUT_ALL; +my $show_not_found = 0; + +my @export_file_list; + +my @build_time; +if (defined($ENV{'KBUILD_BUILD_TIMESTAMP'}) && + (my $seconds = `date -d"${ENV{'KBUILD_BUILD_TIMESTAMP'}}" +%s`) ne '') { + @build_time = gmtime($seconds); +} else { + @build_time = localtime; +} + +my $man_date = ('January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', + 'November', 'December')[$build_time[4]] . + " " . ($build_time[5]+1900); + +# Essentially these are globals. +# They probably want to be tidied up, made more localised or something. +# CAVEAT EMPTOR! Some of the others I localised may not want to be, which +# could cause "use of undefined value" or other bugs. +my ($function, %function_table, %parametertypes, $declaration_purpose); +my $declaration_start_line; +my ($type, $declaration_name, $return_type); +my ($newsection, $newcontents, $prototype, $brcount, %source_map); + +if (defined($ENV{'KBUILD_VERBOSE'})) { + $verbose = "$ENV{'KBUILD_VERBOSE'}"; +} + +# Generated docbook code is inserted in a template at a point where +# docbook v3.1 requires a non-zero sequence of RefEntry's; see: +# http://www.oasis-open.org/docbook/documentation/reference/html/refentry.html +# We keep track of number of generated entries and generate a dummy +# if needs be to ensure the expanded template can be postprocessed +# into html. +my $section_counter = 0; + +my $lineprefix=""; + +# Parser states +use constant { + STATE_NORMAL => 0, # normal code + STATE_NAME => 1, # looking for function name + STATE_FIELD => 2, # scanning field start + STATE_PROTO => 3, # scanning prototype + STATE_DOCBLOCK => 4, # documentation block + STATE_INLINE => 5, # gathering documentation outside main block +}; +my $state; +my $in_doc_sect; + +# Inline documentation state +use constant { + STATE_INLINE_NA => 0, # not applicable ($state != STATE_INLINE) + STATE_INLINE_NAME => 1, # looking for member name (@foo:) + STATE_INLINE_TEXT => 2, # looking for member documentation + STATE_INLINE_END => 3, # done + STATE_INLINE_ERROR => 4, # error - Comment without header was found. + # Spit a warning as it's not + # proper kernel-doc and ignore the rest. +}; +my $inline_doc_state; + +#declaration types: can be +# 'function', 'struct', 'union', 'enum', 'typedef' +my $decl_type; + +my $doc_start = '^/\*\*\s*$'; # Allow whitespace at end of comment start. +my $doc_end = '\*/'; +my $doc_com = '\s*\*\s*'; +my $doc_com_body = '\s*\* ?'; +my $doc_decl = $doc_com . '(\w+)'; +# @params and a strictly limited set of supported section names +my $doc_sect = $doc_com . + '\s*(\@[.\w]+|\@\.\.\.|description|context|returns?|notes?|examples?)\s*:(.*)'; +my $doc_content = $doc_com_body . '(.*)'; +my $doc_block = $doc_com . 'DOC:\s*(.*)?'; +my $doc_inline_start = '^\s*/\*\*\s*$'; +my $doc_inline_sect = '\s*\*\s*(@[\w\s]+):(.*)'; +my $doc_inline_end = '^\s*\*/\s*$'; +my $doc_inline_oneline = '^\s*/\*\*\s*(@[\w\s]+):\s*(.*)\s*\*/\s*$'; +my $export_symbol = '^\s*EXPORT_SYMBOL(_GPL)?\s*\(\s*(\w+)\s*\)\s*;'; + +my %parameterdescs; +my %parameterdesc_start_lines; +my @parameterlist; +my %sections; +my @sectionlist; +my %section_start_lines; +my $sectcheck; +my $struct_actual; + +my $contents = ""; +my $new_start_line = 0; + +# the canonical section names. see also $doc_sect above. +my $section_default = "Description"; # default section +my $section_intro = "Introduction"; +my $section = $section_default; +my $section_context = "Context"; +my $section_return = "Return"; + +my $undescribed = "-- undescribed --"; + +reset_state(); + +while ($ARGV[0] =~ m/^-(.*)/) { + my $cmd = shift @ARGV; + if ($cmd eq "-html") { + $output_mode = "html"; + @highlights = @highlights_html; + $blankline = $blankline_html; + } elsif ($cmd eq "-html5") { + $output_mode = "html5"; + @highlights = @highlights_html5; + $blankline = $blankline_html5; + } elsif ($cmd eq "-man") { + $output_mode = "man"; + @highlights = @highlights_man; + $blankline = $blankline_man; + } elsif ($cmd eq "-text") { + $output_mode = "text"; + @highlights = @highlights_text; + $blankline = $blankline_text; + } elsif ($cmd eq "-rst") { + $output_mode = "rst"; + @highlights = @highlights_rst; + $blankline = $blankline_rst; + } elsif ($cmd eq "-docbook") { + $output_mode = "xml"; + @highlights = @highlights_xml; + $blankline = $blankline_xml; + } elsif ($cmd eq "-list") { + $output_mode = "list"; + @highlights = @highlights_list; + $blankline = $blankline_list; + } elsif ($cmd eq "-gnome") { + $output_mode = "gnome"; + @highlights = @highlights_gnome; + $blankline = $blankline_gnome; + } elsif ($cmd eq "-module") { # not needed for XML, inherits from calling document + $modulename = shift @ARGV; + } elsif ($cmd eq "-function") { # to only output specific functions + $output_selection = OUTPUT_INCLUDE; + $function = shift @ARGV; + $function_table{$function} = 1; + } elsif ($cmd eq "-nofunction") { # output all except specific functions + $output_selection = OUTPUT_EXCLUDE; + $function = shift @ARGV; + $function_table{$function} = 1; + } elsif ($cmd eq "-export") { # only exported symbols + $output_selection = OUTPUT_EXPORTED; + %function_table = (); + } elsif ($cmd eq "-internal") { # only non-exported symbols + $output_selection = OUTPUT_INTERNAL; + %function_table = (); + } elsif ($cmd eq "-export-file") { + my $file = shift @ARGV; + push(@export_file_list, $file); + } elsif ($cmd eq "-v") { + $verbose = 1; + } elsif (($cmd eq "-h") || ($cmd eq "--help")) { + usage(); + } elsif ($cmd eq '-no-doc-sections') { + $no_doc_sections = 1; + } elsif ($cmd eq '-enable-lineno') { + $enable_lineno = 1; + } elsif ($cmd eq '-show-not-found') { + $show_not_found = 1; + } +} + +# continue execution near EOF; + +# get kernel version from env +sub get_kernel_version() { + my $version = 'unknown kernel version'; + + if (defined($ENV{'KERNELVERSION'})) { + $version = $ENV{'KERNELVERSION'}; + } + return $version; +} + +# +sub print_lineno { + my $lineno = shift; + if ($enable_lineno && defined($lineno)) { + print "#define LINENO " . $lineno . "\n"; + } +} +## +# dumps section contents to arrays/hashes intended for that purpose. +# +sub dump_section { + my $file = shift; + my $name = shift; + my $contents = join "\n", @_; + + if ($name =~ m/$type_param/) { + $name = $1; + $parameterdescs{$name} = $contents; + $sectcheck = $sectcheck . $name . " "; + $parameterdesc_start_lines{$name} = $new_start_line; + $new_start_line = 0; + } elsif ($name eq "@\.\.\.") { + $name = "..."; + $parameterdescs{$name} = $contents; + $sectcheck = $sectcheck . $name . " "; + $parameterdesc_start_lines{$name} = $new_start_line; + $new_start_line = 0; + } else { + if (defined($sections{$name}) && ($sections{$name} ne "")) { + # Only warn on user specified duplicate section names. + if ($name ne $section_default) { + print STDERR "${file}:$.: warning: duplicate section name '$name'\n"; + ++$warnings; + } + $sections{$name} .= $contents; + } else { + $sections{$name} = $contents; + push @sectionlist, $name; + $section_start_lines{$name} = $new_start_line; + $new_start_line = 0; + } + } +} + +## +# dump DOC: section after checking that it should go out +# +sub dump_doc_section { + my $file = shift; + my $name = shift; + my $contents = join "\n", @_; + + if ($no_doc_sections) { + return; + } + + if (($output_selection == OUTPUT_ALL) || + ($output_selection == OUTPUT_INCLUDE && + defined($function_table{$name})) || + ($output_selection == OUTPUT_EXCLUDE && + !defined($function_table{$name}))) + { + dump_section($file, $name, $contents); + output_blockhead({'sectionlist' => \@sectionlist, + 'sections' => \%sections, + 'module' => $modulename, + 'content-only' => ($output_selection != OUTPUT_ALL), }); + } +} + +## +# output function +# +# parameterdescs, a hash. +# function => "function name" +# parameterlist => @list of parameters +# parameterdescs => %parameter descriptions +# sectionlist => @list of sections +# sections => %section descriptions +# + +sub output_highlight { + my $contents = join "\n",@_; + my $line; + +# DEBUG +# if (!defined $contents) { +# use Carp; +# confess "output_highlight got called with no args?\n"; +# } + + if ($output_mode eq "html" || $output_mode eq "html5" || + $output_mode eq "xml") { + $contents = local_unescape($contents); + # convert data read & converted thru xml_escape() into &xyz; format: + $contents =~ s/\\\\\\/\&/g; + } +# print STDERR "contents b4:$contents\n"; + eval $dohighlight; + die $@ if $@; +# print STDERR "contents af:$contents\n"; + +# strip whitespaces when generating html5 + if ($output_mode eq "html5") { + $contents =~ s/^\s+//; + $contents =~ s/\s+$//; + } + foreach $line (split "\n", $contents) { + if (! $output_preformatted) { + $line =~ s/^\s*//; + } + if ($line eq ""){ + if (! $output_preformatted) { + print $lineprefix, local_unescape($blankline); + } + } else { + $line =~ s/\\\\\\/\&/g; + if ($output_mode eq "man" && substr($line, 0, 1) eq ".") { + print "\\&$line"; + } else { + print $lineprefix, $line; + } + } + print "\n"; + } +} + +# output sections in html +sub output_section_html(%) { + my %args = %{$_[0]}; + my $section; + + foreach $section (@{$args{'sectionlist'}}) { + print "

$section

\n"; + print "
\n"; + output_highlight($args{'sections'}{$section}); + print "
\n"; + } +} + +# output enum in html +sub output_enum_html(%) { + my %args = %{$_[0]}; + my ($parameter); + my $count; + print "

enum " . $args{'enum'} . "

\n"; + + print "enum " . $args{'enum'} . " {
\n"; + $count = 0; + foreach $parameter (@{$args{'parameterlist'}}) { + print " " . $parameter . ""; + if ($count != $#{$args{'parameterlist'}}) { + $count++; + print ",\n"; + } + print "
"; + } + print "};
\n"; + + print "

Constants

\n"; + print "
\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + print "
" . $parameter . "\n"; + print "
"; + output_highlight($args{'parameterdescs'}{$parameter}); + } + print "
\n"; + output_section_html(@_); + print "
\n"; +} + +# output typedef in html +sub output_typedef_html(%) { + my %args = %{$_[0]}; + my ($parameter); + my $count; + print "

typedef " . $args{'typedef'} . "

\n"; + + print "typedef " . $args{'typedef'} . "\n"; + output_section_html(@_); + print "
\n"; +} + +# output struct in html +sub output_struct_html(%) { + my %args = %{$_[0]}; + my ($parameter); + + print "

" . $args{'type'} . " " . $args{'struct'} . " - " . $args{'purpose'} . "

\n"; + print "" . $args{'type'} . " " . $args{'struct'} . " {
\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + if ($parameter =~ /^#/) { + print "$parameter
\n"; + next; + } + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print "    $1$parameter) ($2);
\n"; + } elsif ($type =~ m/^(.*?)\s*(:.*)/) { + # bitfield + print "    $1 $parameter$2;
\n"; + } else { + print "    $type $parameter;
\n"; + } + } + print "};
\n"; + + print "

Members

\n"; + print "
\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + ($parameter =~ /^#/) && next; + + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + print "
" . $parameter . "\n"; + print "
"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + } + print "
\n"; + output_section_html(@_); + print "
\n"; +} + +# output function in html +sub output_function_html(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + + print "

" . $args{'function'} . " - " . $args{'purpose'} . "

\n"; + print "" . $args{'functiontype'} . "\n"; + print "" . $args{'function'} . "\n"; + print "("; + $count = 0; + foreach $parameter (@{$args{'parameterlist'}}) { + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print "$1$parameter) ($2)"; + } else { + print "" . $type . " " . $parameter . ""; + } + if ($count != $#{$args{'parameterlist'}}) { + $count++; + print ",\n"; + } + } + print ")\n"; + + print "

Arguments

\n"; + print "
\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + print "
" . $parameter . "\n"; + print "
"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + } + print "
\n"; + output_section_html(@_); + print "
\n"; +} + +# output DOC: block header in html +sub output_blockhead_html(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + + foreach $section (@{$args{'sectionlist'}}) { + print "

$section

\n"; + print "
    \n"; + output_highlight($args{'sections'}{$section}); + print "
\n"; + } + print "
\n"; +} + +# output sections in html5 +sub output_section_html5(%) { + my %args = %{$_[0]}; + my $section; + + foreach $section (@{$args{'sectionlist'}}) { + print "
\n"; + print "

$section

\n"; + print "

\n"; + output_highlight($args{'sections'}{$section}); + print "

\n"; + print "
\n"; + } +} + +# output enum in html5 +sub output_enum_html5(%) { + my %args = %{$_[0]}; + my ($parameter); + my $count; + my $html5id; + + $html5id = $args{'enum'}; + $html5id =~ s/[^a-zA-Z0-9\-]+/_/g; + print "
"; + print "

enum " . $args{'enum'} . "

\n"; + print "
    \n"; + print "
  1. "; + print "enum "; + print "" . $args{'enum'} . " {"; + print "
  2. \n"; + $count = 0; + foreach $parameter (@{$args{'parameterlist'}}) { + print "
  3. "; + print "" . $parameter . ""; + if ($count != $#{$args{'parameterlist'}}) { + $count++; + print ","; + } + print "
  4. \n"; + } + print "
  5. };
  6. \n"; + print "
\n"; + + print "
\n"; + print "

Constants

\n"; + print "
\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + print "
" . $parameter . "
\n"; + print "
"; + output_highlight($args{'parameterdescs'}{$parameter}); + print "
\n"; + } + print "
\n"; + print "
\n"; + output_section_html5(@_); + print "
\n"; +} + +# output typedef in html5 +sub output_typedef_html5(%) { + my %args = %{$_[0]}; + my ($parameter); + my $count; + my $html5id; + + $html5id = $args{'typedef'}; + $html5id =~ s/[^a-zA-Z0-9\-]+/_/g; + print "
\n"; + print "

typedef " . $args{'typedef'} . "

\n"; + + print "
    \n"; + print "
  1. "; + print "typedef "; + print "" . $args{'typedef'} . ""; + print "
  2. \n"; + print "
\n"; + output_section_html5(@_); + print "
\n"; +} + +# output struct in html5 +sub output_struct_html5(%) { + my %args = %{$_[0]}; + my ($parameter); + my $html5id; + + $html5id = $args{'struct'}; + $html5id =~ s/[^a-zA-Z0-9\-]+/_/g; + print "
\n"; + print "
\n"; + print "

" . $args{'type'} . " " . $args{'struct'} . "

"; + print "

". $args{'purpose'} . "

\n"; + print "
\n"; + print "
    \n"; + print "
  1. "; + print "" . $args{'type'} . " "; + print "" . $args{'struct'} . " {"; + print "
  2. \n"; + foreach $parameter (@{$args{'parameterlist'}}) { + print "
  3. "; + if ($parameter =~ /^#/) { + print "" . $parameter ."\n"; + print "
  4. \n"; + next; + } + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print "$1 "; + print "$parameter"; + print ") "; + print "($2);"; + } elsif ($type =~ m/^(.*?)\s*(:.*)/) { + # bitfield + print "$1 "; + print "$parameter"; + print "$2;"; + } else { + print "$type "; + print "$parameter;"; + } + print "\n"; + } + print "
  5. };
  6. \n"; + print "
\n"; + + print "
\n"; + print "

Members

\n"; + print "
\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + ($parameter =~ /^#/) && next; + + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + print "
" . $parameter . "
\n"; + print "
"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + print "
\n"; + } + print "
\n"; + print "
\n"; + output_section_html5(@_); + print "
\n"; +} + +# output function in html5 +sub output_function_html5(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + my $html5id; + + $html5id = $args{'function'}; + $html5id =~ s/[^a-zA-Z0-9\-]+/_/g; + print "
\n"; + print "
\n"; + print "

" . $args{'function'} . "

"; + print "

" . $args{'purpose'} . "

\n"; + print "
\n"; + print "
    \n"; + print "
  1. "; + print "" . $args{'functiontype'} . " "; + print "" . $args{'function'} . " ("; + print "
  2. "; + $count = 0; + foreach $parameter (@{$args{'parameterlist'}}) { + print "
  3. "; + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print "$1 "; + print "$parameter"; + print ") "; + print "($2)"; + } else { + print "$type "; + print "$parameter"; + } + if ($count != $#{$args{'parameterlist'}}) { + $count++; + print ","; + } + print "
  4. \n"; + } + print "
  5. )
  6. \n"; + print "
\n"; + + print "
\n"; + print "

Arguments

\n"; + print "

\n"; + print "

\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + print "
" . $parameter . "
\n"; + print "
"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + print "
\n"; + } + print "
\n"; + print "
\n"; + output_section_html5(@_); + print "
\n"; +} + +# output DOC: block header in html5 +sub output_blockhead_html5(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + my $html5id; + + foreach $section (@{$args{'sectionlist'}}) { + $html5id = $section; + $html5id =~ s/[^a-zA-Z0-9\-]+/_/g; + print "
\n"; + print "

$section

\n"; + print "

\n"; + output_highlight($args{'sections'}{$section}); + print "

\n"; + } + print "
\n"; +} + +sub output_section_xml(%) { + my %args = %{$_[0]}; + my $section; + # print out each section + $lineprefix=" "; + foreach $section (@{$args{'sectionlist'}}) { + print "\n"; + print "$section\n"; + if ($section =~ m/EXAMPLE/i) { + print "\n"; + $output_preformatted = 1; + } else { + print "\n"; + } + output_highlight($args{'sections'}{$section}); + $output_preformatted = 0; + if ($section =~ m/EXAMPLE/i) { + print "\n"; + } else { + print "\n"; + } + print "\n"; + } +} + +# output function in XML DocBook +sub output_function_xml(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + my $id; + + $id = "API-" . $args{'function'}; + $id =~ s/[^A-Za-z0-9]/-/g; + + print "\n"; + print "\n"; + print " LINUX\n"; + print " Kernel Hackers Manual\n"; + print " $man_date\n"; + print "\n"; + print "\n"; + print " " . $args{'function'} . "\n"; + print " 9\n"; + print " " . $kernelversion . "\n"; + print "\n"; + print "\n"; + print " " . $args{'function'} . "\n"; + print " \n"; + print " "; + output_highlight ($args{'purpose'}); + print " \n"; + print "\n"; + + print "\n"; + print " Synopsis\n"; + print " \n"; + print " " . $args{'functiontype'} . " "; + print "" . $args{'function'} . " \n"; + + $count = 0; + if ($#{$args{'parameterlist'}} >= 0) { + foreach $parameter (@{$args{'parameterlist'}}) { + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print " $1$parameter)\n"; + print " $2\n"; + } else { + print " " . $type; + print " $parameter\n"; + } + } + } else { + print " \n"; + } + print " \n"; + print "\n"; + + # print parameters + print "\n Arguments\n"; + if ($#{$args{'parameterlist'}} >= 0) { + print " \n"; + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + $type = $args{'parametertypes'}{$parameter}; + + print " \n $type $parameter\n"; + print " \n \n"; + $lineprefix=" "; + output_highlight($args{'parameterdescs'}{$parameter_name}); + print " \n \n \n"; + } + print " \n"; + } else { + print " \n None\n \n"; + } + print "\n"; + + output_section_xml(@_); + print "\n\n"; +} + +# output struct in XML DocBook +sub output_struct_xml(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $id; + + $id = "API-struct-" . $args{'struct'}; + $id =~ s/[^A-Za-z0-9]/-/g; + + print "\n"; + print "\n"; + print " LINUX\n"; + print " Kernel Hackers Manual\n"; + print " $man_date\n"; + print "\n"; + print "\n"; + print " " . $args{'type'} . " " . $args{'struct'} . "\n"; + print " 9\n"; + print " " . $kernelversion . "\n"; + print "\n"; + print "\n"; + print " " . $args{'type'} . " " . $args{'struct'} . "\n"; + print " \n"; + print " "; + output_highlight ($args{'purpose'}); + print " \n"; + print "\n"; + + print "\n"; + print " Synopsis\n"; + print " \n"; + print $args{'type'} . " " . $args{'struct'} . " {\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + if ($parameter =~ /^#/) { + my $prm = $parameter; + # convert data read & converted thru xml_escape() into &xyz; format: + # This allows us to have #define macros interspersed in a struct. + $prm =~ s/\\\\\\/\&/g; + print "$prm\n"; + next; + } + + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + defined($args{'parameterdescs'}{$parameter_name}) || next; + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print " $1 $parameter) ($2);\n"; + } elsif ($type =~ m/^(.*?)\s*(:.*)/) { + # bitfield + print " $1 $parameter$2;\n"; + } else { + print " " . $type . " " . $parameter . ";\n"; + } + } + print "};"; + print " \n"; + print "\n"; + + print " \n"; + print " Members\n"; + + if ($#{$args{'parameterlist'}} >= 0) { + print " \n"; + foreach $parameter (@{$args{'parameterlist'}}) { + ($parameter =~ /^#/) && next; + + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + defined($args{'parameterdescs'}{$parameter_name}) || next; + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + $type = $args{'parametertypes'}{$parameter}; + print " "; + print " $type $parameter\n"; + print " \n"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + print " \n"; + print " \n"; + } + print " \n"; + } else { + print " \n None\n \n"; + } + print " \n"; + + output_section_xml(@_); + + print "\n\n"; +} + +# output enum in XML DocBook +sub output_enum_xml(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + my $id; + + $id = "API-enum-" . $args{'enum'}; + $id =~ s/[^A-Za-z0-9]/-/g; + + print "\n"; + print "\n"; + print " LINUX\n"; + print " Kernel Hackers Manual\n"; + print " $man_date\n"; + print "\n"; + print "\n"; + print " enum " . $args{'enum'} . "\n"; + print " 9\n"; + print " " . $kernelversion . "\n"; + print "\n"; + print "\n"; + print " enum " . $args{'enum'} . "\n"; + print " \n"; + print " "; + output_highlight ($args{'purpose'}); + print " \n"; + print "\n"; + + print "\n"; + print " Synopsis\n"; + print " \n"; + print "enum " . $args{'enum'} . " {\n"; + $count = 0; + foreach $parameter (@{$args{'parameterlist'}}) { + print " $parameter"; + if ($count != $#{$args{'parameterlist'}}) { + $count++; + print ","; + } + print "\n"; + } + print "};"; + print " \n"; + print "\n"; + + print "\n"; + print " Constants\n"; + print " \n"; + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + print " "; + print " $parameter\n"; + print " \n"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + print " \n"; + print " \n"; + } + print " \n"; + print "\n"; + + output_section_xml(@_); + + print "\n\n"; +} + +# output typedef in XML DocBook +sub output_typedef_xml(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $id; + + $id = "API-typedef-" . $args{'typedef'}; + $id =~ s/[^A-Za-z0-9]/-/g; + + print "\n"; + print "\n"; + print " LINUX\n"; + print " Kernel Hackers Manual\n"; + print " $man_date\n"; + print "\n"; + print "\n"; + print " typedef " . $args{'typedef'} . "\n"; + print " 9\n"; + print "\n"; + print "\n"; + print " typedef " . $args{'typedef'} . "\n"; + print " \n"; + print " "; + output_highlight ($args{'purpose'}); + print " \n"; + print "\n"; + + print "\n"; + print " Synopsis\n"; + print " typedef " . $args{'typedef'} . ";\n"; + print "\n"; + + output_section_xml(@_); + + print "\n\n"; +} + +# output in XML DocBook +sub output_blockhead_xml(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + + my $id = $args{'module'}; + $id =~ s/[^A-Za-z0-9]/-/g; + + # print out each section + $lineprefix=" "; + foreach $section (@{$args{'sectionlist'}}) { + if (!$args{'content-only'}) { + print "\n $section\n"; + } + if ($section =~ m/EXAMPLE/i) { + print "\n"; + $output_preformatted = 1; + } else { + print "\n"; + } + output_highlight($args{'sections'}{$section}); + $output_preformatted = 0; + if ($section =~ m/EXAMPLE/i) { + print "\n"; + } else { + print ""; + } + if (!$args{'content-only'}) { + print "\n\n"; + } + } + + print "\n\n"; +} + +# output in XML DocBook +sub output_function_gnome { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + my $id; + + $id = $args{'module'} . "-" . $args{'function'}; + $id =~ s/[^A-Za-z0-9]/-/g; + + print "\n"; + print " " . $args{'function'} . "\n"; + + print " \n"; + print " " . $args{'functiontype'} . " "; + print "" . $args{'function'} . " "; + print "\n"; + + $count = 0; + if ($#{$args{'parameterlist'}} >= 0) { + foreach $parameter (@{$args{'parameterlist'}}) { + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print " $1 $parameter)\n"; + print " $2\n"; + } else { + print " " . $type; + print " $parameter\n"; + } + } + } else { + print " \n"; + } + print " \n"; + if ($#{$args{'parameterlist'}} >= 0) { + print " \n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + print " $parameter\n"; + print " \n"; + $lineprefix=" "; + output_highlight($args{'parameterdescs'}{$parameter_name}); + print " \n"; + } + print " \n"; + } else { + print " \n None\n \n"; + } + + # print out each section + $lineprefix=" "; + foreach $section (@{$args{'sectionlist'}}) { + print "\n $section\n"; + if ($section =~ m/EXAMPLE/i) { + print "\n"; + $output_preformatted = 1; + } else { + } + print "\n"; + output_highlight($args{'sections'}{$section}); + $output_preformatted = 0; + print "\n"; + if ($section =~ m/EXAMPLE/i) { + print "\n"; + } else { + } + print " \n"; + } + + print "\n\n"; +} + +## +# output function in man +sub output_function_man(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + + print ".TH \"$args{'function'}\" 9 \"$args{'function'}\" \"$man_date\" \"Kernel Hacker's Manual\" LINUX\n"; + + print ".SH NAME\n"; + print $args{'function'} . " \\- " . $args{'purpose'} . "\n"; + + print ".SH SYNOPSIS\n"; + if ($args{'functiontype'} ne "") { + print ".B \"" . $args{'functiontype'} . "\" " . $args{'function'} . "\n"; + } else { + print ".B \"" . $args{'function'} . "\n"; + } + $count = 0; + my $parenth = "("; + my $post = ","; + foreach my $parameter (@{$args{'parameterlist'}}) { + if ($count == $#{$args{'parameterlist'}}) { + $post = ");"; + } + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print ".BI \"" . $parenth . $1 . "\" " . $parameter . " \") (" . $2 . ")" . $post . "\"\n"; + } else { + $type =~ s/([^\*])$/$1 /; + print ".BI \"" . $parenth . $type . "\" " . $parameter . " \"" . $post . "\"\n"; + } + $count++; + $parenth = ""; + } + + print ".SH ARGUMENTS\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + print ".IP \"" . $parameter . "\" 12\n"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + } + foreach $section (@{$args{'sectionlist'}}) { + print ".SH \"", uc $section, "\"\n"; + output_highlight($args{'sections'}{$section}); + } +} + +## +# output enum in man +sub output_enum_man(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + + print ".TH \"$args{'module'}\" 9 \"enum $args{'enum'}\" \"$man_date\" \"API Manual\" LINUX\n"; + + print ".SH NAME\n"; + print "enum " . $args{'enum'} . " \\- " . $args{'purpose'} . "\n"; + + print ".SH SYNOPSIS\n"; + print "enum " . $args{'enum'} . " {\n"; + $count = 0; + foreach my $parameter (@{$args{'parameterlist'}}) { + print ".br\n.BI \" $parameter\"\n"; + if ($count == $#{$args{'parameterlist'}}) { + print "\n};\n"; + last; + } + else { + print ", \n.br\n"; + } + $count++; + } + + print ".SH Constants\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + print ".IP \"" . $parameter . "\" 12\n"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + } + foreach $section (@{$args{'sectionlist'}}) { + print ".SH \"$section\"\n"; + output_highlight($args{'sections'}{$section}); + } +} + +## +# output struct in man +sub output_struct_man(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + + print ".TH \"$args{'module'}\" 9 \"" . $args{'type'} . " " . $args{'struct'} . "\" \"$man_date\" \"API Manual\" LINUX\n"; + + print ".SH NAME\n"; + print $args{'type'} . " " . $args{'struct'} . " \\- " . $args{'purpose'} . "\n"; + + print ".SH SYNOPSIS\n"; + print $args{'type'} . " " . $args{'struct'} . " {\n.br\n"; + + foreach my $parameter (@{$args{'parameterlist'}}) { + if ($parameter =~ /^#/) { + print ".BI \"$parameter\"\n.br\n"; + next; + } + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print ".BI \" " . $1 . "\" " . $parameter . " \") (" . $2 . ")" . "\"\n;\n"; + } elsif ($type =~ m/^(.*?)\s*(:.*)/) { + # bitfield + print ".BI \" " . $1 . "\ \" " . $parameter . $2 . " \"" . "\"\n;\n"; + } else { + $type =~ s/([^\*])$/$1 /; + print ".BI \" " . $type . "\" " . $parameter . " \"" . "\"\n;\n"; + } + print "\n.br\n"; + } + print "};\n.br\n"; + + print ".SH Members\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + ($parameter =~ /^#/) && next; + + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + print ".IP \"" . $parameter . "\" 12\n"; + output_highlight($args{'parameterdescs'}{$parameter_name}); + } + foreach $section (@{$args{'sectionlist'}}) { + print ".SH \"$section\"\n"; + output_highlight($args{'sections'}{$section}); + } +} + +## +# output typedef in man +sub output_typedef_man(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + + print ".TH \"$args{'module'}\" 9 \"$args{'typedef'}\" \"$man_date\" \"API Manual\" LINUX\n"; + + print ".SH NAME\n"; + print "typedef " . $args{'typedef'} . " \\- " . $args{'purpose'} . "\n"; + + foreach $section (@{$args{'sectionlist'}}) { + print ".SH \"$section\"\n"; + output_highlight($args{'sections'}{$section}); + } +} + +sub output_blockhead_man(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $count; + + print ".TH \"$args{'module'}\" 9 \"$args{'module'}\" \"$man_date\" \"API Manual\" LINUX\n"; + + foreach $section (@{$args{'sectionlist'}}) { + print ".SH \"$section\"\n"; + output_highlight($args{'sections'}{$section}); + } +} + +## +# output in text +sub output_function_text(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $start; + + print "Name:\n\n"; + print $args{'function'} . " - " . $args{'purpose'} . "\n"; + + print "\nSynopsis:\n\n"; + if ($args{'functiontype'} ne "") { + $start = $args{'functiontype'} . " " . $args{'function'} . " ("; + } else { + $start = $args{'function'} . " ("; + } + print $start; + + my $count = 0; + foreach my $parameter (@{$args{'parameterlist'}}) { + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print $1 . $parameter . ") (" . $2; + } else { + print $type . " " . $parameter; + } + if ($count != $#{$args{'parameterlist'}}) { + $count++; + print ",\n"; + print " " x length($start); + } else { + print ");\n\n"; + } + } + + print "Arguments:\n\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + print $parameter . "\n\t" . $args{'parameterdescs'}{$parameter_name} . "\n"; + } + output_section_text(@_); +} + +#output sections in text +sub output_section_text(%) { + my %args = %{$_[0]}; + my $section; + + print "\n"; + foreach $section (@{$args{'sectionlist'}}) { + print "$section:\n\n"; + output_highlight($args{'sections'}{$section}); + } + print "\n\n"; +} + +# output enum in text +sub output_enum_text(%) { + my %args = %{$_[0]}; + my ($parameter); + my $count; + print "Enum:\n\n"; + + print "enum " . $args{'enum'} . " - " . $args{'purpose'} . "\n\n"; + print "enum " . $args{'enum'} . " {\n"; + $count = 0; + foreach $parameter (@{$args{'parameterlist'}}) { + print "\t$parameter"; + if ($count != $#{$args{'parameterlist'}}) { + $count++; + print ","; + } + print "\n"; + } + print "};\n\n"; + + print "Constants:\n\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + print "$parameter\n\t"; + print $args{'parameterdescs'}{$parameter} . "\n"; + } + + output_section_text(@_); +} + +# output typedef in text +sub output_typedef_text(%) { + my %args = %{$_[0]}; + my ($parameter); + my $count; + print "Typedef:\n\n"; + + print "typedef " . $args{'typedef'} . " - " . $args{'purpose'} . "\n"; + output_section_text(@_); +} + +# output struct as text +sub output_struct_text(%) { + my %args = %{$_[0]}; + my ($parameter); + + print $args{'type'} . " " . $args{'struct'} . " - " . $args{'purpose'} . "\n\n"; + print $args{'type'} . " " . $args{'struct'} . " {\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + if ($parameter =~ /^#/) { + print "$parameter\n"; + next; + } + + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print "\t$1 $parameter) ($2);\n"; + } elsif ($type =~ m/^(.*?)\s*(:.*)/) { + # bitfield + print "\t$1 $parameter$2;\n"; + } else { + print "\t" . $type . " " . $parameter . ";\n"; + } + } + print "};\n\n"; + + print "Members:\n\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + ($parameter =~ /^#/) && next; + + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + print "$parameter\n\t"; + print $args{'parameterdescs'}{$parameter_name} . "\n"; + } + print "\n"; + output_section_text(@_); +} + +sub output_blockhead_text(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + + foreach $section (@{$args{'sectionlist'}}) { + print " $section:\n"; + print " -> "; + output_highlight($args{'sections'}{$section}); + } +} + +## +# output in restructured text +# + +# +# This could use some work; it's used to output the DOC: sections, and +# starts by putting out the name of the doc section itself, but that tends +# to duplicate a header already in the template file. +# +sub output_blockhead_rst(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + + foreach $section (@{$args{'sectionlist'}}) { + if ($output_selection != OUTPUT_INCLUDE) { + print "**$section**\n\n"; + } + print_lineno($section_start_lines{$section}); + output_highlight_rst($args{'sections'}{$section}); + print "\n"; + } +} + +sub output_highlight_rst { + my $contents = join "\n",@_; + my $line; + + # undo the evil effects of xml_escape() earlier + $contents = xml_unescape($contents); + + eval $dohighlight; + die $@ if $@; + + foreach $line (split "\n", $contents) { + print $lineprefix . $line . "\n"; + } +} + +sub output_function_rst(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + my $oldprefix = $lineprefix; + my $start = ""; + + if ($args{'typedef'}) { + print ".. c:type:: ". $args{'function'} . "\n\n"; + print_lineno($declaration_start_line); + print " **Typedef**: "; + $lineprefix = ""; + output_highlight_rst($args{'purpose'}); + $start = "\n\n**Syntax**\n\n ``"; + } else { + print ".. c:function:: "; + } + if ($args{'functiontype'} ne "") { + $start .= $args{'functiontype'} . " " . $args{'function'} . " ("; + } else { + $start .= $args{'function'} . " ("; + } + print $start; + + my $count = 0; + foreach my $parameter (@{$args{'parameterlist'}}) { + if ($count ne 0) { + print ", "; + } + $count++; + $type = $args{'parametertypes'}{$parameter}; + + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print $1 . $parameter . ") (" . $2; + } else { + print $type . " " . $parameter; + } + } + if ($args{'typedef'}) { + print ");``\n\n"; + } else { + print ")\n\n"; + print_lineno($declaration_start_line); + $lineprefix = " "; + output_highlight_rst($args{'purpose'}); + print "\n"; + } + + print "**Parameters**\n\n"; + $lineprefix = " "; + foreach $parameter (@{$args{'parameterlist'}}) { + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + $type = $args{'parametertypes'}{$parameter}; + + if ($type ne "") { + print "``$type $parameter``\n"; + } else { + print "``$parameter``\n"; + } + + print_lineno($parameterdesc_start_lines{$parameter_name}); + + if (defined($args{'parameterdescs'}{$parameter_name}) && + $args{'parameterdescs'}{$parameter_name} ne $undescribed) { + output_highlight_rst($args{'parameterdescs'}{$parameter_name}); + } else { + print " *undescribed*\n"; + } + print "\n"; + } + + $lineprefix = $oldprefix; + output_section_rst(@_); +} + +sub output_section_rst(%) { + my %args = %{$_[0]}; + my $section; + my $oldprefix = $lineprefix; + $lineprefix = ""; + + foreach $section (@{$args{'sectionlist'}}) { + print "**$section**\n\n"; + print_lineno($section_start_lines{$section}); + output_highlight_rst($args{'sections'}{$section}); + print "\n"; + } + print "\n"; + $lineprefix = $oldprefix; +} + +sub output_enum_rst(%) { + my %args = %{$_[0]}; + my ($parameter); + my $oldprefix = $lineprefix; + my $count; + my $name = "enum " . $args{'enum'}; + + print "\n\n.. c:type:: " . $name . "\n\n"; + print_lineno($declaration_start_line); + $lineprefix = " "; + output_highlight_rst($args{'purpose'}); + print "\n"; + + print "**Constants**\n\n"; + $lineprefix = " "; + foreach $parameter (@{$args{'parameterlist'}}) { + print "``$parameter``\n"; + if ($args{'parameterdescs'}{$parameter} ne $undescribed) { + output_highlight_rst($args{'parameterdescs'}{$parameter}); + } else { + print " *undescribed*\n"; + } + print "\n"; + } + + $lineprefix = $oldprefix; + output_section_rst(@_); +} + +sub output_typedef_rst(%) { + my %args = %{$_[0]}; + my ($parameter); + my $oldprefix = $lineprefix; + my $name = "typedef " . $args{'typedef'}; + + print "\n\n.. c:type:: " . $name . "\n\n"; + print_lineno($declaration_start_line); + $lineprefix = " "; + output_highlight_rst($args{'purpose'}); + print "\n"; + + $lineprefix = $oldprefix; + output_section_rst(@_); +} + +sub output_struct_rst(%) { + my %args = %{$_[0]}; + my ($parameter); + my $oldprefix = $lineprefix; + my $name = $args{'type'} . " " . $args{'struct'}; + + print "\n\n.. c:type:: " . $name . "\n\n"; + print_lineno($declaration_start_line); + $lineprefix = " "; + output_highlight_rst($args{'purpose'}); + print "\n"; + + print "**Definition**\n\n"; + print "::\n\n"; + print " " . $args{'type'} . " " . $args{'struct'} . " {\n"; + foreach $parameter (@{$args{'parameterlist'}}) { + if ($parameter =~ /^#/) { + print " " . "$parameter\n"; + next; + } + + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + $type = $args{'parametertypes'}{$parameter}; + if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) { + # pointer-to-function + print " $1 $parameter) ($2);\n"; + } elsif ($type =~ m/^(.*?)\s*(:.*)/) { + # bitfield + print " $1 $parameter$2;\n"; + } else { + print " " . $type . " " . $parameter . ";\n"; + } + } + print " };\n\n"; + + print "**Members**\n\n"; + $lineprefix = " "; + foreach $parameter (@{$args{'parameterlist'}}) { + ($parameter =~ /^#/) && next; + + my $parameter_name = $parameter; + $parameter_name =~ s/\[.*//; + + ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next; + $type = $args{'parametertypes'}{$parameter}; + print_lineno($parameterdesc_start_lines{$parameter_name}); + print "``" . $parameter . "``\n"; + output_highlight_rst($args{'parameterdescs'}{$parameter_name}); + print "\n"; + } + print "\n"; + + $lineprefix = $oldprefix; + output_section_rst(@_); +} + + +## list mode output functions + +sub output_function_list(%) { + my %args = %{$_[0]}; + + print $args{'function'} . "\n"; +} + +# output enum in list +sub output_enum_list(%) { + my %args = %{$_[0]}; + print $args{'enum'} . "\n"; +} + +# output typedef in list +sub output_typedef_list(%) { + my %args = %{$_[0]}; + print $args{'typedef'} . "\n"; +} + +# output struct as list +sub output_struct_list(%) { + my %args = %{$_[0]}; + + print $args{'struct'} . "\n"; +} + +sub output_blockhead_list(%) { + my %args = %{$_[0]}; + my ($parameter, $section); + + foreach $section (@{$args{'sectionlist'}}) { + print "DOC: $section\n"; + } +} + +## +# generic output function for all types (function, struct/union, typedef, enum); +# calls the generated, variable output_ function name based on +# functype and output_mode +sub output_declaration { + no strict 'refs'; + my $name = shift; + my $functype = shift; + my $func = "output_${functype}_$output_mode"; + if (($output_selection == OUTPUT_ALL) || + (($output_selection == OUTPUT_INCLUDE || + $output_selection == OUTPUT_EXPORTED) && + defined($function_table{$name})) || + (($output_selection == OUTPUT_EXCLUDE || + $output_selection == OUTPUT_INTERNAL) && + !($functype eq "function" && defined($function_table{$name})))) + { + &$func(@_); + $section_counter++; + } +} + +## +# generic output function - calls the right one based on current output mode. +sub output_blockhead { + no strict 'refs'; + my $func = "output_blockhead_" . $output_mode; + &$func(@_); + $section_counter++; +} + +## +# takes a declaration (struct, union, enum, typedef) and +# invokes the right handler. NOT called for functions. +sub dump_declaration($$) { + no strict 'refs'; + my ($prototype, $file) = @_; + my $func = "dump_" . $decl_type; + &$func(@_); +} + +sub dump_union($$) { + dump_struct(@_); +} + +sub dump_struct($$) { + my $x = shift; + my $file = shift; + my $nested; + + if ($x =~ /(struct|union)\s+(\w+)\s*{(.*)}/) { + my $decl_type = $1; + $declaration_name = $2; + my $members = $3; + + # ignore embedded structs or unions + $members =~ s/({.*})//g; + $nested = $1; + + # ignore members marked private: + $members =~ s/\/\*\s*private:.*?\/\*\s*public:.*?\*\///gosi; + $members =~ s/\/\*\s*private:.*//gosi; + # strip comments: + $members =~ s/\/\*.*?\*\///gos; + $nested =~ s/\/\*.*?\*\///gos; + # strip attributes + $members =~ s/__attribute__\s*\(\([a-z,_\*\s\(\)]*\)\)//i; + $members =~ s/__aligned\s*\([^;]*\)//gos; + $members =~ s/\s*CRYPTO_MINALIGN_ATTR//gos; + # replace DECLARE_BITMAP + $members =~ s/DECLARE_BITMAP\s*\(([^,)]+), ([^,)]+)\)/unsigned long $1\[BITS_TO_LONGS($2)\]/gos; + # replace DECLARE_HASHTABLE + $members =~ s/DECLARE_HASHTABLE\s*\(([^,)]+), ([^,)]+)\)/unsigned long $1\[1 << (($2) - 1)\]/gos; + + create_parameterlist($members, ';', $file); + check_sections($file, $declaration_name, $decl_type, $sectcheck, $struct_actual, $nested); + + output_declaration($declaration_name, + 'struct', + {'struct' => $declaration_name, + 'module' => $modulename, + 'parameterlist' => \@parameterlist, + 'parameterdescs' => \%parameterdescs, + 'parametertypes' => \%parametertypes, + 'sectionlist' => \@sectionlist, + 'sections' => \%sections, + 'purpose' => $declaration_purpose, + 'type' => $decl_type + }); + } + else { + print STDERR "${file}:$.: error: Cannot parse struct or union!\n"; + ++$errors; + } +} + +sub dump_enum($$) { + my $x = shift; + my $file = shift; + + $x =~ s@/\*.*?\*/@@gos; # strip comments. + # strip #define macros inside enums + $x =~ s@#\s*((define|ifdef)\s+|endif)[^;]*;@@gos; + + if ($x =~ /enum\s+(\w+)\s*{(.*)}/) { + $declaration_name = $1; + my $members = $2; + my %_members; + + $members =~ s/\s+$//; + + foreach my $arg (split ',', $members) { + $arg =~ s/^\s*(\w+).*/$1/; + push @parameterlist, $arg; + if (!$parameterdescs{$arg}) { + $parameterdescs{$arg} = $undescribed; + print STDERR "${file}:$.: warning: Enum value '$arg' ". + "not described in enum '$declaration_name'\n"; + } + $_members{$arg} = 1; + } + + while (my ($k, $v) = each %parameterdescs) { + if (!exists($_members{$k})) { + print STDERR "${file}:$.: warning: Excess enum value " . + "'$k' description in '$declaration_name'\n"; + } + } + + output_declaration($declaration_name, + 'enum', + {'enum' => $declaration_name, + 'module' => $modulename, + 'parameterlist' => \@parameterlist, + 'parameterdescs' => \%parameterdescs, + 'sectionlist' => \@sectionlist, + 'sections' => \%sections, + 'purpose' => $declaration_purpose + }); + } + else { + print STDERR "${file}:$.: error: Cannot parse enum!\n"; + ++$errors; + } +} + +sub dump_typedef($$) { + my $x = shift; + my $file = shift; + + $x =~ s@/\*.*?\*/@@gos; # strip comments. + + # Parse function prototypes + if ($x =~ /typedef\s+(\w+)\s*\(\*\s*(\w\S+)\s*\)\s*\((.*)\);/ || + $x =~ /typedef\s+(\w+)\s*(\w\S+)\s*\s*\((.*)\);/) { + + # Function typedefs + $return_type = $1; + $declaration_name = $2; + my $args = $3; + + create_parameterlist($args, ',', $file); + + output_declaration($declaration_name, + 'function', + {'function' => $declaration_name, + 'typedef' => 1, + 'module' => $modulename, + 'functiontype' => $return_type, + 'parameterlist' => \@parameterlist, + 'parameterdescs' => \%parameterdescs, + 'parametertypes' => \%parametertypes, + 'sectionlist' => \@sectionlist, + 'sections' => \%sections, + 'purpose' => $declaration_purpose + }); + return; + } + + while (($x =~ /\(*.\)\s*;$/) || ($x =~ /\[*.\]\s*;$/)) { + $x =~ s/\(*.\)\s*;$/;/; + $x =~ s/\[*.\]\s*;$/;/; + } + + if ($x =~ /typedef.*\s+(\w+)\s*;/) { + $declaration_name = $1; + + output_declaration($declaration_name, + 'typedef', + {'typedef' => $declaration_name, + 'module' => $modulename, + 'sectionlist' => \@sectionlist, + 'sections' => \%sections, + 'purpose' => $declaration_purpose + }); + } + else { + print STDERR "${file}:$.: error: Cannot parse typedef!\n"; + ++$errors; + } +} + +sub save_struct_actual($) { + my $actual = shift; + + # strip all spaces from the actual param so that it looks like one string item + $actual =~ s/\s*//g; + $struct_actual = $struct_actual . $actual . " "; +} + +sub create_parameterlist($$$) { + my $args = shift; + my $splitter = shift; + my $file = shift; + my $type; + my $param; + + # temporarily replace commas inside function pointer definition + while ($args =~ /(\([^\),]+),/) { + $args =~ s/(\([^\),]+),/$1#/g; + } + + foreach my $arg (split($splitter, $args)) { + # strip comments + $arg =~ s/\/\*.*\*\///; + # strip leading/trailing spaces + $arg =~ s/^\s*//; + $arg =~ s/\s*$//; + $arg =~ s/\s+/ /; + + if ($arg =~ /^#/) { + # Treat preprocessor directive as a typeless variable just to fill + # corresponding data structures "correctly". Catch it later in + # output_* subs. + push_parameter($arg, "", $file); + } elsif ($arg =~ m/\(.+\)\s*\(/) { + # pointer-to-function + $arg =~ tr/#/,/; + $arg =~ m/[^\(]+\(\*?\s*(\w*)\s*\)/; + $param = $1; + $type = $arg; + $type =~ s/([^\(]+\(\*?)\s*$param/$1/; + save_struct_actual($param); + push_parameter($param, $type, $file); + } elsif ($arg) { + $arg =~ s/\s*:\s*/:/g; + $arg =~ s/\s*\[/\[/g; + + my @args = split('\s*,\s*', $arg); + if ($args[0] =~ m/\*/) { + $args[0] =~ s/(\*+)\s*/ $1/; + } + + my @first_arg; + if ($args[0] =~ /^(.*\s+)(.*?\[.*\].*)$/) { + shift @args; + push(@first_arg, split('\s+', $1)); + push(@first_arg, $2); + } else { + @first_arg = split('\s+', shift @args); + } + + unshift(@args, pop @first_arg); + $type = join " ", @first_arg; + + foreach $param (@args) { + if ($param =~ m/^(\*+)\s*(.*)/) { + save_struct_actual($2); + push_parameter($2, "$type $1", $file); + } + elsif ($param =~ m/(.*?):(\d+)/) { + if ($type ne "") { # skip unnamed bit-fields + save_struct_actual($1); + push_parameter($1, "$type:$2", $file) + } + } + else { + save_struct_actual($param); + push_parameter($param, $type, $file); + } + } + } + } +} + +sub push_parameter($$$) { + my $param = shift; + my $type = shift; + my $file = shift; + + if (($anon_struct_union == 1) && ($type eq "") && + ($param eq "}")) { + return; # ignore the ending }; from anon. struct/union + } + + $anon_struct_union = 0; + $param =~ s/[\[\)].*//; + + if ($type eq "" && $param =~ /\.\.\.$/) + { + if (!$param =~ /\w\.\.\.$/) { + # handles unnamed variable parameters + $param = "..."; + } + if (!defined $parameterdescs{$param} || $parameterdescs{$param} eq "") { + $parameterdescs{$param} = "variable arguments"; + } + } + elsif ($type eq "" && ($param eq "" or $param eq "void")) + { + $param="void"; + $parameterdescs{void} = "no arguments"; + } + elsif ($type eq "" && ($param eq "struct" or $param eq "union")) + # handle unnamed (anonymous) union or struct: + { + $type = $param; + $param = "{unnamed_" . $param . "}"; + $parameterdescs{$param} = "anonymous\n"; + $anon_struct_union = 1; + } + + # warn if parameter has no description + # (but ignore ones starting with # as these are not parameters + # but inline preprocessor statements); + # also ignore unnamed structs/unions; + if (!$anon_struct_union) { + if (!defined $parameterdescs{$param} && $param !~ /^#/) { + + $parameterdescs{$param} = $undescribed; + + if (($type eq 'function') || ($type eq 'enum')) { + print STDERR "${file}:$.: warning: Function parameter ". + "or member '$param' not " . + "described in '$declaration_name'\n"; + } + print STDERR "${file}:$.: warning:" . + " No description found for parameter '$param'\n"; + ++$warnings; + } + } + + $param = xml_escape($param); + + # strip spaces from $param so that it is one continuous string + # on @parameterlist; + # this fixes a problem where check_sections() cannot find + # a parameter like "addr[6 + 2]" because it actually appears + # as "addr[6", "+", "2]" on the parameter list; + # but it's better to maintain the param string unchanged for output, + # so just weaken the string compare in check_sections() to ignore + # "[blah" in a parameter string; + ###$param =~ s/\s*//g; + push @parameterlist, $param; + $type =~ s/\s\s+/ /g; + $parametertypes{$param} = $type; +} + +sub check_sections($$$$$$) { + my ($file, $decl_name, $decl_type, $sectcheck, $prmscheck, $nested) = @_; + my @sects = split ' ', $sectcheck; + my @prms = split ' ', $prmscheck; + my $err; + my ($px, $sx); + my $prm_clean; # strip trailing "[array size]" and/or beginning "*" + + foreach $sx (0 .. $#sects) { + $err = 1; + foreach $px (0 .. $#prms) { + $prm_clean = $prms[$px]; + $prm_clean =~ s/\[.*\]//; + $prm_clean =~ s/__attribute__\s*\(\([a-z,_\*\s\(\)]*\)\)//i; + # ignore array size in a parameter string; + # however, the original param string may contain + # spaces, e.g.: addr[6 + 2] + # and this appears in @prms as "addr[6" since the + # parameter list is split at spaces; + # hence just ignore "[..." for the sections check; + $prm_clean =~ s/\[.*//; + + ##$prm_clean =~ s/^\**//; + if ($prm_clean eq $sects[$sx]) { + $err = 0; + last; + } + } + if ($err) { + if ($decl_type eq "function") { + print STDERR "${file}:$.: warning: " . + "Excess function parameter " . + "'$sects[$sx]' " . + "description in '$decl_name'\n"; + ++$warnings; + } else { + if ($nested !~ m/\Q$sects[$sx]\E/) { + print STDERR "${file}:$.: warning: " . + "Excess $decl_type member " . + "'$sects[$sx]' " . + "description in '$decl_name'\n"; + ++$warnings; + } + } + } + } +} + +## +# Checks the section describing the return value of a function. +sub check_return_section { + my $file = shift; + my $declaration_name = shift; + my $return_type = shift; + + # Ignore an empty return type (It's a macro) + # Ignore functions with a "void" return type. (But don't ignore "void *") + if (($return_type eq "") || ($return_type =~ /void\s*\w*\s*$/)) { + return; + } + + if (!defined($sections{$section_return}) || + $sections{$section_return} eq "") { + print STDERR "${file}:$.: warning: " . + "No description found for return value of " . + "'$declaration_name'\n"; + ++$warnings; + } +} + +## +# takes a function prototype and the name of the current file being +# processed and spits out all the details stored in the global +# arrays/hashes. +sub dump_function($$) { + my $prototype = shift; + my $file = shift; + my $noret = 0; + + $prototype =~ s/^static +//; + $prototype =~ s/^extern +//; + $prototype =~ s/^asmlinkage +//; + $prototype =~ s/^inline +//; + $prototype =~ s/^__inline__ +//; + $prototype =~ s/^__inline +//; + $prototype =~ s/^__always_inline +//; + $prototype =~ s/^noinline +//; + $prototype =~ s/__init +//; + $prototype =~ s/__init_or_module +//; + $prototype =~ s/__meminit +//; + $prototype =~ s/__must_check +//; + $prototype =~ s/__weak +//; + my $define = $prototype =~ s/^#\s*define\s+//; #ak added + $prototype =~ s/__attribute__\s*\(\( + (?: + [\w\s]++ # attribute name + (?:\([^)]*+\))? # attribute arguments + \s*+,? # optional comma at the end + )+ + \)\)\s+//x; + + # Yes, this truly is vile. We are looking for: + # 1. Return type (may be nothing if we're looking at a macro) + # 2. Function name + # 3. Function parameters. + # + # All the while we have to watch out for function pointer parameters + # (which IIRC is what the two sections are for), C types (these + # regexps don't even start to express all the possibilities), and + # so on. + # + # If you mess with these regexps, it's a good idea to check that + # the following functions' documentation still comes out right: + # - parport_register_device (function pointer parameters) + # - atomic_set (macro) + # - pci_match_device, __copy_to_user (long return type) + + if ($define && $prototype =~ m/^()([a-zA-Z0-9_~:]+)\s+/) { + # This is an object-like macro, it has no return type and no parameter + # list. + # Function-like macros are not allowed to have spaces between + # declaration_name and opening parenthesis (notice the \s+). + $return_type = $1; + $declaration_name = $2; + $noret = 1; + } elsif ($prototype =~ m/^()([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || + $prototype =~ m/^(\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || + $prototype =~ m/^(\w+\s*\*+)\s*([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || + $prototype =~ m/^(\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || + $prototype =~ m/^(\w+\s+\w+\s*\*+)\s*([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || + $prototype =~ m/^(\w+\s+\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || + $prototype =~ m/^(\w+\s+\w+\s+\w+\s*\*+)\s*([a-zA-Z0-9_~:]+)\s*\(([^\(]*)\)/ || + $prototype =~ m/^()([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || + $prototype =~ m/^(\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || + $prototype =~ m/^(\w+\s*\*+)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || + $prototype =~ m/^(\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || + $prototype =~ m/^(\w+\s+\w+\s*\*+)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || + $prototype =~ m/^(\w+\s+\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || + $prototype =~ m/^(\w+\s+\w+\s+\w+\s*\*+)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || + $prototype =~ m/^(\w+\s+\w+\s+\w+\s+\w+)\s+([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || + $prototype =~ m/^(\w+\s+\w+\s+\w+\s+\w+\s*\*+)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/ || + $prototype =~ m/^(\w+\s+\w+\s*\*+\s*\w+\s*\*+\s*)\s*([a-zA-Z0-9_~:]+)\s*\(([^\{]*)\)/) { + $return_type = $1; + $declaration_name = $2; + my $args = $3; + + create_parameterlist($args, ',', $file); + } else { + print STDERR "${file}:$.: warning: cannot understand function prototype: '$prototype'\n"; + return; + } + + my $prms = join " ", @parameterlist; + check_sections($file, $declaration_name, "function", $sectcheck, $prms, ""); + + # This check emits a lot of warnings at the moment, because many + # functions don't have a 'Return' doc section. So until the number + # of warnings goes sufficiently down, the check is only performed in + # verbose mode. + # TODO: always perform the check. + if ($verbose && !$noret) { + check_return_section($file, $declaration_name, $return_type); + } + + output_declaration($declaration_name, + 'function', + {'function' => $declaration_name, + 'module' => $modulename, + 'functiontype' => $return_type, + 'parameterlist' => \@parameterlist, + 'parameterdescs' => \%parameterdescs, + 'parametertypes' => \%parametertypes, + 'sectionlist' => \@sectionlist, + 'sections' => \%sections, + 'purpose' => $declaration_purpose + }); +} + +sub reset_state { + $function = ""; + %parameterdescs = (); + %parametertypes = (); + @parameterlist = (); + %sections = (); + @sectionlist = (); + $sectcheck = ""; + $struct_actual = ""; + $prototype = ""; + + $state = STATE_NORMAL; + $inline_doc_state = STATE_INLINE_NA; +} + +sub tracepoint_munge($) { + my $file = shift; + my $tracepointname = 0; + my $tracepointargs = 0; + + if ($prototype =~ m/TRACE_EVENT\((.*?),/) { + $tracepointname = $1; + } + if ($prototype =~ m/DEFINE_SINGLE_EVENT\((.*?),/) { + $tracepointname = $1; + } + if ($prototype =~ m/DEFINE_EVENT\((.*?),(.*?),/) { + $tracepointname = $2; + } + $tracepointname =~ s/^\s+//; #strip leading whitespace + if ($prototype =~ m/TP_PROTO\((.*?)\)/) { + $tracepointargs = $1; + } + if (($tracepointname eq 0) || ($tracepointargs eq 0)) { + print STDERR "${file}:$.: warning: Unrecognized tracepoint format: \n". + "$prototype\n"; + } else { + $prototype = "static inline void trace_$tracepointname($tracepointargs)"; + } +} + +sub syscall_munge() { + my $void = 0; + + $prototype =~ s@[\r\n\t]+@ @gos; # strip newlines/CR's/tabs +## if ($prototype =~ m/SYSCALL_DEFINE0\s*\(\s*(a-zA-Z0-9_)*\s*\)/) { + if ($prototype =~ m/SYSCALL_DEFINE0/) { + $void = 1; +## $prototype = "long sys_$1(void)"; + } + + $prototype =~ s/SYSCALL_DEFINE.*\(/long sys_/; # fix return type & func name + if ($prototype =~ m/long (sys_.*?),/) { + $prototype =~ s/,/\(/; + } elsif ($void) { + $prototype =~ s/\)/\(void\)/; + } + + # now delete all of the odd-number commas in $prototype + # so that arg types & arg names don't have a comma between them + my $count = 0; + my $len = length($prototype); + if ($void) { + $len = 0; # skip the for-loop + } + for (my $ix = 0; $ix < $len; $ix++) { + if (substr($prototype, $ix, 1) eq ',') { + $count++; + if ($count % 2 == 1) { + substr($prototype, $ix, 1) = ' '; + } + } + } +} + +sub process_proto_function($$) { + my $x = shift; + my $file = shift; + + $x =~ s@\/\/.*$@@gos; # strip C99-style comments to end of line + + if ($x =~ m#\s*/\*\s+MACDOC\s*#io || ($x =~ /^#/ && $x !~ /^#\s*define/)) { + # do nothing + } + elsif ($x =~ /([^\{]*)/) { + $prototype .= $1; + } + + if (($x =~ /\{/) || ($x =~ /\#\s*define/) || ($x =~ /;/)) { + $prototype =~ s@/\*.*?\*/@@gos; # strip comments. + $prototype =~ s@[\r\n]+@ @gos; # strip newlines/cr's. + $prototype =~ s@^\s+@@gos; # strip leading spaces + if ($prototype =~ /SYSCALL_DEFINE/) { + syscall_munge(); + } + if ($prototype =~ /TRACE_EVENT/ || $prototype =~ /DEFINE_EVENT/ || + $prototype =~ /DEFINE_SINGLE_EVENT/) + { + tracepoint_munge($file); + } + dump_function($prototype, $file); + reset_state(); + } +} + +sub process_proto_type($$) { + my $x = shift; + my $file = shift; + + $x =~ s@[\r\n]+@ @gos; # strip newlines/cr's. + $x =~ s@^\s+@@gos; # strip leading spaces + $x =~ s@\s+$@@gos; # strip trailing spaces + $x =~ s@\/\/.*$@@gos; # strip C99-style comments to end of line + + if ($x =~ /^#/) { + # To distinguish preprocessor directive from regular declaration later. + $x .= ";"; + } + + while (1) { + if ( $x =~ /([^{};]*)([{};])(.*)/ ) { + if( length $prototype ) { + $prototype .= " " + } + $prototype .= $1 . $2; + ($2 eq '{') && $brcount++; + ($2 eq '}') && $brcount--; + if (($2 eq ';') && ($brcount == 0)) { + dump_declaration($prototype, $file); + reset_state(); + last; + } + $x = $3; + } else { + $prototype .= $x; + last; + } + } +} + +# xml_escape: replace <, >, and & in the text stream; +# +# however, formatting controls that are generated internally/locally in the +# kernel-doc script are not escaped here; instead, they begin life like +# $blankline_html (4 of '\' followed by a mnemonic + ':'), then these strings +# are converted to their mnemonic-expected output, without the 4 * '\' & ':', +# just before actual output; (this is done by local_unescape()) +sub xml_escape($) { + my $text = shift; + if (($output_mode eq "text") || ($output_mode eq "man")) { + return $text; + } + $text =~ s/\&/\\\\\\amp;/g; + $text =~ s/\/\\\\\\gt;/g; + return $text; +} + +# xml_unescape: reverse the effects of xml_escape +sub xml_unescape($) { + my $text = shift; + if (($output_mode eq "text") || ($output_mode eq "man")) { + return $text; + } + $text =~ s/\\\\\\amp;/\&/g; + $text =~ s/\\\\\\lt;//g; + return $text; +} + +# convert local escape strings to html +# local escape strings look like: '\\\\menmonic:' (that's 4 backslashes) +sub local_unescape($) { + my $text = shift; + if (($output_mode eq "text") || ($output_mode eq "man")) { + return $text; + } + $text =~ s/\\\\\\\\lt://g; + return $text; +} + +sub map_filename($) { + my $file; + my ($orig_file) = @_; + + if (defined($ENV{'SRCTREE'})) { + $file = "$ENV{'SRCTREE'}" . "/" . $orig_file; + } else { + $file = $orig_file; + } + + if (defined($source_map{$file})) { + $file = $source_map{$file}; + } + + return $file; +} + +sub process_export_file($) { + my ($orig_file) = @_; + my $file = map_filename($orig_file); + + if (!open(IN,"<$file")) { + print STDERR "Error: Cannot open file $file\n"; + ++$errors; + return; + } + + while () { + if (/$export_symbol/) { + $function_table{$2} = 1; + } + } + + close(IN); +} + +sub process_file($) { + my $file; + my $identifier; + my $func; + my $descr; + my $in_purpose = 0; + my $initial_section_counter = $section_counter; + my ($orig_file) = @_; + my $leading_space; + + $file = map_filename($orig_file); + + if (!open(IN,"<$file")) { + print STDERR "Error: Cannot open file $file\n"; + ++$errors; + return; + } + + $. = 1; + + $section_counter = 0; + while () { + while (s/\\\s*$//) { + $_ .= ; + } + if ($state == STATE_NORMAL) { + if (/$doc_start/o) { + $state = STATE_NAME; # next line is always the function name + $in_doc_sect = 0; + $declaration_start_line = $. + 1; + } + } elsif ($state == STATE_NAME) {# this line is the function name (always) + if (/$doc_block/o) { + $state = STATE_DOCBLOCK; + $contents = ""; + $new_start_line = $. + 1; + + if ( $1 eq "" ) { + $section = $section_intro; + } else { + $section = $1; + } + } + elsif (/$doc_decl/o) { + $identifier = $1; + if (/\s*([\w\s]+?)\s*-/) { + $identifier = $1; + } + + $state = STATE_FIELD; + # if there's no @param blocks need to set up default section + # here + $contents = ""; + $section = $section_default; + $new_start_line = $. + 1; + if (/-(.*)/) { + # strip leading/trailing/multiple spaces + $descr= $1; + $descr =~ s/^\s*//; + $descr =~ s/\s*$//; + $descr =~ s/\s+/ /g; + $declaration_purpose = xml_escape($descr); + $in_purpose = 1; + } else { + $declaration_purpose = ""; + } + + if (($declaration_purpose eq "") && $verbose) { + print STDERR "${file}:$.: warning: missing initial short description on line:\n"; + print STDERR $_; + ++$warnings; + } + + if ($identifier =~ m/^struct/) { + $decl_type = 'struct'; + } elsif ($identifier =~ m/^union/) { + $decl_type = 'union'; + } elsif ($identifier =~ m/^enum/) { + $decl_type = 'enum'; + } elsif ($identifier =~ m/^typedef/) { + $decl_type = 'typedef'; + } else { + $decl_type = 'function'; + } + + if ($verbose) { + print STDERR "${file}:$.: info: Scanning doc for $identifier\n"; + } + } else { + print STDERR "${file}:$.: warning: Cannot understand $_ on line $.", + " - I thought it was a doc line\n"; + ++$warnings; + $state = STATE_NORMAL; + } + } elsif ($state == STATE_FIELD) { # look for head: lines, and include content + if (/$doc_sect/i) { # case insensitive for supported section names + $newsection = $1; + $newcontents = $2; + + # map the supported section names to the canonical names + if ($newsection =~ m/^description$/i) { + $newsection = $section_default; + } elsif ($newsection =~ m/^context$/i) { + $newsection = $section_context; + } elsif ($newsection =~ m/^returns?$/i) { + $newsection = $section_return; + } elsif ($newsection =~ m/^\@return$/) { + # special: @return is a section, not a param description + $newsection = $section_return; + } + + if (($contents ne "") && ($contents ne "\n")) { + if (!$in_doc_sect && $verbose) { + print STDERR "${file}:$.: warning: contents before sections\n"; + ++$warnings; + } + dump_section($file, $section, xml_escape($contents)); + $section = $section_default; + } + + $in_doc_sect = 1; + $in_purpose = 0; + $contents = $newcontents; + $new_start_line = $.; + while ((substr($contents, 0, 1) eq " ") || + substr($contents, 0, 1) eq "\t") { + $contents = substr($contents, 1); + } + if ($contents ne "") { + $contents .= "\n"; + } + $section = $newsection; + $leading_space = undef; + } elsif (/$doc_end/) { + if (($contents ne "") && ($contents ne "\n")) { + dump_section($file, $section, xml_escape($contents)); + $section = $section_default; + $contents = ""; + } + # look for doc_com + + doc_end: + if ($_ =~ m'\s*\*\s*[a-zA-Z_0-9:\.]+\*/') { + print STDERR "${file}:$.: warning: suspicious ending line: $_"; + ++$warnings; + } + + $prototype = ""; + $state = STATE_PROTO; + $brcount = 0; +# print STDERR "end of doc comment, looking for prototype\n"; + } elsif (/$doc_content/) { + # miguel-style comment kludge, look for blank lines after + # @parameter line to signify start of description + if ($1 eq "") { + if ($section =~ m/^@/ || $section eq $section_context) { + dump_section($file, $section, xml_escape($contents)); + $section = $section_default; + $contents = ""; + $new_start_line = $.; + } else { + $contents .= "\n"; + } + $in_purpose = 0; + } elsif ($in_purpose == 1) { + # Continued declaration purpose + chomp($declaration_purpose); + $declaration_purpose .= " " . xml_escape($1); + $declaration_purpose =~ s/\s+/ /g; + } else { + my $cont = $1; + if ($section =~ m/^@/ || $section eq $section_context) { + if (!defined $leading_space) { + if ($cont =~ m/^(\s+)/) { + $leading_space = $1; + } else { + $leading_space = ""; + } + } + + $cont =~ s/^$leading_space//; + } + $contents .= $cont . "\n"; + } + } else { + # i dont know - bad line? ignore. + print STDERR "${file}:$.: warning: bad line: $_"; + ++$warnings; + } + } elsif ($state == STATE_INLINE) { # scanning for inline parameters + # First line (state 1) needs to be a @parameter + if ($inline_doc_state == STATE_INLINE_NAME && /$doc_inline_sect/o) { + $section = $1; + $contents = $2; + $new_start_line = $.; + if ($contents ne "") { + while ((substr($contents, 0, 1) eq " ") || + substr($contents, 0, 1) eq "\t") { + $contents = substr($contents, 1); + } + $contents .= "\n"; + } + $inline_doc_state = STATE_INLINE_TEXT; + # Documentation block end */ + } elsif (/$doc_inline_end/) { + if (($contents ne "") && ($contents ne "\n")) { + dump_section($file, $section, xml_escape($contents)); + $section = $section_default; + $contents = ""; + } + $state = STATE_PROTO; + $inline_doc_state = STATE_INLINE_NA; + # Regular text + } elsif (/$doc_content/) { + if ($inline_doc_state == STATE_INLINE_TEXT) { + $contents .= $1 . "\n"; + # nuke leading blank lines + if ($contents =~ /^\s*$/) { + $contents = ""; + } + } elsif ($inline_doc_state == STATE_INLINE_NAME) { + $inline_doc_state = STATE_INLINE_ERROR; + print STDERR "${file}:$.: warning: "; + print STDERR "Incorrect use of kernel-doc format: $_"; + ++$warnings; + } + } + } elsif ($state == STATE_PROTO) { # scanning for function '{' (end of prototype) + if (/$doc_inline_oneline/) { + $section = $1; + $contents = $2; + if ($contents ne "") { + $contents .= "\n"; + dump_section($file, $section, xml_escape($contents)); + $section = $section_default; + $contents = ""; + } + } elsif (/$doc_inline_start/) { + $state = STATE_INLINE; + $inline_doc_state = STATE_INLINE_NAME; + } elsif ($decl_type eq 'function') { + process_proto_function($_, $file); + } else { + process_proto_type($_, $file); + } + } elsif ($state == STATE_DOCBLOCK) { + if (/$doc_end/) + { + dump_doc_section($file, $section, xml_escape($contents)); + $section = $section_default; + $contents = ""; + $function = ""; + %parameterdescs = (); + %parametertypes = (); + @parameterlist = (); + %sections = (); + @sectionlist = (); + $prototype = ""; + $state = STATE_NORMAL; + } + elsif (/$doc_content/) + { + if ( $1 eq "" ) + { + $contents .= $blankline; + } + else + { + $contents .= $1 . "\n"; + } + } + } + } + if ($initial_section_counter == $section_counter) { + print STDERR "${file}:1: warning: no structured comments found\n"; + if (($output_selection == OUTPUT_INCLUDE) && ($show_not_found == 1)) { + print STDERR " Was looking for '$_'.\n" for keys %function_table; + } + if ($output_mode eq "xml") { + # The template wants at least one RefEntry here; make one. + print "\n"; + print " \n"; + print " \n"; + print " ${orig_file}\n"; + print " \n"; + print " \n"; + print " Document generation inconsistency\n"; + print " \n"; + print " \n"; + print " \n"; + print " \n"; + print " Oops\n"; + print " \n"; + print " \n"; + print " \n"; + print " The template for this document tried to insert\n"; + print " the structured comment from the file\n"; + print " ${orig_file} at this point,\n"; + print " but none was found.\n"; + print " This dummy section is inserted to allow\n"; + print " generation to continue.\n"; + print " \n"; + print " \n"; + print " \n"; + print "\n"; + } + } +} + + +$kernelversion = get_kernel_version(); + +# generate a sequence of code that will splice in highlighting information +# using the s// operator. +for (my $k = 0; $k < @highlights; $k++) { + my $pattern = $highlights[$k][0]; + my $result = $highlights[$k][1]; +# print STDERR "scanning pattern:$pattern, highlight:($result)\n"; + $dohighlight .= "\$contents =~ s:$pattern:$result:gs;\n"; +} + +# Read the file that maps relative names to absolute names for +# separate source and object directories and for shadow trees. +if (open(SOURCE_MAP, "<.tmp_filelist.txt")) { + my ($relname, $absname); + while() { + chop(); + ($relname, $absname) = (split())[0..1]; + $relname =~ s:^/+::; + $source_map{$relname} = $absname; + } + close(SOURCE_MAP); +} + +if ($output_selection == OUTPUT_EXPORTED || + $output_selection == OUTPUT_INTERNAL) { + + push(@export_file_list, @ARGV); + + foreach (@export_file_list) { + chomp; + process_export_file($_); + } +} + +foreach (@ARGV) { + chomp; + process_file($_); +} +if ($verbose && $errors) { + print STDERR "$errors errors\n"; +} +if ($verbose && $warnings) { + print STDERR "$warnings warnings\n"; +} + +exit($errors); diff --git a/libopeniscsiusr/docs/libopeniscsiusr.h.3 b/libopeniscsiusr/docs/libopeniscsiusr.h.3 new file mode 100644 index 0000000..9aecbb6 --- /dev/null +++ b/libopeniscsiusr/docs/libopeniscsiusr.h.3 @@ -0,0 +1,89 @@ +.TH "libopeniscsiusr.h" 3 "November 2017" "iSCSI userspace API - libopeniscsiusr Manual" + +.SH NAME +libopeniscsiusr.h \- iSCSI userspace API. + +.SH SYNOPSIS +#include + +.SH "DESCRIPTION" + +All the libopeniscsiusr public functions ship their own man pages. +You may use 'man -k iscsi' to find out and use 'man 3 ' to check +the detail usage. + +.SH "USAGE" + +To use libopeniscsiusr in your project, we suggest to use the 'pkg-config' way: + + * Add this line into your configure.ac: + + PKG_CHECK_MODULES([LIBISCSIUSR], [libopeniscsiusr]) + + * Add these lines into your Makefile.am: + + foo_LDFLAGS += $(LIBISCSIUSR_LIBS) + foo_CFLAGS += $(LIBISCSIUSR_CFLAGS) + +.SH LOG HANDLING + +The log handler function could be set via 'iscsi_context_log_func_set()'. +The log priority could be set via 'iscsi_context_log_priority_set()'. + +By default, the log priorities is 'LIBISCSI_LOG_PRIORITY_WARNING'. +By default, the log handler is print log to STDERR, and its code is listed +below in case you want to take it as an example to create your own log handler. + + #define _ISCSI_LOG_STRERR_ALIGN_WIDTH 80 + + void _iscsi_log_stderr(struct iscsi_context *ctx, int priority, + const char *file, int line, + const char *func_name, + const char *format, va_list args) + { + int printed_bytes = 0; + + printed_bytes += fprintf(stderr, "iSCSI %s: ", + iscsi_log_priority_str(priority)); + printed_bytes += vfprintf(stderr, format, args); + + if (printed_bytes < _ISCSI_LOG_STRERR_ALIGN_WIDTH) { + fprintf(stderr, "%*s # %s:%s():%d\n", + _ISCSI_LOG_STRERR_ALIGN_WIDTH - printed_bytes, + "", file, func_name, line); + } else { + fprintf(stderr, " # %s:%s():%d\n", file, func_name, + line); + } + } + + +.SH "SAMPLE CODE" + + struct iscsi_context *ctx = NULL; + struct iscsi_session **ses = NULL; + uint32_t se_count = 0; + uint32_t i = 0; + int rc = EXIT_SUCCESS; + + ctx = iscsi_context_new(); + iscsi_context_log_priority_set(ctx, LIBISCSI_LOG_PRIORITY_DEBUG); + + if (iscsi_sessions_get(ctx, &ses, &se_count) != LIBISCSI_OK) { + printf("FAILED\n"); + rc = EXIT_FAILURE; + } else { + printf("\nGot %" PRIu32 " iSCSI sessions\n", se_count); + for (i = 0; i < se_count; ++i) + printf("SID is %" PRIu32 "\n", + iscsi_session_sid_get(ses[i])); + iscsi_sessions_free(ses, se_count); + } + iscsi_context_free(ctx); + exit(rc); + +.SH "LICENSE" +GPLv3+ + +.SH "BUG" +Please report bug to https://github.com/open-iscsi/open-iscsi/issues diff --git a/libopeniscsiusr/docs/split-man.pl b/libopeniscsiusr/docs/split-man.pl new file mode 100644 index 0000000..916082d --- /dev/null +++ b/libopeniscsiusr/docs/split-man.pl @@ -0,0 +1,39 @@ +#!/usr/bin/perl +# Originally From: +# https://www.kernel.org/doc/Documentation/kernel-doc-nano-HOWTO.txt +# +# Changes: +# * Create manpage section 3 instead of 9. +# * Replace 'Kernel Hackers Manual' to +# 'iSCSI Userspace API - libopeniscsiusr Manual' +# * Remove LINUX from header. +$man_sec_num = 3; +$title = 'iSCSI Userspace API - libopeniscsiusr Manual'; + +if ( $#ARGV < 0 ) { + die "where do I put the results?\n"; +} + +mkdir $ARGV[0], 0777; +$state = 0; +while () { + if (/^\.TH \"[^\"]*\" 9 \"([^\"]*)\"/) { + if ( $state == 1 ) { close OUT } + $state = 1; + $fn = "$ARGV[0]/$1.$man_sec_num"; + print STDERR "Creating $fn\n"; + open OUT, ">$fn" or die "can't open $fn: $!\n"; + + # Change man page code from 9 to $man_sec_num; + s/^\.TH (\"[^\"]*\") 9 \"([^\"]*)\"/\.TH $1 $man_sec_num \"$2\"/; + s/Kernel Hacker's Manual/$title/g; + s/LINUX//g; + + print OUT $_; + } + elsif ( $state != 0 ) { + print OUT $_; + } +} + +close OUT; diff --git a/libopeniscsiusr/idbm.c b/libopeniscsiusr/idbm.c new file mode 100644 index 0000000..d020e6c --- /dev/null +++ b/libopeniscsiusr/idbm.c @@ -0,0 +1,1096 @@ +/* + * Copyright (C) 2017-2018 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ + +/* The code below is modified from usr/idbm.c which licensed like below: + * + * iSCSI Discovery Database Library + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * Copyright (C) 2006 Mike Christie + * Copyright (C) 2006 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + * + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE /* For strerror_r() */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libopeniscsiusr/libopeniscsiusr_common.h" + +#include "context.h" +#include "idbm.h" +#include "misc.h" +#include "idbm_fields.h" +#include "iface.h" +#include "version.h" +#include "node.h" +#include "default.h" + +#define TYPE_INT_O 1 +#define TYPE_STR 2 +#define TYPE_UINT8 3 +#define TYPE_UINT16 4 +#define TYPE_UINT32 5 +#define TYPE_INT32 6 +#define TYPE_INT64 7 +#define TYPE_BOOL 8 +#define MAX_KEYS 256 /* number of keys total(including CNX_MAX) */ +#define NAME_MAXVAL 128 /* the maximum length of key name */ +#define VALUE_MAXVAL 256 /* the maximum length of 223 bytes in the RFC. */ +/* ^ MAX_KEYS, NAME_MAXVAL and VALUE_MAXVAL are copied from usr/idbm.h + * The RFC 3720 only said: + * If not otherwise specified, the maximum length of a simple-value (not + * its encoded representation) is 255 bytes, not including the delimiter + * (comma or zero byte). + */ + +#define OPTS_MAXVAL 8 + +#define IDBM_HIDE 0 /* Hide parameter when print. */ +#define IDBM_SHOW 1 /* Show parameter when print. */ +#define IDBM_MASKED 2 /* Show "stars" instead of real value when print */ + +#define ISCSI_BEGIN_REC "# BEGIN RECORD "ISCSI_VERSION_STR +#define ISCSI_END_REC "# END RECORD" + +#ifndef LOCK_DIR +#define LOCK_DIR "/run/lock/iscsi" +#endif +#define LOCK_FILE LOCK_DIR"/lock" +#define LOCK_WRITE_FILE LOCK_DIR"/lock.write" + +#define _rec_str(_key, _recs, _org, _name, _show, _n, _mod) \ +do { \ + _recs[_n].type = TYPE_STR; \ + _strncpy(_recs[_n].name, _key, NAME_MAXVAL); \ + if (strlen((char*)_org->_name)) \ + _strncpy((char*)_recs[_n].value, (char*)_org->_name, \ + VALUE_MAXVAL); \ + _recs[_n].data = &_org->_name; \ + _recs[_n].data_len = sizeof(_org->_name); \ + _recs[_n].visible = _show; \ + _recs[_n].can_modify = _mod; \ + _n++; \ +} while(0) + +#define _rec_uint8(_key, _recs, _org, _name, _show, _n, _mod) \ +do { \ + _recs[_n].type = TYPE_UINT8; \ + _strncpy(_recs[_n].name, _key, NAME_MAXVAL); \ + snprintf(_recs[_n].value, VALUE_MAXVAL, "%" PRIu8, _org->_name); \ + _recs[_n].data = &_org->_name; \ + _recs[_n].data_len = sizeof(_org->_name); \ + _recs[_n].visible = _show; \ + _recs[_n].can_modify = _mod; \ + _n++; \ +} while (0) + +#define _rec_uint16(_key, _recs, _org, _name, _show, _n, _mod) \ +do { \ + _recs[_n].type = TYPE_UINT16; \ + _strncpy(_recs[_n].name, _key, NAME_MAXVAL); \ + snprintf(_recs[_n].value, VALUE_MAXVAL, "%" PRIu16, _org->_name); \ + _recs[_n].data = &_org->_name; \ + _recs[_n].data_len = sizeof(_org->_name); \ + _recs[_n].visible = _show; \ + _recs[_n].can_modify = _mod; \ + _n++; \ +} while (0) + +#define _rec_uint32(_key, _recs, _org, _name, _show, _n, _mod) \ +do { \ + _recs[_n].type = TYPE_UINT32; \ + _strncpy(_recs[_n].name, _key, NAME_MAXVAL); \ + snprintf(_recs[_n].value, VALUE_MAXVAL, "%" PRIu32, _org->_name); \ + _recs[_n].data = &_org->_name; \ + _recs[_n].data_len = sizeof(_org->_name); \ + _recs[_n].visible = _show; \ + _recs[_n].can_modify = _mod; \ + _n++; \ +} while (0) + +#define _rec_int32(_key, _recs, _org, _name, _show, _n, _mod) \ +do { \ + _recs[_n].type = TYPE_INT32; \ + _strncpy(_recs[_n].name, _key, NAME_MAXVAL); \ + snprintf(_recs[_n].value, VALUE_MAXVAL, "%" PRIi32, _org->_name); \ + _recs[_n].data = &_org->_name; \ + _recs[_n].data_len = sizeof(_org->_name); \ + _recs[_n].visible = _show; \ + _recs[_n].can_modify = _mod; \ + _n++; \ +} while (0) + +#define _rec_int64(_key, _recs, _org, _name, _show, _n, _mod) \ +do { \ + _recs[_n].type = TYPE_INT64; \ + _strncpy(_recs[_n].name, _key, NAME_MAXVAL); \ + snprintf(_recs[_n].value, VALUE_MAXVAL, "%" PRIi64, _org->_name); \ + _recs[_n].data = &_org->_name; \ + _recs[_n].data_len = sizeof(_org->_name); \ + _recs[_n].visible = _show; \ + _recs[_n].can_modify = _mod; \ + _n++; \ +} while (0) + +#define _rec_bool(_key, _recs, _org, _name, _show, _n, _mod) \ +do { \ + _recs[_n].type = TYPE_BOOL; \ + _strncpy(_recs[_n].name, _key, NAME_MAXVAL); \ + snprintf(_recs[_n].value, VALUE_MAXVAL, "%s", \ + _org->_name ? "Yes" : "No"); \ + _recs[_n].data = &_org->_name; \ + _recs[_n].data_len = sizeof(_org->_name); \ + _recs[_n].visible = _show; \ + _recs[_n].can_modify = _mod; \ + _n++; \ +} while(0) + +#define _rec_int_o2(_key, _recs, _org, _name, _show, _op0, _op1, _n, _mod) \ +do { \ + _recs[_n].type = TYPE_INT_O; \ + _strncpy(_recs[_n].name, _key, NAME_MAXVAL); \ + if (_org->_name == 0) _strncpy(_recs[_n].value, _op0, VALUE_MAXVAL); \ + if (_org->_name == 1) _strncpy(_recs[_n].value, _op1, VALUE_MAXVAL); \ + _recs[_n].data = &_org->_name; \ + _recs[_n].data_len = sizeof(_org->_name); \ + _recs[_n].visible = _show; \ + _recs[_n].opts[0] = _op0; \ + _recs[_n].opts[1] = _op1; \ + _recs[_n].numopts = 2; \ + _recs[_n].can_modify = _mod; \ + _n++; \ +} while(0) + +#define _rec_int_o3(_key, _recs, _org, _name, _show, _op0, _op1, _op2, _n, \ + _mod) \ +do { \ + _rec_int_o2(_key, _recs, _org, _name, _show, _op0, _op1, _n, _mod); \ + _n--; \ + if (_org->_name == 2) _strncpy(_recs[_n].value, _op2, VALUE_MAXVAL);\ + _recs[_n].opts[2] = _op2; \ + _recs[_n].numopts = 3; \ + _n++; \ +} while(0) + +#define _rec_int_o4(_key, _recs, _org, _name, _show, _op0, _op1, _op2, _op3, \ + _n, _mod) \ +do { \ + _rec_int_o3(_key, _recs, _org, _name, _show, _op0, _op1, _op2, _n, \ + _mod); \ + _n--; \ + if (_org->_name == 3) _strncpy(_recs[_n].value, _op3, VALUE_MAXVAL);\ + _recs[_n].opts[3] = _op3; \ + _recs[_n].numopts = 4; \ + _n++; \ +} while(0) + +#define _rec_int_o5(_key, _recs, _org, _name, _show, _op0, _op1, _op2, _op3, \ + _op4, _n, _mod) \ +do { \ + _rec_int_o4(_key, _recs, _org, _name, _show, _op0, _op1, _op2, _op3, \ + _n, _mod); \ + _n--; \ + if (_org->_name == 4) _strncpy(_recs[_n].value, _op4, VALUE_MAXVAL);\ + _recs[_n].opts[4] = _op4; \ + _recs[_n].numopts = 5; \ + _n++; \ +} while(0) + +#define _rec_int_o6(_key, _recs, _org, _name, _show, _op0, _op1, _op2, _op3, \ + _op4, _op5, _n, _mod) \ +do { \ + _rec_int_o5(_key, _recs, _org, _name, _show, _op0, _op1, _op2, _op3, \ + _op4, _n, _mod); \ + _n--; \ + if (_org->_name == 5) _strncpy(_recs[_n].value, _op5, VALUE_MAXVAL);\ + _recs[_n].opts[5] = _op5; \ + _recs[_n].numopts = 6; \ + _n++; \ +} while(0) + +enum modify_mode { + _CANNOT_MODIFY, + _CAN_MODIFY, +}; + +struct idbm_rec { + int type; + char name[NAME_MAXVAL]; + char value[VALUE_MAXVAL]; + void *data; + int data_len; + int visible; + char* opts[OPTS_MAXVAL]; + int numopts; + /* + * TODO: make it a enum that can indicate whether it also requires + * a relogin to pick up if a session is running. + */ + enum modify_mode can_modify; +}; + +static void _idbm_node_rec_link(struct iscsi_node *node, struct idbm_rec *recs); + +int _idbm_lock(struct iscsi_context *ctx) +{ + int fd, i, ret; + struct idbm *db = NULL; + char strerr_buff[_STRERR_BUFF_LEN]; + int errno_save = 0; + + assert(ctx != NULL); + + db = ctx->db; + + if (db->refs > 0) { + db->refs++; + return 0; + } + + if (access(LOCK_DIR, F_OK) != 0) { + if (mkdir(LOCK_DIR, 0660) != 0) { + _error(ctx, "Could not open %s: %d %s", LOCK_DIR, errno, + _strerror(errno, strerr_buff)); + return LIBISCSI_ERR_IDBM; + } + } + + fd = open(LOCK_FILE, O_RDWR | O_CREAT, 0666); + if (fd >= 0) + close(fd); + + for (i = 0; i < 3000; i++) { + ret = link(LOCK_FILE, LOCK_WRITE_FILE); + if (ret == 0) + break; + errno_save = errno; + + if (errno != EEXIST) { + _error(ctx, "Maybe you are not root? " + "Could not lock discovery DB: %s: %d %s", + LOCK_WRITE_FILE, errno_save, + _strerror(errno_save, strerr_buff)); + return LIBISCSI_ERR_IDBM; + } else if (i == 0) + _debug(ctx, "Waiting for discovery DB lock on %s", + LOCK_WRITE_FILE); + + usleep(10000); + } + + if (ret != 0) { + _error(ctx, "Timeout on acquiring lock on DB: %s, errno: %d %s", + LOCK_WRITE_FILE, errno_save, + _strerror(errno_save, strerr_buff)); + return LIBISCSI_ERR_IDBM; + } + + db->refs = 1; + return 0; +} + +void _idbm_unlock(struct iscsi_context *ctx) +{ + struct idbm *db = NULL; + + assert(ctx != NULL); + + db = ctx->db; + + if (db->refs > 1) { + db->refs--; + return; + } + + db->refs = 0; + unlink(LOCK_WRITE_FILE); +} + +static struct idbm_rec* _idbm_recs_alloc(void) +{ + return calloc(MAX_KEYS, sizeof(struct idbm_rec)); +} + +static void _idbm_recs_free(struct idbm_rec* recs) +{ + free(recs); +} + +static int _idbm_iface_rec_link(struct iscsi_iface *iface, + struct idbm_rec *recs, int num) +{ + int init_num = num; + if (init_num == 0) + _rec_str(IFACE_ISCSINAME, recs, iface, name, IDBM_SHOW, num, + _CANNOT_MODIFY); + else + _rec_str(IFACE_ISCSINAME, recs, iface, name, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_str(IFACE_NETNAME, recs, iface, netdev, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_str(IFACE_IPADDR, recs, iface, ipaddress, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_uint8(IFACE_PREFIX_LEN, recs, iface, prefix_len, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_str(IFACE_HWADDR, recs, iface, hwaddress, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_str(IFACE_TRANSPORTNAME, recs, iface, transport_name, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_str(IFACE_INAME, recs, iface, iname, IDBM_SHOW, num, _CAN_MODIFY); + _rec_str(IFACE_STATE, recs, iface, state, IDBM_SHOW, num, _CAN_MODIFY); + _rec_uint16(IFACE_VLAN_ID, recs, iface, vlan_id, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_uint8(IFACE_VLAN_PRIORITY, recs, iface, vlan_priority, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_str(IFACE_VLAN_STATE, recs, iface, vlan_state, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_uint32(IFACE_NUM, recs, iface, iface_num, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_uint16(IFACE_MTU, recs, iface, mtu, IDBM_SHOW, num, _CAN_MODIFY); + _rec_uint16(IFACE_PORT, recs, iface, port, IDBM_SHOW, num, _CAN_MODIFY); + + if (! iface->is_ipv6) { + _rec_str(IFACE_BOOT_PROTO, recs, iface, bootproto, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_str(IFACE_SUBNET_MASK, recs, iface, subnet_mask, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_str(IFACE_GATEWAY, recs, iface, gateway, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_str(IFACE_DHCP_ALT_CID, recs, iface, + dhcp_alt_client_id_state, IDBM_SHOW, num, _CAN_MODIFY); + _rec_str(IFACE_DHCP_ALT_CID_STR, recs, iface, + dhcp_alt_client_id, IDBM_SHOW, num, _CAN_MODIFY); + _rec_str(IFACE_DHCP_DNS, recs, iface, dhcp_dns, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_str(IFACE_DHCP_LEARN_IQN, recs, iface, dhcp_learn_iqn, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_str(IFACE_DHCP_REQ_VID, recs, iface, + dhcp_req_vendor_id_state, IDBM_SHOW, num, _CAN_MODIFY); + _rec_str(IFACE_DHCP_VID, recs, iface, dhcp_vendor_id_state, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_str(IFACE_DHCP_VID_STR, recs, iface, dhcp_vendor_id, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_str(IFACE_DHCP_SLP_DA, recs, iface, dhcp_slp_da, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_str(IFACE_FRAGMENTATION, recs, iface, fragmentation, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_str(IFACE_GRAT_ARP, recs, iface, gratuitous_arp, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_str(IFACE_IN_FORWARD, recs, iface, incoming_forwarding, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_str(IFACE_TOS_STATE, recs, iface, tos_state, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_uint8(IFACE_TOS, recs, iface, tos, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_uint8(IFACE_TTL, recs, iface, ttl, IDBM_SHOW, num, + _CAN_MODIFY); + } else { + _rec_str(IFACE_IPV6_AUTOCFG, recs, iface, ipv6_autocfg, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_str(IFACE_LINKLOCAL_AUTOCFG, recs, iface, + linklocal_autocfg, IDBM_SHOW, num, _CAN_MODIFY); + _rec_str(IFACE_ROUTER_AUTOCFG, recs, iface, router_autocfg, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_str(IFACE_LINKLOCAL, recs, iface, ipv6_linklocal, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_str(IFACE_ROUTER, recs, iface, ipv6_router, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_uint8(IFACE_DUP_ADDR_DETECT_CNT, recs, iface, + dup_addr_detect_cnt, IDBM_SHOW, num, _CAN_MODIFY); + _rec_uint32(IFACE_FLOW_LABEL, recs, iface, flow_label, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_str(IFACE_GRAT_NEIGHBOR_ADV, recs, iface, + gratuitous_neighbor_adv, IDBM_SHOW, num, _CAN_MODIFY); + _rec_uint8(IFACE_HOP_LIMIT, recs, iface, hop_limit, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_str(IFACE_MLD, recs, iface, mld, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_uint32(IFACE_ND_REACHABLE_TMO, recs, iface, + nd_reachable_tmo, IDBM_SHOW, num, _CAN_MODIFY); + _rec_uint32(IFACE_ND_REXMIT_TIME, recs, iface, nd_rexmit_time, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_uint32(IFACE_ND_STALE_TMO, recs, iface, nd_stale_tmo, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_uint32(IFACE_RTR_ADV_LINK_MTU, recs, iface, + router_adv_link_mtu, IDBM_SHOW, num, _CAN_MODIFY); + _rec_uint8(IFACE_TRAFFIC_CLASS, recs, iface, traffic_class, + IDBM_SHOW, num, _CAN_MODIFY); + } + + _rec_str(IFACE_DELAYED_ACK, recs, iface, delayed_ack, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_str(IFACE_TCP_NAGLE, recs, iface, nagle, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_str(IFACE_TCP_WSF_STATE, recs, iface, tcp_wsf_state, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_uint8(IFACE_TCP_WSF, recs, iface, tcp_wsf, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_uint8(IFACE_TCP_TIMER_SCALE, recs, iface, tcp_timer_scale, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_str(IFACE_TCP_TIMESTAMP, recs, iface, tcp_timestamp, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_str(IFACE_REDIRECT, recs, iface, redirect, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_uint16(IFACE_DEF_TMF_TMO, recs, iface, def_task_mgmt_tmo, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_str(IFACE_HDRDGST, recs, iface, header_digest, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_str(IFACE_DATADGST, recs, iface, data_digest, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_str(IFACE_IMM_DATA, recs, iface, immediate_data, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_str(IFACE_INITIAL_R2T, recs, iface, initial_r2t, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_str(IFACE_DSEQ_INORDER, recs, iface, data_seq_inorder, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_str(IFACE_DPDU_INORDER, recs, iface, data_pdu_inorder, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_uint8(IFACE_ERL, recs, iface, erl, IDBM_SHOW, num, _CAN_MODIFY); + _rec_uint32(IFACE_MAX_RECV_DLEN, recs, iface, max_recv_dlength, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_uint32(IFACE_FIRST_BURST, recs, iface, first_burst_len, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_uint16(IFACE_MAX_R2T, recs, iface, max_out_r2t, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_uint32(IFACE_MAX_BURST, recs, iface, max_burst_len, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_str(IFACE_CHAP_AUTH, recs, iface, chap_auth, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_str(IFACE_BIDI_CHAP, recs, iface, bidi_chap, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_str(IFACE_STRICT_LOGIN_COMP, recs, iface, strict_login_comp, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_str(IFACE_DISCOVERY_AUTH, recs, iface, discovery_auth, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_str(IFACE_DISCOVERY_LOGOUT, recs, iface, discovery_logout, + IDBM_SHOW, num, _CAN_MODIFY); + return num; +} + +static void _idbm_recs_print(struct idbm_rec *recs, FILE *f, int show) +{ + int i; + fprintf(f, "%s\n", ISCSI_BEGIN_REC); + for (i = 0; i < MAX_KEYS; i++) { + if (recs[i].visible == IDBM_HIDE) + continue; + if (show == IDBM_MASKED && recs[i].visible == IDBM_MASKED) { + if (*(char*)recs[i].data) { + fprintf(f, "%s = ********\n", recs[i].name); + continue; + } + /* fall through */ + } + + if (strlen(recs[i].value)) + fprintf(f, "%s = %s\n", recs[i].name, recs[i].value); + else if (f == stdout) + fprintf(f, "%s = \n", recs[i].name); + } + fprintf(f, "%s\n", ISCSI_END_REC); +} + +void _idbm_iface_print(struct iscsi_iface *iface, FILE *f) +{ + struct idbm_rec *recs = NULL; + + recs = _idbm_recs_alloc(); + if (recs == NULL) + return; + + _idbm_iface_rec_link(iface, recs, 0); + + _idbm_recs_print(recs, f, IDBM_SHOW); + + _idbm_recs_free(recs); +} + +void _idbm_node_print(struct iscsi_node *node, FILE *f, bool show_secret) +{ + struct idbm_rec *recs = NULL; + + recs = _idbm_recs_alloc(); + if (recs == NULL) + return; + + _idbm_node_rec_link(node, recs); + _idbm_recs_print(recs, f, show_secret ? IDBM_SHOW : IDBM_MASKED); + _idbm_recs_free(recs); +} + +static int _idbm_rec_update_param(struct iscsi_context *ctx, + struct idbm_rec *recs, char *name, + char *value, int line_number) +{ + int rc = LIBISCSI_OK; + int i = 0; + int j = 0; + int passwd_done = 0; + char passwd_len[8]; + + assert(ctx != NULL); + assert(recs != NULL); + assert(name != NULL); + assert(value != NULL); + +setup_passwd_len: + for (i = 0; i < MAX_KEYS; ++i) { + if (!strcmp(name, recs[i].name)) { + _debug(ctx, "updated '%s', '%s' => '%s'", name, + recs[i].value, value); + /* parse recinfo by type */ + switch (recs[i].type) { + case TYPE_UINT8: + if (!recs[i].data) + continue; + + *(uint8_t *)recs[i].data = + strtoul(value, NULL, 10); + goto updated; + case TYPE_UINT16: + if (!recs[i].data) + continue; + + *(uint16_t *)recs[i].data = + strtoul(value, NULL, 10); + goto updated; + case TYPE_UINT32: + if (!recs[i].data) + continue; + + *(uint32_t *)recs[i].data = + strtoul(value, NULL, 10); + goto updated; + case TYPE_STR: + if (!recs[i].data) + continue; + + _strncpy((char*)recs[i].data, + value, recs[i].data_len); + goto updated; + case TYPE_INT32: + if (!recs[i].data) + continue; + + *(int32_t *)recs[i].data = + strtoul(value, NULL, 10); + goto updated; + case TYPE_INT64: + if (!recs[i].data) + continue; + + *(int64_t *)recs[i].data = + strtoull(value, NULL, 10); + goto updated; + case TYPE_INT_O: + for (j = 0; j < recs[i].numopts; ++j) { + if (!strcmp(value, recs[i].opts[j])) { + if (!recs[i].data) + continue; + + *(int*)recs[i].data = j; + goto updated; + } + } + goto unknown_value; + case TYPE_BOOL: + if (!recs[i].data) + continue; + if (strcmp(value, "Yes") == 0) + *(bool *)recs[i].data = true; + else if (strcmp(value, "No") == 0) + *(bool *)recs[i].data = false; + else + goto unknown_value; + goto updated; + default: +unknown_value: + _error(ctx, "Got unknown data type %d " + "for name '%s', value '%s'", + recs[i].data, recs[i].name, + recs[i].value); + rc = LIBISCSI_ERR_BUG; + goto out; + } + if (line_number) { + _warn(ctx, "config file line %d contains " + "unknown value format '%s' for " + "parameter name '%s'", + line_number, value, name); + } else { + _error(ctx, "unknown value format '%s' for " + "parameter name '%s'", value, name); + rc = LIBISCSI_ERR_INVAL; + } + goto out; + } + } + _error(ctx, "Unknown parameter name %s", name); + rc = LIBISCSI_ERR_INVAL; + goto out; + +updated: + _strncpy((char*)recs[i].value, value, VALUE_MAXVAL); + +#define check_password_param(_param) \ + if (!passwd_done && !strcmp(#_param, name)) { \ + passwd_done = 1; \ + name = #_param "_length"; \ + snprintf(passwd_len, 8, "%.7d", (int)strlen(value) & 0xffff); \ + value = passwd_len; \ + goto setup_passwd_len; \ + } + + check_password_param(node.session.auth.password); + check_password_param(node.session.auth.password_in); + check_password_param(discovery.sendtargets.auth.password); + check_password_param(discovery.sendtargets.auth.password_in); + check_password_param(discovery.slp.auth.password); + check_password_param(discovery.slp.auth.password_in); + check_password_param(host.auth.password); + check_password_param(host.auth.password_in); + +out: + return rc; +} + +/* + * from linux kernel + */ +static char *strstrip(char *s) +{ + size_t size; + char *end; + + size = strlen(s); + if (!size) + return s; + + end = s + size - 1; + while (end >= s && isspace(*end)) + end--; + *(end + 1) = '\0'; + + while (*s && isspace(*s)) + s++; + + return s; +} + +static int _idbm_recs_read(struct iscsi_context *ctx, struct idbm_rec *recs, + const char *conf_path) +{ + int rc = LIBISCSI_OK; + char name[NAME_MAXVAL]; + char value[VALUE_MAXVAL]; + char *line = NULL; + char *nl = NULL; + char buffer[2048]; + int line_number = 0; + int c = 0; + int i = 0; + FILE *f = NULL; + int errno_save = 0; + char strerr_buff[_STRERR_BUFF_LEN]; + + assert(ctx != NULL); + assert(recs != NULL); + assert(conf_path != NULL); + + f = fopen(conf_path, "r"); + errno_save = errno; + if (!f) { + _error(ctx, "Failed to open %s using read mode: %d %s", + conf_path, errno_save, + _strerror(errno_save, strerr_buff)); + rc = LIBISCSI_ERR_IDBM; + goto out; + } + + _info(ctx, "Parsing iSCSI interface configuration %s", conf_path); + /* process the config file */ + do { + line = fgets(buffer, sizeof (buffer), f); + line_number++; + if (!line) + continue; + if (strlen(line) == 0) + continue; + + nl = line + strlen(line) - 1; + if (*nl != '\n') { + _warn(ctx, "Config file %s line %d too long.", + conf_path, line_number); + continue; + } + + line = strstrip(line); + /* process any non-empty, non-comment lines */ + if (!*line || *line == '\0' || *line == '\n' || *line == '#') + continue; + + /* parse name */ + i=0; nl = line; *name = 0; + while (*nl && !isspace(c = *nl) && *nl != '=') { + *(name+i) = *nl; i++; nl++; + } + if (!*nl) { + _warn(ctx, "config file %s line %d do not has value", + conf_path, line_number); + continue; + } + *(name+i)=0; nl++; + /* skip after-name traling spaces */ + while (*nl && isspace(c = *nl)) nl++; + if (*nl && *nl != '=') { + _warn(ctx, "config file %s line %d has not '=' " + "separator", conf_path, line_number); + continue; + } + /* skip '=' sepa */ + nl++; + /* skip after-sepa traling spaces */ + while (*nl && isspace(c = *nl)) nl++; + if (!*nl) { + _warn(ctx, "config file %s line %d do not has value", + conf_path, line_number); + continue; + } + /* parse value */ + i=0; *value = 0; + while (*nl) { + *(value+i) = *nl; i++; nl++; + } + *(value+i) = 0; + + rc = _idbm_rec_update_param(ctx, recs, name, value, + line_number); + if (rc == LIBISCSI_ERR_INVAL) { + _error(ctx, "config file %s invalid.", conf_path); + goto out; + } else if (rc != LIBISCSI_OK) + goto out; + } while (line); + +out: + if (f != NULL) + fclose(f); + return rc; +} + +int _idbm_iface_get(struct iscsi_context *ctx, const char *iface_name, struct + iscsi_iface **iface) +{ + int rc = LIBISCSI_OK; + char *conf_path = NULL; + struct idbm_rec *recs = NULL; + + assert(iface != NULL); + assert(ctx != NULL); + + *iface = NULL; + + if (iface_name == NULL) + goto out; + + if (strcmp(iface_name, "iface.example") == 0) + goto out; + + _good(_asprintf(&conf_path, "%s/%s", IFACE_CONFIG_DIR, iface_name), + rc, out); + + *iface = calloc(1, sizeof(struct iscsi_iface)); + _alloc_null_check(ctx, *iface, rc, out); + + snprintf((*iface)->name, sizeof((*iface)->name)/sizeof(char), + "%s", iface_name); + + if (strstr(iface_name, "ipv6")) + (*iface)->is_ipv6 = true; + + recs = _idbm_recs_alloc(); + _alloc_null_check(ctx, recs, rc, out); + + _idbm_iface_rec_link(*iface, recs, 0); + + _good(_idbm_recs_read(ctx, recs, conf_path), rc, out); + + if (! _iface_is_valid(*iface)) { + _warn(ctx, "'%s' is not a valid iSCSI interface configuration " + "file", conf_path); + iscsi_iface_free(*iface); + *iface = NULL; + /* We still treat this as pass(no error) */ + } + +out: + if (rc != LIBISCSI_OK) { + iscsi_iface_free(*iface); + *iface = NULL; + } + free(conf_path); + _idbm_recs_free(recs); + return rc; +} +struct idbm *_idbm_new(void) +{ + return calloc(1, sizeof(struct idbm)); +} + +void _idbm_free(struct idbm *db) +{ + free(db); +} + +static void _idbm_node_rec_link(struct iscsi_node *node, struct idbm_rec *recs) +{ + int num = 0; + + _rec_str(NODE_NAME, recs, node, target_name, IDBM_SHOW, num, + _CANNOT_MODIFY); + _rec_int32(NODE_TPGT, recs, node, tpgt, IDBM_SHOW, num, + _CANNOT_MODIFY); + _rec_int_o3(NODE_STARTUP, recs, node, startup, IDBM_SHOW, "manual", + "automatic", "onboot", num, _CAN_MODIFY); + _rec_bool(NODE_LEADING_LOGIN, recs, node, leading_login, IDBM_SHOW, + num, _CAN_MODIFY); + + /* + * Note: because we do not add the iface.iscsi_ifacename to + * sysfs iscsiadm does some weird matching. We can change the iface + * values if a session is not running, but node record ifaces values + * have to be changed and so do the iface record ones. + * + * Users should normally not want to change the iface ones + * in the node record directly and instead do it through + * the iface mode which will do the right thing (although that + * needs some locking). + */ + num = _idbm_iface_rec_link(&((*node).iface), recs, num); + + _rec_str(NODE_DISC_ADDR, recs, node, disc_address, IDBM_SHOW, num, + _CANNOT_MODIFY); + _rec_int32(NODE_DISC_PORT, recs, node, disc_port, IDBM_SHOW, num, + _CANNOT_MODIFY); + _rec_int_o6(NODE_DISC_TYPE, recs, node, disc_type, IDBM_SHOW, + "send_targets", "isns", "offload_send_targets", "slp", + "static", "fw", num, _CANNOT_MODIFY); + + _rec_uint32(SESSION_INIT_CMDSN, recs, node, session.initial_cmdsn, + IDBM_SHOW, num,_CAN_MODIFY); + _rec_int64(SESSION_INIT_LOGIN_RETRY, recs, node, + session.initial_login_retry_max, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_int64(SESSION_XMIT_THREAD_PRIORITY, recs, node, + session.xmit_thread_priority, IDBM_SHOW, num, _CAN_MODIFY); + _rec_uint16(SESSION_CMDS_MAX, recs, node, session.cmds_max, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_uint16(SESSION_QDEPTH, recs, node, session.queue_depth, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_int64(SESSION_NR_SESSIONS, recs, node, session.nr_sessions, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_int_o2(SESSION_AUTH_METHOD, recs, node, session.auth.authmethod, + IDBM_SHOW, "None", "CHAP", num, _CAN_MODIFY); + _rec_str(SESSION_USERNAME, recs, node, session.auth.username, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_str(SESSION_PASSWORD, recs, node, session.auth.password, + IDBM_MASKED, num, _CAN_MODIFY); + _rec_uint32(SESSION_PASSWORD_LEN, recs, node, + session.auth.password_length, IDBM_HIDE, num, _CAN_MODIFY); + _rec_str(SESSION_USERNAME_IN, recs, node, session.auth.username_in, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_str(SESSION_PASSWORD_IN, recs, node, session.auth.password_in, + IDBM_MASKED, num, _CAN_MODIFY); + _rec_uint32(SESSION_PASSWORD_IN_LEN, recs, node, + session.auth.password_in_length, IDBM_HIDE, num, + _CAN_MODIFY); + _rec_int64(SESSION_REPLACEMENT_TMO, recs, node, + session.tmo.replacement_timeout, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_int64(SESSION_ABORT_TMO, recs, node, session.err_tmo.abort_timeout, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_int64(SESSION_LU_RESET_TMO, recs, node, + session.err_tmo.lu_reset_timeout, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_int64(SESSION_TGT_RESET_TMO, recs, node, + session.err_tmo.tgt_reset_timeout, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_int64(SESSION_HOST_RESET_TMO, recs, node, + session.err_tmo.host_reset_timeout, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_bool(SESSION_FAST_ABORT, recs, node, session.op_cfg.FastAbort, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_bool(SESSION_INITIAL_R2T, recs, node, session.op_cfg.InitialR2T, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_bool(SESSION_IMM_DATA, recs, node, session.op_cfg.ImmediateData, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_int64(SESSION_FIRST_BURST, recs, node, + session.op_cfg.FirstBurstLength, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_int64(SESSION_MAX_BURST, recs, node, session.op_cfg.MaxBurstLength, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_int64(SESSION_DEF_TIME2RETAIN, recs, node, + session.op_cfg.DefaultTime2Retain, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_int64(SESSION_DEF_TIME2WAIT, recs, node, + session.op_cfg.DefaultTime2Wait, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_int64(SESSION_MAX_CONNS, recs, node, session.op_cfg.MaxConnections, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_int64(SESSION_MAX_R2T, recs, node, + session.op_cfg.MaxOutstandingR2T, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_int64(SESSION_ERL, recs, node, session.op_cfg.ERL, IDBM_SHOW, num, + _CAN_MODIFY); + _rec_int_o2(SESSION_SCAN, recs, node, session.scan, IDBM_SHOW, "manual", + "auto", num, _CAN_MODIFY); + _rec_int64(SESSION_REOPEN_MAX, recs, node, session.reopen_max, IDBM_SHOW, num, + _CAN_MODIFY); + + _rec_str(CONN_ADDR, recs, node, conn.address, IDBM_SHOW, num, + _CANNOT_MODIFY); + _rec_int32(CONN_PORT, recs, node, conn.port, IDBM_SHOW, num, + _CANNOT_MODIFY); + _rec_int_o3(CONN_STARTUP, recs, node, conn.startup, IDBM_SHOW, + "manual", "automatic", "onboot", num, _CAN_MODIFY); + _rec_int64(CONN_WINDOW_SIZE, recs, node, conn.tcp.window_size, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_int64(CONN_SERVICE_TYPE, recs, node, conn.tcp.type_of_service, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_int64(CONN_LOGOUT_TMO, recs, node, conn.tmo.logout_timeout, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_int64(CONN_LOGIN_TMO, recs, node, conn.tmo.login_timeout, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_int64(CONN_AUTH_TMO, recs, node, conn.tmo.auth_timeout, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_int64(CONN_NOP_INT, recs, node, conn.tmo.noop_out_interval, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_int64(CONN_NOP_TMO, recs, node, conn.tmo.noop_out_timeout, + IDBM_SHOW, num, _CAN_MODIFY); + _rec_int64(CONN_MAX_XMIT_DLEN, recs, node, + conn.op_cfg.MaxXmitDataSegmentLength, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_int64(CONN_MAX_RECV_DLEN, recs, node, + conn.op_cfg.MaxRecvDataSegmentLength, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_int_o4(CONN_HDR_DIGEST, recs, node, conn.op_cfg.HeaderDigest, + IDBM_SHOW, "None", "CRC32C", "CRC32C,None", + "None,CRC32C", num, _CAN_MODIFY); + _rec_int_o4(CONN_DATA_DIGEST, recs, node, conn.op_cfg.DataDigest, + IDBM_SHOW, "None", "CRC32C", "CRC32C,None", + "None,CRC32C", num, _CAN_MODIFY); + _rec_bool(CONN_IFMARKER, recs, node, conn.op_cfg.IFMarker, IDBM_SHOW, + num, _CAN_MODIFY); + _rec_bool(CONN_OFMARKER, recs, node, conn.op_cfg.OFMarker, IDBM_SHOW, + num, _CAN_MODIFY); +} + +int _idbm_node_get(struct iscsi_context *ctx, const char *target_name, + const char *portal, const char *iface_name, + struct iscsi_node **node) +{ + int rc = LIBISCSI_OK; + char *conf_path = NULL; + struct idbm_rec *recs = NULL; + + assert(node != NULL); + assert(ctx != NULL); + + *node = NULL; + + if ((target_name == NULL) || (portal == NULL)) + goto out; + + if (iface_name == NULL) // old style of config + _good(_asprintf(&conf_path, "%s/%s/%s", NODE_CONFIG_DIR, + target_name, portal), rc, out); + else + _good(_asprintf(&conf_path, "%s/%s/%s/%s", NODE_CONFIG_DIR, + target_name, portal, iface_name), rc, out); + + *node = calloc(1, sizeof(struct iscsi_node)); + _alloc_null_check(ctx, *node, rc, out); + + _default_node(*node); + + recs = _idbm_recs_alloc(); + _alloc_null_check(ctx, recs, rc, out); + + _idbm_node_rec_link(*node, recs); + + _good(_idbm_recs_read(ctx, recs, conf_path), rc, out); + + if (! _iface_is_valid(&((*node)->iface))) { + _warn(ctx, "'%s' has invalid iSCSI interface configuration", + conf_path); + iscsi_node_free(*node); + *node = NULL; + /* We still treat this as pass(no error) */ + goto out; + } + + // Add extra properties + if (strchr((*node)->conn.address, '.')) { + (*node)->conn.is_ipv6 = false; + snprintf((*node)->portal, sizeof((*node)->portal)/sizeof(char), + "%s:%" PRIi32, (*node)->conn.address, + (*node)->conn.port); + + } else { + (*node)->conn.is_ipv6 = true; + snprintf((*node)->portal, sizeof((*node)->portal)/sizeof(char), + "[%s]:%" PRIi32, (*node)->conn.address, + (*node)->conn.port); + } + + + +out: + if (rc != LIBISCSI_OK) { + iscsi_node_free(*node); + *node = NULL; + } + free(conf_path); + _idbm_recs_free(recs); + return rc; +} diff --git a/libopeniscsiusr/idbm.h b/libopeniscsiusr/idbm.h new file mode 100644 index 0000000..3fd0864 --- /dev/null +++ b/libopeniscsiusr/idbm.h @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2017-2018 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE /* For NI_MAXHOST */ +#endif + +#ifndef __ISCSI_OPEN_USR_IDBM_H__ +#define __ISCSI_OPEN_USR_IDBM_H__ + +#include +#include +#include + +#include "libopeniscsiusr/libopeniscsiusr_common.h" + +#define ISCSI_CONFIG_ROOT "/etc/iscsi/" +#define IFACE_CONFIG_DIR ISCSI_CONFIG_ROOT"ifaces" +#define AUTH_STR_MAX_LEN 256 +#define BOOT_NAME_MAXLEN 256 +#define IDBM_DUMP_SIZE 8192 + + +struct __DLL_LOCAL idbm; + +struct idbm { + int refs; +}; + +enum iscsi_auth_method { + ISCSI_AUTH_METHOD_NONE, + ISCSI_AUTH_METHOD_CHAP, +}; + +enum iscsi_startup_type { + ISCSI_STARTUP_MANUAL, + ISCSI_STARTUP_AUTOMATIC, + ISCSI_STARTUP_ONBOOT, +}; + +enum discovery_type { + DISCOVERY_TYPE_SENDTARGETS, + DISCOVERY_TYPE_ISNS, + DISCOVERY_TYPE_OFFLOAD_SENDTARGETS, + DISCOVERY_TYPE_SLP, + DISCOVERY_TYPE_STATIC, + DISCOVERY_TYPE_FW, +}; + +enum leading_login_type { + LEADING_LOGIN_NO, + LEADING_LOGIN_YES, +}; + +enum init_scan_type { + INIT_SCAN_MANUAL, + INIT_SCAN_AUTO, +}; + +enum digest_type { + DIGEST_NEVER, + DIGEST_ALWAYS, + DIGEST_PREFER_ON, + DIGEST_PREFER_OFF, +}; + +/* all authentication-related options should be added to this structure. + * this structure is per-session, and can be configured + * by TargetName but not Subnet. + */ +struct iscsi_auth_config { + enum iscsi_auth_method authmethod; + char username[AUTH_STR_MAX_LEN]; + unsigned char password[AUTH_STR_MAX_LEN]; + uint32_t password_length; + char username_in[AUTH_STR_MAX_LEN]; + unsigned char password_in[AUTH_STR_MAX_LEN]; + uint32_t password_in_length; +}; + +/* all TCP options go in this structure. + * this structure is per-portal, and can be configured + * both by TargetName and Subnet. + */ +struct iscsi_tcp_config { + int64_t window_size; + int64_t type_of_service; + /* ^ try to set IP TOS bits */ +}; + +/* all per-session timeouts go in this structure. + * this structure is per-session, and can be configured + * by TargetName but not by Subnet. + */ +struct iscsi_session_tmo_cfg { + int64_t replacement_timeout; +}; + +/* all error handling timeouts go in this structure. + * this structure is per-portal, and can be configured + * both by TargetName and Subnet. + */ +struct iscsi_error_tmo_cfg { + int64_t abort_timeout; + int64_t host_reset_timeout; + int64_t lu_reset_timeout; + int64_t tgt_reset_timeout; +}; + +/* all per-connection timeouts go in this structure. + * this structure is per-portal, and can be configured + * both by TargetName and Subnet. + */ +struct iscsi_conn_tmo_cfg { + int64_t login_timeout; + int64_t logout_timeout; + int64_t auth_timeout; + int64_t active_timeout; + int64_t noop_out_interval; + int64_t noop_out_timeout; +}; + +struct iscsi_conn_op_cfg { + int64_t MaxRecvDataSegmentLength; + int64_t MaxXmitDataSegmentLength; + enum digest_type HeaderDigest; + enum digest_type DataDigest; + bool IFMarker; + bool OFMarker; +}; + +struct iscsi_conn { + enum iscsi_startup_type startup; + char address[NI_MAXHOST]; + int32_t port; + struct iscsi_tcp_config tcp; + struct iscsi_conn_tmo_cfg tmo; + struct iscsi_conn_op_cfg op_cfg; + bool is_ipv6; +}; + +/* all iSCSI operational params go in this structure. + * this structure is per-portal, and can be configured + * both by TargetName and Subnet. + */ +struct iscsi_session_op_cfg { + int64_t DataPDUInOrder; + int64_t DataSequenceInOrder; + int64_t protocol; + bool InitialR2T; + bool ImmediateData; + int64_t FirstBurstLength; + int64_t MaxBurstLength; + int64_t DefaultTime2Wait; + int64_t DefaultTime2Retain; + int64_t MaxConnections; + int64_t MaxOutstandingR2T; + int64_t ERL; + bool FastAbort; +}; + +struct iscsi_session_idbm { + uint32_t initial_cmdsn; + int64_t reopen_max; + int64_t initial_login_retry_max; + int64_t xmit_thread_priority; + uint16_t cmds_max; + uint16_t queue_depth; + int64_t nr_sessions; + enum init_scan_type scan; + struct iscsi_auth_config auth; + struct iscsi_session_tmo_cfg tmo; + struct iscsi_error_tmo_cfg err_tmo; + struct iscsi_session_op_cfg op_cfg; + struct iscsi_session *se; + uint32_t sid; + /* + * This is a flag passed to iscsid. If set, multiple sessions are + * allowed to be initiated on this record + */ + unsigned char multiple; + char boot_root[BOOT_NAME_MAXLEN]; + char boot_nic[BOOT_NAME_MAXLEN]; + char boot_target[BOOT_NAME_MAXLEN]; + +}; + +__DLL_LOCAL struct idbm *_idbm_new(void); +__DLL_LOCAL void _idbm_free(struct idbm *db); +__DLL_LOCAL int _idbm_lock(struct iscsi_context *ctx); +__DLL_LOCAL void _idbm_unlock(struct iscsi_context *ctx); +__DLL_LOCAL void _idbm_iface_print(struct iscsi_iface *iface, FILE *f); +__DLL_LOCAL int _idbm_iface_get(struct iscsi_context *ctx, + const char *iface_name, + struct iscsi_iface **iface); +__DLL_LOCAL int _idbm_node_get(struct iscsi_context *ctx, + const char *target_name, + const char *portal, + const char *iface_name, + struct iscsi_node **node); +__DLL_LOCAL void _idbm_node_print(struct iscsi_node *node, FILE *f, + bool show_secret); + +#endif /* End of __ISCSI_OPEN_USR_IDBM_H__ */ diff --git a/libopeniscsiusr/idbm_fields.h b/libopeniscsiusr/idbm_fields.h new file mode 100644 index 0000000..29a2090 --- /dev/null +++ b/libopeniscsiusr/idbm_fields.h @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2018 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ + +#ifndef __ISCSI_OPEN_USER_IDBM_FIELDS_H +#define __ISCSI_OPEN_USER_IDBM_FIELDS_H + +/* iface fields */ +#define IFACE_HWADDR "iface.hwaddress" +#define IFACE_ISCSINAME "iface.iscsi_ifacename" +#define IFACE_NETNAME "iface.net_ifacename" +#define IFACE_TRANSPORTNAME "iface.transport_name" +#define IFACE_INAME "iface.initiatorname" +#define IFACE_ISID "iface.isid" +#define IFACE_BOOT_PROTO "iface.bootproto" +#define IFACE_IPADDR "iface.ipaddress" +#define IFACE_PREFIX_LEN "iface.prefix_len" +#define IFACE_SUBNET_MASK "iface.subnet_mask" +#define IFACE_GATEWAY "iface.gateway" +#define IFACE_PRIMARY_DNS "iface.primary_dns" +#define IFACE_SEC_DNS "iface.secondary_dns" +#define IFACE_VLAN_ID "iface.vlan_id" +#define IFACE_VLAN_PRIORITY "iface.vlan_priority" +#define IFACE_VLAN_STATE "iface.vlan_state" +#define IFACE_LINKLOCAL "iface.ipv6_linklocal" +#define IFACE_ROUTER "iface.ipv6_router" +#define IFACE_IPV6_AUTOCFG "iface.ipv6_autocfg" +#define IFACE_LINKLOCAL_AUTOCFG "iface.linklocal_autocfg" +#define IFACE_ROUTER_AUTOCFG "iface.router_autocfg" +#define IFACE_STATE "iface.state" +#define IFACE_NUM "iface.iface_num" +#define IFACE_MTU "iface.mtu" +#define IFACE_PORT "iface.port" +#define IFACE_DELAYED_ACK "iface.delayed_ack" +#define IFACE_TCP_NAGLE "iface.tcp_nagle" +#define IFACE_TCP_WSF_STATE "iface.tcp_wsf_state" +#define IFACE_TCP_WSF "iface.tcp_wsf" +#define IFACE_TCP_TIMER_SCALE "iface.tcp_timer_scale" +#define IFACE_TCP_TIMESTAMP "iface.tcp_timestamp" +#define IFACE_DHCP_DNS "iface.dhcp_dns" +#define IFACE_DHCP_SLP_DA "iface.dhcp_slp_da" +#define IFACE_TOS_STATE "iface.tos_state" +#define IFACE_TOS "iface.tos" +#define IFACE_GRAT_ARP "iface.gratuitous_arp" +#define IFACE_DHCP_ALT_CID "iface.dhcp_alt_client_id_state" +#define IFACE_DHCP_ALT_CID_STR "iface.dhcp_alt_client_id" +#define IFACE_DHCP_REQ_VID "iface.dhcp_req_vendor_id_state" +#define IFACE_DHCP_VID "iface.dhcp_vendor_id_state" +#define IFACE_DHCP_VID_STR "iface.dhcp_vendor_id" +#define IFACE_DHCP_LEARN_IQN "iface.dhcp_learn_iqn" +#define IFACE_FRAGMENTATION "iface.fragmentation" +#define IFACE_IN_FORWARD "iface.incoming_forwarding" +#define IFACE_TTL "iface.ttl" +#define IFACE_GRAT_NEIGHBOR_ADV "iface.gratuitous_neighbor_adv" +#define IFACE_REDIRECT "iface.redirect" +#define IFACE_IGNORE_ICMP_ECHO_REQ "iface.ignore_icmp_echo_request" +#define IFACE_MLD "iface.mld" +#define IFACE_FLOW_LABEL "iface.flow_label" +#define IFACE_TRAFFIC_CLASS "iface.traffic_class" +#define IFACE_HOP_LIMIT "iface.hop_limit" +#define IFACE_ND_REACHABLE_TMO "iface.nd_reachable_tmo" +#define IFACE_ND_REXMIT_TIME "iface.nd_rexmit_time" +#define IFACE_ND_STALE_TMO "iface.nd_stale_tmo" +#define IFACE_DUP_ADDR_DETECT_CNT "iface.dup_addr_detect_cnt" +#define IFACE_RTR_ADV_LINK_MTU "iface.router_adv_link_mtu" +#define IFACE_DEF_TMF_TMO "iface.def_task_mgmt_timeout" +#define IFACE_HDRDGST "iface.header_digest" +#define IFACE_DATADGST "iface.data_digest" +#define IFACE_IMM_DATA "iface.immediate_data" +#define IFACE_INITIAL_R2T "iface.initial_r2t" +#define IFACE_DSEQ_INORDER "iface.data_seq_inorder" +#define IFACE_DPDU_INORDER "iface.data_pdu_inorder" +#define IFACE_ERL "iface.erl" +#define IFACE_MAX_RECV_DLEN "iface.max_receive_data_len" +#define IFACE_FIRST_BURST "iface.first_burst_len" +#define IFACE_MAX_R2T "iface.max_outstanding_r2t" +#define IFACE_MAX_BURST "iface.max_burst_len" +#define IFACE_CHAP_AUTH "iface.chap_auth" +#define IFACE_BIDI_CHAP "iface.bidi_chap" +#define IFACE_STRICT_LOGIN_COMP "iface.strict_login_compliance" +#define IFACE_DISCOVERY_AUTH "iface.discovery_auth" +#define IFACE_DISCOVERY_LOGOUT "iface.discovery_logout" + +/* node fields */ +#define NODE_NAME "node.name" +#define NODE_TPGT "node.tpgt" +#define NODE_STARTUP "node.startup" +#define NODE_LEADING_LOGIN "node.leading_login" +#define NODE_DISC_ADDR "node.discovery_address" +#define NODE_DISC_PORT "node.discovery_port" +#define NODE_DISC_TYPE "node.discovery_type" +#define NODE_BOOT_LUN "node.boot_lun" + +/* session fields */ +#define SESSION_INIT_CMDSN "node.session.initial_cmdsn" +#define SESSION_INIT_LOGIN_RETRY "node.session.initial_login_retry_max" +#define SESSION_CMDS_MAX "node.session.cmds_max" +#define SESSION_XMIT_THREAD_PRIORITY "node.session.xmit_thread_priority" +#define SESSION_QDEPTH "node.session.queue_depth" +#define SESSION_NR_SESSIONS "node.session.nr_sessions" +#define SESSION_AUTH_METHOD "node.session.auth.authmethod" +#define SESSION_USERNAME "node.session.auth.username" +#define SESSION_PASSWORD "node.session.auth.password" +#define SESSION_PASSWORD_LEN "node.session.auth.password_length" +#define SESSION_USERNAME_IN "node.session.auth.username_in" +#define SESSION_PASSWORD_IN "node.session.auth.password_in" +#define SESSION_PASSWORD_IN_LEN "node.session.auth.password_in_length" +#define SESSION_REPLACEMENT_TMO "node.session.timeo.replacement_timeout" +#define SESSION_ABORT_TMO "node.session.err_timeo.abort_timeout" +#define SESSION_LU_RESET_TMO "node.session.err_timeo.lu_reset_timeout" +#define SESSION_TGT_RESET_TMO "node.session.err_timeo.tgt_reset_timeout" +#define SESSION_HOST_RESET_TMO "node.session.err_timeo.host_reset_timeout" +#define SESSION_FAST_ABORT "node.session.iscsi.FastAbort" +#define SESSION_INITIAL_R2T "node.session.iscsi.InitialR2T" +#define SESSION_IMM_DATA "node.session.iscsi.ImmediateData" +#define SESSION_FIRST_BURST "node.session.iscsi.FirstBurstLength" +#define SESSION_MAX_BURST "node.session.iscsi.MaxBurstLength" +#define SESSION_DEF_TIME2RETAIN "node.session.iscsi.DefaultTime2Retain" +#define SESSION_DEF_TIME2WAIT "node.session.iscsi.DefaultTime2Wait" +#define SESSION_MAX_CONNS "node.session.iscsi.MaxConnections" +#define SESSION_MAX_R2T "node.session.iscsi.MaxOutstandingR2T" +#define SESSION_ERL "node.session.iscsi.ERL" +#define SESSION_SCAN "node.session.scan" +#define SESSION_REOPEN_MAX "node.session.reopen_max" + +/* connections fields */ +#define CONN_ADDR "node.conn[0].address" +#define CONN_PORT "node.conn[0].port" +#define CONN_STARTUP "node.conn[0].startup" +#define CONN_WINDOW_SIZE "node.conn[0].tcp.window_size" +#define CONN_SERVICE_TYPE "node.conn[0].tcp.type_of_service" +#define CONN_LOGOUT_TMO "node.conn[0].timeo.logout_timeout" +#define CONN_LOGIN_TMO "node.conn[0].timeo.login_timeout" +#define CONN_AUTH_TMO "node.conn[0].timeo.auth_timeout" +#define CONN_NOP_INT "node.conn[0].timeo.noop_out_interval" +#define CONN_NOP_TMO "node.conn[0].timeo.noop_out_timeout" +#define CONN_MAX_XMIT_DLEN "node.conn[0].iscsi.MaxXmitDataSegmentLength" +#define CONN_MAX_RECV_DLEN "node.conn[0].iscsi.MaxRecvDataSegmentLength" +#define CONN_HDR_DIGEST "node.conn[0].iscsi.HeaderDigest" +#define CONN_DATA_DIGEST "node.conn[0].iscsi.DataDigest" +#define CONN_IFMARKER "node.conn[0].iscsi.IFMarker" +#define CONN_OFMARKER "node.conn[0].iscsi.OFMarker" + +#endif /* End of __ISCSI_OPEN_USER_IDBM_FIELDS_H */ diff --git a/libopeniscsiusr/iface.c b/libopeniscsiusr/iface.c new file mode 100644 index 0000000..e7938a5 --- /dev/null +++ b/libopeniscsiusr/iface.c @@ -0,0 +1,920 @@ +/* + * Copyright (C) 2017-2018 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE /* For NI_MAXHOST */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libopeniscsiusr/libopeniscsiusr.h" +#include "misc.h" +#include "sysfs.h" +#include "iface.h" +#include "context.h" +#include "idbm.h" +#include "default.h" + +#define ISCSIUIO_PATH "/sbin/iscsiuio" + +struct _iscsi_net_drv { + const char *net_driver_name; // Ethernet driver. + const char *iscsi_driver_name; // iSCSI offload driver. + const char *transport_name; // iSCSI transport name. +}; + +static struct _iscsi_net_drv _ISCSI_NET_DRVS[] = { + {"cxgb3", "cxgb3i", "cxgb3i"}, + {"cxgb4", "cxgb4i", "cxgb4i"}, + {"bnx2", "bnx2i" , "bnx2i"}, + {"bnx2x", "bnx2i", "bnx2i"}, +}; + +const struct iscsi_iface _DEFAULT_IFACES[] = { + { + .name = "default", + .transport_name = "tcp", + }, + { + .name = "iser", + .transport_name = "iser", + }, +}; + +static int _load_kernel_module(struct iscsi_context *ctx, const char *drv_name); +static int _iface_conf_write(struct iscsi_context *ctx, + struct iscsi_iface *iface); +static int _fill_hw_iface_from_sys(struct iscsi_context *ctx, + struct iscsi_iface *iface, + const char *iface_kern_id); + +_iscsi_getter_func_gen(iscsi_iface, hwaddress, const char *); +_iscsi_getter_func_gen(iscsi_iface, transport_name, const char *); +_iscsi_getter_func_gen(iscsi_iface, ipaddress, const char *); +_iscsi_getter_func_gen(iscsi_iface, netdev, const char *); +_iscsi_getter_func_gen(iscsi_iface, iname, const char *); +_iscsi_getter_func_gen(iscsi_iface, port_state, const char *); +_iscsi_getter_func_gen(iscsi_iface, port_speed, const char *); +_iscsi_getter_func_gen(iscsi_iface, name, const char *); + +int _iscsi_iface_get_from_sysfs(struct iscsi_context *ctx, uint32_t host_id, + uint32_t sid, char *iface_kern_id, + struct iscsi_iface **iface) +{ + int rc = LIBISCSI_OK; + char *sysfs_se_dir_path = NULL; + char *sysfs_sh_dir_path = NULL; + char *sysfs_scsi_host_dir_path = NULL; + char proc_name[ISCSI_TRANSPORT_NAME_MAXLEN]; + struct iscsi_iface **ifaces = NULL; + uint32_t iface_count = 0; + uint32_t i = 0; + struct iscsi_iface *tmp_iface = NULL; + bool bound_by_hwaddr = false; + bool bound_by_netdev = false; + bool matched = false; + + assert(ctx != NULL); + assert(iface != NULL); + + *iface = NULL; + + if (sid != 0) { + _good(_asprintf(&sysfs_se_dir_path, "%s/session%" PRIu32, + _ISCSI_SYS_SESSION_DIR, sid), rc, out); + } + + _good(_asprintf(&sysfs_sh_dir_path, "%s/host%" PRIu32, + _ISCSI_SYS_HOST_DIR, host_id),rc, out); + + _good(_asprintf(&sysfs_scsi_host_dir_path, "%s/host%" PRIu32, + _SCSI_SYS_HOST_DIR, host_id), rc, out); + + *iface = (struct iscsi_iface *) calloc(1, sizeof(struct iscsi_iface)); + _alloc_null_check(ctx, *iface, rc, out); + + _good(_sysfs_prop_get_str(ctx, sysfs_scsi_host_dir_path, "proc_name", + proc_name, sizeof(proc_name) / sizeof(char), + NULL /* raise error if failed */), + rc, out); + + if (strncmp(proc_name, "iscsi_", strlen("iscsi_")) == 0) + _strncpy((*iface)->transport_name, proc_name + strlen("iscsi_"), + sizeof((*iface)->transport_name) / sizeof(char)); + else + _strncpy((*iface)->transport_name, proc_name, + sizeof((*iface)->transport_name) / sizeof(char)); + + _good(_sysfs_prop_get_str(ctx, sysfs_sh_dir_path, "hwaddress", + (*iface)->hwaddress, + sizeof((*iface)->hwaddress) / sizeof(char), + DEFAULT_HWADDRESS), + rc, out); + if (strcmp((*iface)->hwaddress, DEFAULT_HWADDRESS) != 0) + bound_by_hwaddr = true; + + _good(_sysfs_prop_get_str(ctx, sysfs_sh_dir_path, "netdev", + (*iface)->netdev, + sizeof((*iface)->netdev) / sizeof(char), + DEFAULT_NETDEV), + rc, out); + if (strcmp((*iface)->netdev, DEFAULT_NETDEV) != 0) + bound_by_netdev = true; + + if (sysfs_se_dir_path) + _sysfs_prop_get_str(ctx, sysfs_se_dir_path, "initiatorname", + (*iface)->iname, + sizeof((*iface)->iname) / sizeof(char), ""); + if (strcmp((*iface)->iname, "") == 0) + _good(_sysfs_prop_get_str(ctx, sysfs_sh_dir_path, + "initiatorname", (*iface)->iname, + sizeof((*iface)->iname) / + sizeof(char), ""), + rc, out); + + _good(_sysfs_prop_get_str(ctx, sysfs_sh_dir_path, "port_state", + (*iface)->port_state, + sizeof((*iface)->port_state) / sizeof(char), + "unknown"), + rc, out); + + if (strcmp((*iface)->port_state, "Unknown!") == 0) + _strncpy((*iface)->port_state, "unknown", + sizeof((*iface)->port_state) / sizeof(char)); + + _good(_sysfs_prop_get_str(ctx, sysfs_sh_dir_path, "port_speed", + (*iface)->port_speed, + sizeof((*iface)->port_speed) / sizeof(char), + "unknown"), + rc, out); + + if (strncmp((*iface)->port_speed, "Unknown", strlen("Unknown")) == 0) + _strncpy((*iface)->port_speed, "unknown", + sizeof((*iface)->port_speed) / sizeof(char)); + + if (sysfs_se_dir_path != NULL) + _sysfs_prop_get_str(ctx, sysfs_se_dir_path, "ifacename", + (*iface)->name, + sizeof((*iface)->name)/sizeof(char), ""); + + if (iface_kern_id != NULL) { + _good(_fill_hw_iface_from_sys(ctx, *iface, iface_kern_id), + rc, out); + } else { + _good(_sysfs_prop_get_str(ctx, sysfs_sh_dir_path, "ipaddress", + (*iface)->ipaddress, + sizeof((*iface)->ipaddress) / + sizeof(char), DEFAULT_IPADDRESS), + rc, out); + /* bnx2i does not create + * /sys/class/iscsi_iface/ + * We need to use transport_name.hwaddress as iface name. + */ + _debug(ctx, "HAHA: hwaddress %s", (*iface)->hwaddress); + if (bound_by_hwaddr) + snprintf((*iface)->name, + sizeof((*iface)->name)/sizeof(char), + "%s.%s", (*iface)->transport_name, + (*iface)->hwaddress); + } + + if (strcmp((*iface)->name, "") == 0) { + /* + * Before 2.0.870, we only could bind by netdeivce or hwaddress, + * so we did a simple reverse lookup to go from sysfs info to + * the iface name. After 2.0.870 we added a lot of options to + * the iface binding so we added the ifacename to the kernel. + * + * Below codes are for older kernels that do not export the + * ifacename. If the user was doing iscsi_tcp session binding + * we will find the iface by matching net info. + */ + + _good(iscsi_ifaces_get(ctx, &ifaces, &iface_count), rc, out); + + for (i = 0; i < iface_count; ++i) { + tmp_iface = ifaces[i]; + if ((bound_by_hwaddr == true) && + (strcmp(tmp_iface->hwaddress, (*iface)->hwaddress) + == 0)) { + _strncpy((*iface)->name, tmp_iface->name, + sizeof((*iface)->name)/sizeof(char)); + matched = true; + break; + } + if ((bound_by_netdev == true) && + (strcmp(tmp_iface->netdev, (*iface)->netdev) + == 0)) { + _strncpy((*iface)->name, tmp_iface->name, + sizeof((*iface)->name)/sizeof(char)); + matched = true; + break; + } + } + if (!matched) + _strncpy((*iface)->name, DEFAULT_IFACENAME, + sizeof((*iface)->name) / sizeof(char)); + } + +out: + if (rc != LIBISCSI_OK) { + iscsi_iface_free(*iface); + *iface = NULL; + } + free(sysfs_se_dir_path); + free(sysfs_sh_dir_path); + free(sysfs_scsi_host_dir_path); + iscsi_ifaces_free(ifaces, iface_count); + return rc; +} + +/* create all ifaces for a host from sysfs */ +int _iscsi_ifaces_get_from_sysfs(struct iscsi_context *ctx, uint32_t host_id, + struct iscsi_iface ***ifaces, uint32_t *iface_count) +{ + int rc = LIBISCSI_OK; + char **iface_kern_ids = NULL; + uint32_t i = 0; + + assert(ctx != NULL); + assert(ifaces != NULL); + + *ifaces = NULL; + *iface_count = 0; + + _good(_iscsi_iface_kern_ids_of_host_id(ctx, host_id, &iface_kern_ids, iface_count), + rc, out); + if (*iface_count > 0) { + *ifaces = (struct iscsi_iface **) calloc(*iface_count, + sizeof(struct iscsi_iface *)); + _alloc_null_check(ctx, *ifaces, rc, out); + for (i = 0; i < *iface_count; i++) { + _good(_iscsi_iface_get_from_sysfs(ctx, host_id, 0, + iface_kern_ids[i], &(*ifaces)[i]), rc, out); + } + } else { + /* if there's no iface exported in sysfs, + * we should still be able to create one record per host */ + *ifaces = (struct iscsi_iface **) calloc(1, sizeof(struct iscsi_iface *)); + _alloc_null_check(ctx, *ifaces, rc, out); + *iface_count = 1; + _good(_iscsi_iface_get_from_sysfs(ctx, host_id, 0, NULL, &(*ifaces)[0]), rc, out); + } +out: + if (iface_kern_ids != NULL) { + for (i = 0; i < *iface_count; i++) { + free(iface_kern_ids[i]); + } + free(iface_kern_ids); + } + if (rc != LIBISCSI_OK) { + iscsi_ifaces_free(*ifaces, *iface_count); + *ifaces = NULL; + *iface_count = 0; + } + return rc; +} + +int iscsi_default_iface_setup(struct iscsi_context *ctx) +{ + int rc = LIBISCSI_OK; + char strerr_buff[_STRERR_BUFF_LEN]; + int errno_save = 0; + struct _eth_if **eifs = NULL; + uint32_t eif_count = 0; + uint32_t i = 0; + uint32_t n = 0; + size_t j = 0; + struct _iscsi_net_drv *ind = NULL; + uint32_t *hids = NULL; + uint32_t hid_count = 0; + struct iscsi_iface **ifaces = NULL; + uint32_t iface_count = 0; + char *path = NULL; + + assert(ctx != NULL); + + _good(_idbm_lock(ctx), rc, out); + + if ((access(IFACE_CONFIG_DIR, F_OK) != 0) && + (mkdir(IFACE_CONFIG_DIR, 0660) != 0)) { + errno_save = errno; + _idbm_unlock(ctx); + _error(ctx, "Could not make %s folder(%d %s). " + "HW/OFFLOAD iscsi may not be supported.", + IFACE_CONFIG_DIR, errno_save, + _strerror(errno_save, strerr_buff)); + if (errno_save == EACCES) + return LIBISCSI_ERR_ACCESS; + return LIBISCSI_ERR_BUG; + } + _idbm_unlock(ctx); + + /* Load kernel driver for iSCSI offload cards, like cxgb3i */ + _good(_eth_ifs_get(ctx, &eifs, &eif_count), rc, out); + + for (i = 0; i < eif_count; ++i) { + for (j = 0; + j < sizeof(_ISCSI_NET_DRVS)/sizeof(struct _iscsi_net_drv); + ++j) { + ind = &(_ISCSI_NET_DRVS[j]); + if ((ind->net_driver_name == NULL) || + (strcmp(eifs[i]->driver_name, + ind->net_driver_name) != 0)) + continue; + /* + * iSCSI hardware offload for bnx2{,x} is only supported + * if the iscsiuio executable is available. + */ + if ((strcmp(eifs[i]->driver_name, "bnx2x") == 0) || + (strcmp(eifs[i]->driver_name, "bnx2") == 0)) { + if (access(ISCSIUIO_PATH, F_OK) != 0) { + _debug(ctx, "iSCSI offload on %s(%s) " + "via %s is not supported due to " + "missing %s", eifs[i]->if_name, + eifs[i]->driver_name, + ind->iscsi_driver_name, + ISCSIUIO_PATH); + continue; + } + } + + if (_iscsi_transport_is_loaded(ind->transport_name)) + continue; + + _debug(ctx, "Loading kernel module %s for iSCSI " + "offload on %s(%s)", ind->iscsi_driver_name, + eifs[i]->if_name, eifs[i]->driver_name); + _good(_load_kernel_module(ctx, ind->iscsi_driver_name), + rc, out); + } + } + + _good(_iscsi_hids_get(ctx, &hids, &hid_count), rc, out); + for (i = 0; i < hid_count; ++i) { + /* Create /etc/iscsi/ifaces/ file if not found + */ + _good(_iscsi_ifaces_get_from_sysfs(ctx, hids[i], &ifaces, &iface_count), + rc, out); + for (n = 0; n < iface_count; n++) { + if ( ! iscsi_is_default_iface(ifaces[n])) { + _good(_asprintf(&path, "%s/%s", IFACE_CONFIG_DIR, + ifaces[n]->name), rc, out); + if (access(path, F_OK) != 0) + rc = _iface_conf_write(ctx, ifaces[n]); + free(path); + path = NULL; + } + iscsi_iface_free(ifaces[n]); + ifaces[n] = NULL; + if (rc != LIBISCSI_OK) + goto out; + } + free(ifaces); + ifaces = NULL; + } + +out: + if (ifaces != NULL) { + for (i = 0; i < iface_count; i++) + free(ifaces[i]); + free(ifaces); + } + _eth_ifs_free(eifs, eif_count); + free(path); + free(hids); + return rc; +} + +static int _load_kernel_module(struct iscsi_context *ctx, const char *drv_name) +{ + struct kmod_ctx *kctx = NULL; + struct kmod_module *mod = NULL; + int rc = LIBISCSI_OK; + + kctx = kmod_new(NULL, NULL); + _alloc_null_check(ctx, kctx, rc, out); + + kmod_load_resources(kctx); + + if (kmod_module_new_from_name(kctx, drv_name, &mod)) { + _error(ctx, "Failed to load module %s.", drv_name); + rc = LIBISCSI_ERR_TRANS_NOT_FOUND; + goto out; + } + + if (kmod_module_probe_insert_module(mod, KMOD_PROBE_APPLY_BLACKLIST, + NULL, NULL, NULL, NULL)) { + _error(ctx, "Could not insert module %s. Kmod error %d", + drv_name, rc); + rc = LIBISCSI_ERR_TRANS_NOT_FOUND; + } + kmod_module_unref(mod); + +out: + if (kctx != NULL) + kmod_unref(kctx); + return rc; +} + +static int _iface_conf_write(struct iscsi_context *ctx, + struct iscsi_iface *iface) +{ + char *conf_path = NULL; + char strerr_buff[_STRERR_BUFF_LEN]; + int errno_save = 0; + FILE *f = NULL; + int rc = 0; + + if (iscsi_is_default_iface(iface)) { + _error(ctx, "iface %s is not a special interface and " + "is not stored in %s", iface->name, IFACE_CONFIG_DIR); + return LIBISCSI_ERR_INVAL; + } + + _good(_idbm_lock(ctx), rc, out); + + _good(_asprintf(&conf_path, "%s/%s", IFACE_CONFIG_DIR, + iface->name), rc, out); + _debug(ctx, "Creating iSCSI interface configuration file '%s' " + "using kernel information", conf_path); + f = fopen(conf_path, "w"); + errno_save = errno; + if (!f) { + _error(ctx, "Failed to open %s using write mode: %d %s", + conf_path, errno_save, + _strerror(errno_save, strerr_buff)); + rc = LIBISCSI_ERR_IDBM; + goto out; + } + + _idbm_iface_print(iface, f); + + _idbm_unlock(ctx); + +out: + free(conf_path); + if (f != NULL) + fclose(f); + return rc; +} + +// mimic of iscsi_sysfs_read_iface() in iscsi_sysfs.c. +static int _fill_hw_iface_from_sys(struct iscsi_context *ctx, + struct iscsi_iface *iface, + const char *iface_kern_id) +{ + int rc = LIBISCSI_OK; + char *sysfs_iface_dir_path = NULL; + uint32_t tmp_host_no = 0; + uint32_t iface_num = 0; + int iface_type = 0; + + assert(ctx != NULL); + assert(iface != NULL); + assert(iface_kern_id != NULL); + + _good(_asprintf(&sysfs_iface_dir_path, "%s/%s", _ISCSI_SYS_IFACE_DIR, + iface_kern_id), rc, out); + + _good(_sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "ipaddress", + iface->ipaddress, + sizeof(iface->ipaddress) / + sizeof(char), DEFAULT_IPADDRESS), + rc, out); + + if (strncmp(iface_kern_id, "ipv4", strlen("ipv4")) == 0) { + iface->is_ipv6 = false; + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "bootproto", iface->bootproto, + sizeof(iface->bootproto) / sizeof(char), + ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "gateway", + iface->gateway, + sizeof(iface->gateway) / sizeof(char), + ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "subnet", + iface->subnet_mask, + sizeof(iface->subnet_mask) / sizeof(char), + ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "dhcp_alt_client_id_en", + iface->dhcp_alt_client_id, + sizeof(iface->dhcp_alt_client_id) / + sizeof(char), + ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "dhcp_alt_client_id", + iface->dhcp_alt_client_id, + sizeof(iface->dhcp_alt_client_id) / + sizeof(char), + ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "dhcp_dns_address_en", iface->dhcp_dns, + sizeof(iface->dhcp_dns) / sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "dhcp_learn_iqn_en", iface->dhcp_learn_iqn, + sizeof(iface->dhcp_learn_iqn) / + sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "dhcp_req_vendor_id_en", + iface->dhcp_req_vendor_id_state, + sizeof(iface->dhcp_req_vendor_id_state) / + sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "dhcp_use_vendor_id_en", + iface->dhcp_vendor_id_state, + sizeof(iface->dhcp_vendor_id_state) / + sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "dhcp_vendor_id", iface->dhcp_vendor_id, + sizeof(iface->dhcp_vendor_id) / + sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "dhcp_slp_da_info_en", iface->dhcp_slp_da, + sizeof(iface->dhcp_slp_da) / sizeof(char), + ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "fragment_disable", iface->fragmentation, + sizeof(iface->fragmentation) / sizeof(char), + ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "grat_arp_en", iface->gratuitous_arp, + sizeof(iface->gratuitous_arp) / + sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "incoming_forwarding_en", + iface->incoming_forwarding, + sizeof(iface->incoming_forwarding) / + sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "tos_en", + iface->tos_state, sizeof(iface->tos_state) / + sizeof(char), ""); + _sysfs_prop_get_u8(ctx, sysfs_iface_dir_path, "tos", + &iface->tos, 0, true); + _sysfs_prop_get_u8(ctx, sysfs_iface_dir_path, "ttl", + &iface->ttl, 0, true); + } else { + iface->is_ipv6 = true; + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "ipaddr_autocfg", + iface->ipv6_autocfg, + sizeof(iface->ipv6_autocfg) / sizeof(char), + ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "link_local_addr", iface->ipv6_linklocal, + sizeof(iface->ipv6_linklocal) / + sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "link_local_autocfg", + iface->linklocal_autocfg, + sizeof(iface->linklocal_autocfg) / + sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "router_addr", iface->ipv6_router, + sizeof(iface->ipv6_router) / sizeof(char), + ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "router_state", iface->router_autocfg, + sizeof(iface->router_autocfg) / + sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "grat_neighbor_adv_en", + iface->gratuitous_neighbor_adv, + sizeof(iface->gratuitous_neighbor_adv) / + sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "mld_en", + iface->mld, sizeof(iface->mld) / + sizeof(char), ""); + _sysfs_prop_get_u8(ctx, sysfs_iface_dir_path, + "dup_addr_detect_cnt", + &iface->dup_addr_detect_cnt, 0, true); + _sysfs_prop_get_u8(ctx, sysfs_iface_dir_path, "hop_limit", + &iface->hop_limit, 0, true); + _sysfs_prop_get_u32(ctx, sysfs_iface_dir_path, + "flow_label", &iface->flow_label, 0, true); + _sysfs_prop_get_u32(ctx, sysfs_iface_dir_path, + "nd_reachable_tmo", + &iface->nd_reachable_tmo, 0, true); + _sysfs_prop_get_u32(ctx, sysfs_iface_dir_path, "nd_rexmit_time", + &iface->nd_rexmit_time, 0, true); + _sysfs_prop_get_u32(ctx, sysfs_iface_dir_path, "nd_stale_tmo", + &iface->nd_stale_tmo, 0, true); + _sysfs_prop_get_u32(ctx, sysfs_iface_dir_path, + "router_adv_link_mtu", + &iface->router_adv_link_mtu, 0, true); + _sysfs_prop_get_u32(ctx, sysfs_iface_dir_path, "traffic_class", + &iface->traffic_class, 0, true); + } + + _sysfs_prop_get_u16(ctx, sysfs_iface_dir_path, "port", &iface->port, 0, + true); + _sysfs_prop_get_u16(ctx, sysfs_iface_dir_path, "mtu", &iface->mtu, 0, + true); + _sysfs_prop_get_u16(ctx, sysfs_iface_dir_path, "vlan_id", + &iface->vlan_id, UINT16_MAX, true); + _sysfs_prop_get_u8(ctx, sysfs_iface_dir_path, "vlan_priority", + &iface->vlan_priority, UINT8_MAX, true); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "vlan_enabled", + iface->vlan_state, sizeof(iface->vlan_state) / + sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "enabled", iface->state, + sizeof(iface->state) / sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "delayed_ack_en", + iface->delayed_ack, + sizeof(iface->delayed_ack) / sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "tcp_nagle_disable", + iface->nagle, sizeof(iface->nagle) / sizeof(char), + ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "tcp_wsf_disable", + iface->tcp_wsf_state, + sizeof(iface->tcp_wsf_state) / sizeof(char), ""); + _sysfs_prop_get_u8(ctx, sysfs_iface_dir_path, "tcp_wsf", + &iface->tcp_wsf, 0, true); + _sysfs_prop_get_u8(ctx, sysfs_iface_dir_path, "tcp_timer_scale", + &iface->tcp_timer_scale, 0, true); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "tcp_timestamp_en", + iface->tcp_timestamp, + sizeof(iface->tcp_timestamp) / sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "redirect_en", + iface->redirect, + sizeof(iface->redirect) / sizeof(char), ""); + _sysfs_prop_get_u16(ctx, sysfs_iface_dir_path, "def_taskmgmt_tmo", + &iface->def_task_mgmt_tmo, 0, true); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "header_digest", + iface->header_digest, + sizeof(iface->header_digest) / sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "data_digest", + iface->data_digest, + sizeof(iface->data_digest) / sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "immediate_data", + iface->immediate_data, + sizeof(iface->immediate_data) / sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "initial_r2t", + iface->initial_r2t, + sizeof(iface->initial_r2t) / sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "data_seq_in_order", + iface->data_seq_inorder, + sizeof(iface->data_seq_inorder) / sizeof(char), + ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "data_pdu_in_order", + iface->data_pdu_inorder, + sizeof(iface->data_pdu_inorder) / sizeof(char), ""); + _sysfs_prop_get_u8(ctx, sysfs_iface_dir_path, "erl", &iface->erl, 0, + true); + _sysfs_prop_get_u32(ctx, sysfs_iface_dir_path, "max_recv_dlength", + &iface->max_recv_dlength, 0, true); + _sysfs_prop_get_u32(ctx, sysfs_iface_dir_path, "first_burst_len", + &iface->first_burst_len, 0, true); + _sysfs_prop_get_u16(ctx, sysfs_iface_dir_path, "max_outstanding_r2t", + &iface->max_out_r2t, 0, true); + _sysfs_prop_get_u32(ctx, sysfs_iface_dir_path, "max_burst_len", + &iface->max_burst_len, 0, true); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "chap_auth", + iface->chap_auth, + sizeof(iface->chap_auth) / sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "bidi_chap", + iface->bidi_chap, + sizeof(iface->bidi_chap) / sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, "strict_login_comp_en", + iface->strict_login_comp, + sizeof(iface->strict_login_comp) / sizeof(char), + ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "discovery_auth_optional", + iface->discovery_auth, + sizeof(iface->discovery_auth) / sizeof(char), ""); + _sysfs_prop_get_str(ctx, sysfs_iface_dir_path, + "discovery_logout", + iface->discovery_logout, + sizeof(iface->discovery_logout) / sizeof(char), ""); + + if (sscanf(iface_kern_id, "ipv%d-iface-%" SCNu32 "-%" SCNu32, + &iface_type, &tmp_host_no, &iface_num) == 3) + iface->iface_num = iface_num; + + snprintf(iface->name, sizeof(iface->name)/sizeof(char), + "%s.%s.%s.%u", iface->transport_name, + iface->hwaddress, iface->is_ipv6 ? "ipv6" : "ipv4", + iface->iface_num); + +out: + free(sysfs_iface_dir_path); + return rc; +} + +int iscsi_ifaces_get(struct iscsi_context *ctx, struct iscsi_iface ***ifaces, + uint32_t *iface_count) +{ + int rc = LIBISCSI_OK; + struct dirent **namelist = NULL; + int n = 0; + size_t i = 0; + struct iscsi_iface *iface = NULL; + int j = 0; + uint32_t real_iface_count = 0; + + assert(ctx != NULL); + assert(ifaces != NULL); + assert(iface_count != NULL); + + *ifaces = NULL; + *iface_count = 0; + + _good(_idbm_lock(ctx), rc, out); + + _good(_scandir(ctx, IFACE_CONFIG_DIR, &namelist, &n), rc, out); + _debug(ctx, "Got %d iface from %s folder", n, IFACE_CONFIG_DIR); + *iface_count = (n + sizeof(_DEFAULT_IFACES)/sizeof(struct iscsi_iface)) + & UINT32_MAX; + *ifaces = (struct iscsi_iface **) calloc(*iface_count, + sizeof(struct iscsi_iface *)); + _alloc_null_check(ctx, *ifaces, rc, out); + + for (j = 0; j < n; ++j) { + _good(_idbm_iface_get(ctx, namelist[j]->d_name, &iface), + rc, out); + if (iface != NULL) { + (*ifaces)[real_iface_count++] = iface; + } + } + + for (i = 0; i < sizeof(_DEFAULT_IFACES)/sizeof(struct iscsi_iface); + ++i) { + iface = calloc(1, sizeof(struct iscsi_iface)); + _alloc_null_check(ctx, iface, rc, out); + (*ifaces)[real_iface_count++] = iface; + memcpy(iface, &_DEFAULT_IFACES[i], sizeof(struct iscsi_iface)); + } + + *iface_count = real_iface_count; + +out: + _scandir_free(namelist, n); + _idbm_unlock(ctx); + if (rc != LIBISCSI_OK) { + iscsi_ifaces_free(*ifaces, *iface_count); + *ifaces = NULL; + *iface_count = 0; + } + return rc; +} + +void iscsi_ifaces_free(struct iscsi_iface **ifaces, uint32_t iface_count) +{ + uint32_t i = 0; + + if ((ifaces == NULL) || (iface_count == 0)) + return; + + for (i = 0; i < iface_count; ++i) + iscsi_iface_free(ifaces[i]); + free (ifaces); +} + +static bool _iface_is_bound_by_hwaddr(struct iscsi_iface *iface) +{ + if (iface && strlen(iface->hwaddress) && + strcmp(iface->hwaddress, DEFAULT_HWADDRESS)) + return true; + return false; +} + +static bool _iface_is_bound_by_netdev(struct iscsi_iface *iface) +{ + if (iface && strlen(iface->netdev) && + strcmp(iface->netdev, DEFAULT_NETDEV)) + return true; + return false; +} + +bool _iface_is_valid(struct iscsi_iface *iface) +{ + if (!iface) + return false; + + if (strlen(iface->name) == 0) + return false; + + if (strlen(iface->transport_name) == 0) + return false; + + if (_iface_is_bound_by_hwaddr(iface)) + return true; + + if (_iface_is_bound_by_netdev(iface)) + return true; + + /* bound by transport name */ + return true; +} + +bool iscsi_is_default_iface(struct iscsi_iface *iface) +{ + size_t i = 0; + for (; i < sizeof(_DEFAULT_IFACES)/sizeof(struct iscsi_iface); ++i) { + if (strcmp(iface->name, _DEFAULT_IFACES[i].name) == 0) + return true; + } + return false; +} + +const char *iscsi_iface_dump_config(struct iscsi_iface *iface) +{ + FILE *f = NULL; + char *buff = NULL; + + assert(iface != NULL); + + buff = calloc(1, IDBM_DUMP_SIZE); + if (buff == NULL) + return NULL; + + f = fmemopen(buff, IDBM_DUMP_SIZE - 1, "w"); + if (f == NULL) { + free(buff); + return NULL; + } + + _idbm_iface_print(iface, f); + + fclose(f); + + return buff; +} + +void iscsi_iface_print_config(struct iscsi_iface *iface) +{ + assert(iface != NULL); + _idbm_iface_print(iface, stdout); +} + +int iscsi_iface_get(struct iscsi_context *ctx, const char *iface_name, + struct iscsi_iface **iface) +{ + int rc = LIBISCSI_OK; + assert(ctx != NULL); + assert(iface_name != NULL); + assert(strlen(iface_name) != 0); + assert(iface != NULL); + + *iface = NULL; + + size_t i = 0; + for (; i < sizeof(_DEFAULT_IFACES)/sizeof(struct iscsi_iface); ++i) { + if (strcmp(iface_name, _DEFAULT_IFACES[i].name) == 0) { + *iface = calloc(1, sizeof(struct iscsi_iface)); + _alloc_null_check(ctx, *iface, rc, out); + memcpy(*iface, &_DEFAULT_IFACES[i], + sizeof(struct iscsi_iface)); + goto out; + } + } + + rc = _idbm_lock(ctx); + if (rc != LIBISCSI_OK) + return rc; + + rc = _idbm_iface_get(ctx, iface_name, iface); + if (*iface == NULL) + rc = LIBISCSI_ERR_IDBM; + + _idbm_unlock(ctx); + +out: + return rc; +} + +void iscsi_iface_free(struct iscsi_iface *iface) +{ + free(iface); +} diff --git a/libopeniscsiusr/iface.h b/libopeniscsiusr/iface.h new file mode 100644 index 0000000..f20ea13 --- /dev/null +++ b/libopeniscsiusr/iface.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2017-2018 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ + +#ifndef __ISCSI_USR_IFACE_H__ +#define __ISCSI_USR_IFACE_H__ + +#include "libopeniscsiusr/libopeniscsiusr_common.h" +#include +#include +#include + +#define VALUE_MAXVAL 256 /* the maximum length of 223 bytes in the RFC. */ +/* ^ VALUE_MAXVAL is copied from usr/idbm.h + * The RFC 3720 only said: + * If not otherwise specified, the maximum length of a simple-value (not + * its encoded representation) is 255 bytes, not including the delimiter + * (comma or zero byte). + */ + +#define ISCSI_MAX_IFACE_LEN 65 +#define ISCSI_TRANSPORT_NAME_MAXLEN 16 +#define ISCSI_MAX_STR_LEN 80 +#define ISCSI_HWADDRESS_BUF_SIZE 18 +#define TARGET_NAME_MAXLEN VALUE_MAXVAL +/* ^ TODO(Gris Ge): Above 5 constants are copy from usr/config.h, need to + * verify them in linux kernel code + */ + +struct iscsi_iface { + /* iscsi iface record name */ + char name[ISCSI_MAX_IFACE_LEN]; + uint32_t iface_num; + /* network layer iface name (eth0) */ + char netdev[IFNAMSIZ]; + char ipaddress[NI_MAXHOST]; + char subnet_mask[NI_MAXHOST]; + char gateway[NI_MAXHOST]; + char bootproto[ISCSI_MAX_STR_LEN]; + char ipv6_linklocal[NI_MAXHOST]; + char ipv6_router[NI_MAXHOST]; + char ipv6_autocfg[NI_MAXHOST]; + char linklocal_autocfg[NI_MAXHOST]; + char router_autocfg[NI_MAXHOST]; + uint8_t prefix_len; + /* ^ prefix_len is removed, as linux kernel has no such sysfs property + * and there is no actual code in usr/ folder set this property + * + * Added back, we need to be backward compatible with iface records + * created by older tools. Look at fixing code to ignore in record + * files instead? - cleech + */ + uint16_t vlan_id; + uint8_t vlan_priority; + char vlan_state[ISCSI_MAX_STR_LEN]; + char state[ISCSI_MAX_STR_LEN]; /* 0 = disable, + * 1 = enable */ + uint16_t mtu; + uint16_t port; + char delayed_ack[ISCSI_MAX_STR_LEN]; + char nagle[ISCSI_MAX_STR_LEN]; + char tcp_wsf_state[ISCSI_MAX_STR_LEN]; + uint8_t tcp_wsf; + uint8_t tcp_timer_scale; + char tcp_timestamp[ISCSI_MAX_STR_LEN]; + char dhcp_dns[ISCSI_MAX_STR_LEN]; + char dhcp_slp_da[ISCSI_MAX_STR_LEN]; + char tos_state[ISCSI_MAX_STR_LEN]; + uint8_t tos; + char gratuitous_arp[ISCSI_MAX_STR_LEN]; + char dhcp_alt_client_id_state[ISCSI_MAX_STR_LEN]; + char dhcp_alt_client_id[ISCSI_MAX_STR_LEN]; + char dhcp_req_vendor_id_state[ISCSI_MAX_STR_LEN]; + char dhcp_vendor_id_state[ISCSI_MAX_STR_LEN]; + char dhcp_vendor_id[ISCSI_MAX_STR_LEN]; + char dhcp_learn_iqn[ISCSI_MAX_STR_LEN]; + char fragmentation[ISCSI_MAX_STR_LEN]; + char incoming_forwarding[ISCSI_MAX_STR_LEN]; + uint8_t ttl; + char gratuitous_neighbor_adv[ISCSI_MAX_STR_LEN]; + char redirect[ISCSI_MAX_STR_LEN]; + char mld[ISCSI_MAX_STR_LEN]; + uint32_t flow_label; + uint32_t traffic_class; + uint8_t hop_limit; + uint32_t nd_reachable_tmo; + uint32_t nd_rexmit_time; + uint32_t nd_stale_tmo; + uint8_t dup_addr_detect_cnt; + uint32_t router_adv_link_mtu; + uint16_t def_task_mgmt_tmo; + char header_digest[ISCSI_MAX_STR_LEN]; + char data_digest[ISCSI_MAX_STR_LEN]; + char immediate_data[ISCSI_MAX_STR_LEN]; + char initial_r2t[ISCSI_MAX_STR_LEN]; + char data_seq_inorder[ISCSI_MAX_STR_LEN]; + char data_pdu_inorder[ISCSI_MAX_STR_LEN]; + uint8_t erl; + uint32_t max_recv_dlength; + uint32_t first_burst_len; + uint16_t max_out_r2t; + uint32_t max_burst_len; + char chap_auth[ISCSI_MAX_STR_LEN]; + char bidi_chap[ISCSI_MAX_STR_LEN]; + char strict_login_comp[ISCSI_MAX_STR_LEN]; + char discovery_auth[ISCSI_MAX_STR_LEN]; + char discovery_logout[ISCSI_MAX_STR_LEN]; + char port_state[ISCSI_MAX_STR_LEN]; + char port_speed[ISCSI_MAX_STR_LEN]; + /* + * TODO: we may have to make this bigger and interconnect + * specific for infiniband + */ + char hwaddress[ISCSI_HWADDRESS_BUF_SIZE]; + char transport_name[ISCSI_TRANSPORT_NAME_MAXLEN]; + /* + * This is only used for boot now, but the iser guys + * can use this for their virtualization idea. + */ + char alias[TARGET_NAME_MAXLEN + 1]; + char iname[TARGET_NAME_MAXLEN + 1]; + bool is_ipv6; +}; + +__DLL_LOCAL int _iscsi_iface_get_from_sysfs(struct iscsi_context *ctx, + uint32_t host_id, uint32_t sid, + char *iface_kern_id, + struct iscsi_iface **iface); + +__DLL_LOCAL bool _iface_is_valid(struct iscsi_iface *iface); + +#endif /* End of __ISCSI_USR_IFACE_H__ */ diff --git a/libopeniscsiusr/libopeniscsiusr.pc.in b/libopeniscsiusr/libopeniscsiusr.pc.in new file mode 100644 index 0000000..f0fb583 --- /dev/null +++ b/libopeniscsiusr/libopeniscsiusr.pc.in @@ -0,0 +1,9 @@ +includedir=__INCLUDE_DIR__ +libdir=__LIB_DIR__ + +Name: libopeniscsiusr +Version: __VERSION__ +Description: iSCSI userspace library +Requires: +Libs: -L${libdir} -liscsiusr +Cflags: -I${includedir} diff --git a/libopeniscsiusr/libopeniscsiusr/libopeniscsiusr.h b/libopeniscsiusr/libopeniscsiusr/libopeniscsiusr.h new file mode 100644 index 0000000..4395902 --- /dev/null +++ b/libopeniscsiusr/libopeniscsiusr/libopeniscsiusr.h @@ -0,0 +1,549 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ +#ifndef _LIB_OPEN_ISCSI_USR_H_ +#define _LIB_OPEN_ISCSI_USR_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include "libopeniscsiusr_common.h" +#include "libopeniscsiusr_session.h" +#include "libopeniscsiusr_iface.h" +#include "libopeniscsiusr_node.h" + +/** + * iscsi_log_priority_str() - Convert log priority to string. + * + * Convert log priority to string (const char *). + * + * @priority: + * int. Log priority. + * + * Return: + * const char *. Please don't free returned pointer. Valid string are: + * + * * "ERROR" for LIBISCSI_LOG_PRIORITY_ERROR + * + * * "WARN" for LIBISCSI_LOG_PRIORITY_WARNING + * + * * "INFO" for LIBISCSI_LOG_PRIORITY_INFO + * + * * "DEBUG" for LIBISCSI_LOG_PRIORITY_DEBUG + * + * * "Invalid argument" for invalid log priority. + */ +__DLL_EXPORT const char *iscsi_log_priority_str(int priority); + +/** + * iscsi_strerror() - Convert error code to string. + * + * Convert error code (int) to string (const char *): + * + * * LIBISCSI_OK -- "OK" + * + * * LIBISCSI_ERR_BUG -- "BUG of libopeniscsiusr library" + * + * * LIBISCSI_ERR_SESS_NOT_FOUND - "Specified iSCSI session not found" + * + * * LIBISCSI_ERR_ACCESS - "Permission deny" + * + * * LIBISCSI_ERR_NOMEM - "Out of memory" + * + * * LIBISCSI_ERR_SYSFS_LOOKUP - "Could not lookup object in sysfs" + * + * * Other invalid error number -- "Invalid argument" + * + * @rc: + * int. Return code by libiscsiur functions. When provided error code is + * not a valid error code, return "Invalid argument". + * + * Return: + * const char *. The meaning of provided error code. Don't free returned + * pointer. + */ +__DLL_EXPORT const char *iscsi_strerror(int rc); + +/** + * iscsi_context_new() - Create struct iscsi_context. + * + * The default logging level (LIBISCSI_LOG_PRIORITY_DEFAULT) is + * LIBISCSI_LOG_PRIORITY_WARNING which means only warning and error message will + * be forward to log handler function. The default log handler function will + * print log message to STDERR, to change so, please use + * iscsi_context_log_func_set() to set your own log handler, check manpage + * libopeniscsiusr.h(3) for detail. + * + * Return: + * Pointer of 'struct iscsi_context'. Should be freed by + * iscsi_context_free(). + */ +__DLL_EXPORT struct iscsi_context *iscsi_context_new(void); + +/** + * iscsi_context_free() - Release the memory of struct iscsi_context. + * + * Release the memory of struct iscsi_context, but the userdata memory defined + * via iscsi_context_userdata_set() will not be touched. + * + * @ctx: + * Pointer of 'struct iscsi_context'. + * Return: + * void + */ +__DLL_EXPORT void iscsi_context_free(struct iscsi_context *ctx); + +/** + * iscsi_context_log_priority_set() - Set log priority. + * + * + * When library generates log message, only equal or more important(less value) + * message will be forwarded to log handler function. Valid log priority values + * are: + * + * * LIBISCSI_LOG_PRIORITY_ERROR -- 3 + * + * * LIBISCSI_LOG_PRIORITY_WARNING -- 4 + * + * * LIBISCSI_LOG_PRIORITY_INFO -- 6 + * + * * LIBISCSI_LOG_PRIORITY_DEBUG -- 7 + * + * @ctx: + * Pointer of 'struct iscsi_context'. + * If this pointer is NULL, your program will be terminated by assert. + * + * @priority: + * int, log priority. + * + * Return: + * void + */ +__DLL_EXPORT void iscsi_context_log_priority_set(struct iscsi_context *ctx, + int priority); + +/** + * iscsi_context_log_priority_get() - Get log priority. + * + * Retrieve current log priority. Valid log priority values are: + * + * * LIBISCSI_LOG_PRIORITY_ERROR -- 3 + * + * * LIBISCSI_LOG_PRIORITY_WARNING -- 4 + * + * * LIBISCSI_LOG_PRIORITY_INFO -- 5 + * + * * LIBISCSI_LOG_PRIORITY_DEBUG -- 7 + * + * @ctx: + * Pointer of 'struct iscsi_context'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * int, log priority. + */ +__DLL_EXPORT int iscsi_context_log_priority_get(struct iscsi_context *ctx); + +/** + * iscsi_context_log_func_set() - Set log handler function. + * + * Set custom log handler. The log handler will be invoked when log message + * is equal or more important(less value) than log priority setting. + * Please check manpage libopeniscsiusr.h(3) for detail usage. + * + * @ctx: + * Pointer of 'struct iscsi_context'. + * If this pointer is NULL, your program will be terminated by assert. + * @log_func: + * Pointer of log handler function. + * + * Return: + * void + */ +__DLL_EXPORT void iscsi_context_log_func_set + (struct iscsi_context *ctx, + void (*log_func) (struct iscsi_context *ctx, int priority, + const char *file, int line, const char *func_name, + const char *format, va_list args)); + +/** + * iscsi_context_userdata_set() - Set user data pointer. + * + * Store user data pointer into 'struct iscsi_context'. + * + * @ctx: + * Pointer of 'struct iscsi_context'. + * If this pointer is NULL, your program will be terminated by assert. + * @userdata: + * Pointer of user defined data. + * + * Return: + * void + */ +__DLL_EXPORT void iscsi_context_userdata_set(struct iscsi_context *ctx, + void *userdata); + +/** + * iscsi_context_userdata_get() - Get user data pointer. + * + * Retrieve user data pointer from 'struct iscsi_context'. + * + * @ctx: + * Pointer of 'struct iscsi_context'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * void *. Pointer of user defined data. + */ +__DLL_EXPORT void *iscsi_context_userdata_get(struct iscsi_context *ctx); + +/** + * iscsi_sessions_get() - Retrieve all iSCSI sessions. + * + * Retrieves all iSCSI sessions. For the properties of 'struct iscsi_session', + * please refer to the functions defined in 'libopeniscsiusr_session.h' file. + * + * @ctx: + * Pointer of 'struct iscsi_context'. + * If this pointer is NULL, your program will be terminated by assert. + * @ses: + * Output pointer of 'struct iscsi_session' pointer array. Its memory + * should be freed by iscsi_sessions_free(). + * If this pointer is NULL, your program will be terminated by assert. + * @se_count: + * Output pointer of uint32_t. Will store the size of + * 'struct iscsi_session' pointer array. + * + * Return: + * int. Valid error codes are: + * + * * LIBISCSI_OK + * + * * LIBISCSI_ERR_BUG + * + * * LIBISCSI_ERR_NOMEM + * + * * LIBISCSI_ERR_ACCESS + * + * * LIBISCSI_ERR_SYSFS_LOOKUP + * + * Error number could be converted to string by iscsi_strerror(). + */ +__DLL_EXPORT int iscsi_sessions_get(struct iscsi_context *ctx, + struct iscsi_session ***ses, + uint32_t *se_count); + +/** + * iscsi_sessions_free() - Free the memory of 'struct iscsi_session' pointer + * array + * + * Free the memory of 'iscsi_session' pointer array generated by + * 'iscsi_sessions_get()'. + * If provided 'ses' pointer is NULL or 'session_count' is 0, do nothing. + * + * @ses: + * Pointer of 'struct iscsi_session' pointer array. + * @session_count: + * uint32_t, the size of 'struct iscsi_session' pointer array. + * + * Return: + * void + */ +__DLL_EXPORT void iscsi_sessions_free(struct iscsi_session **ses, + uint32_t session_count); + +/** + * iscsi_session_get() - Retrieve specified iSCSI sessions. + * + * Retrieves specified iSCSI sessions. For the properties of + * 'struct iscsi_session', please refer to the functions defined in + * 'libopeniscsiusr_session.h' file. + * + * @ctx: + * Pointer of 'struct iscsi_context'. + * If this pointer is NULL, your program will be terminated by assert. + * @sid: + * uint32_t, iSCSI session ID. + * @se: + * Output pointer of 'struct iscsi_session' pointer. Its memory + * should be freed by iscsi_session_free(). + * If this pointer is NULL, your program will be terminated by assert. + * If specified iSCSI session does not exists, this pointer will be set to + * NULL with LIBISCSI_OK returned. + * + * Return: + * int. Valid error codes are: + * + * * LIBISCSI_OK + * + * * LIBISCSI_ERR_BUG + * + * * LIBISCSI_ERR_NOMEM + * + * * LIBISCSI_ERR_ACCESS + * + * * LIBISCSI_ERR_SYSFS_LOOKUP + * + * * LIBISCSI_ERR_SESS_NOT_FOUND + * + * Error number could be converted to string by iscsi_strerror(). + */ +__DLL_EXPORT int iscsi_session_get(struct iscsi_context *ctx, uint32_t sid, + struct iscsi_session **se); + +/** + * iscsi_session_free() - Free the memory of 'struct iscsi_session'. + * + * Free the memory of 'iscsi_session' pointer generated by + * 'iscsi_sessions_get()'. + * If provided 'se' pointer is NULL, do nothing. + * + * @se: + * Pointer of 'struct iscsi_session' pointer. + * + * Return: + * void + */ +__DLL_EXPORT void iscsi_session_free(struct iscsi_session *se); + +/** + * iscsi_default_iface_setup() - Setup default iSCSI interfaces. + * + * Setup default iSCSI interfaces for iSCSI TCP, iSER and iSCSI hardware offload + * cards. It is required after new iSCSI hardware offload card installed. + * + * Below kernel modules will be loaded when required by this function: + * + * * cxgb3i + * * cxgb4i + * * bnx2i + * + * It will also create configuration files for iSCSI hardware offload cards in + * /etc/iscsi/ifaces/. + * + * @ctx: + * Pointer of 'struct iscsi_context'. + * + * Return: + * int. Valid error codes are: + * + * * LIBISCSI_OK + * + * * LIBISCSI_ERR_BUG + * + * * LIBISCSI_ERR_NOMEM + * + * * LIBISCSI_ERR_ACCESS + * + * * LIBISCSI_ERR_SYSFS_LOOKUP + * + * * LIBISCSI_ERR_IDBM + * + * Error number could be converted to string by iscsi_strerror(). + */ +__DLL_EXPORT int iscsi_default_iface_setup(struct iscsi_context *ctx); + +/** + * iscsi_ifaces_get() - Retrieve all iSCSI interfaces. + * + * Retrieves all iSCSI interfaces. For the properties of 'struct iscsi_iface', + * please refer to the functions defined in 'libopeniscsiusr_iface.h' file. + * The returned results contains default iSCSI interfaces(iser and iscsi_tcp) + * and iSCSI interfaces configured in "/etc/iscsi/ifaces/". + * Illegal configuration file will be skipped and warned. + * To generate iSCSI interface configuration when new card installed, please + * use iscsi_default_iface_setup(). + * + * @ctx: + * Pointer of 'struct iscsi_context'. + * If this pointer is NULL, your program will be terminated by assert. + * @ifaces: + * Output pointer of 'struct iscsi_iface' pointer array. Its memory + * should be freed by iscsi_ifaces_free(). + * If this pointer is NULL, your program will be terminated by assert. + * @iface_count: + * Output pointer of uint32_t. Will store the size of + * 'struct iscsi_iface' pointer array. + * + * Return: + * int. Valid error codes are: + * + * * LIBISCSI_OK + * + * * LIBISCSI_ERR_BUG + * + * * LIBISCSI_ERR_NOMEM + * + * * LIBISCSI_ERR_ACCESS + * + * * LIBISCSI_ERR_SYSFS_LOOKUP + * + * Error number could be converted to string by iscsi_strerror(). + */ +__DLL_EXPORT int iscsi_ifaces_get(struct iscsi_context *ctx, + struct iscsi_iface ***ifaces, + uint32_t *iface_count); + +/** + * iscsi_ifaces_free() - Free the memory of 'struct iscsi_iface' pointer + * array + * + * Free the memory of 'iscsi_iface' pointer array generated by + * 'iscsi_ifaces_get()'. + * If provided 'ifaces' pointer is NULL or 'iface_count' is 0, do nothing. + * + * @ifaces: + * Pointer of 'struct iscsi_iface' pointer array. + * @iface_count: + * uint32_t, the size of 'struct iscsi_iface' pointer array. + * + * Return: + * void + */ +__DLL_EXPORT void iscsi_ifaces_free(struct iscsi_iface **ifaces, + uint32_t iface_count); + +/** + * iscsi_iface_get() - Retrieve specified iSCSI interface. + * + * Retrieves specified iSCSI interfaces by reading configuration from + * "/etc/iscsi/iface/". + * To generate iSCSI interface configuration when new card installed, please + * use iscsi_default_iface_setup(). + * Illegal configuration file will be treated as error LIBISCSI_ERR_IDBM. + * Configuration file not found will be treated as error LIBISCSI_ERR_INVAL. + * + * @ctx: + * Pointer of 'struct iscsi_context'. + * If this pointer is NULL, your program will be terminated by assert. + * @iface_name: + * String. Name of iSCSI interface. Also the file name of configuration + * file "/etc/iscsi/iface/". + * If this pointer is NULL or empty string, your program will be terminated + * by assert. + * @iface: + * Output pointer of 'struct iscsi_iface'. Its memory should be freed by + * iscsi_iface_free(). + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * int. Valid error codes are: + * + * * LIBISCSI_OK + * + * * LIBISCSI_ERR_BUG + * + * * LIBISCSI_ERR_NOMEM + * + * * LIBISCSI_ERR_ACCESS + * + * * LIBISCSI_ERR_SYSFS_LOOKUP + * + * * LIBISCSI_ERR_IDBM + * + * Error number could be converted to string by iscsi_strerror(). + */ +__DLL_EXPORT int iscsi_iface_get(struct iscsi_context *ctx, + const char *iface_name, + struct iscsi_iface **iface); + +/** + * iscsi_iface_free() - Free the memory of 'struct iscsi_iface' pointer. + * + * Free the memory of 'iscsi_iface' pointer generated by 'iscsi_iface_get()'. + * If provided 'iface' pointer is NULL, do nothing. + * + * @iface: + * Pointer of 'struct iscsi_iface' pointer. + * + * Return: + * void + */ +__DLL_EXPORT void iscsi_iface_free(struct iscsi_iface *iface); + +/** + * iscsi_nodes_get() - Retrieve all iSCSI nodes. + * + * Retrieves all iSCSI nodes. For the properties of 'struct iscsi_node', + * please refer to the functions defined in 'libopeniscsiusr_node.h' file. + * The returned results contains iSCSI nodes configured in "/etc/iscsi/nodes/". + * Illegal configuration file will be skipped and warned. + * The returned 'struct iscsi_node' pointer array is sorted by target name, + * connection address, connection port and interface. + * + * @ctx: + * Pointer of 'struct iscsi_context'. + * If this pointer is NULL, your program will be terminated by assert. + * @nodes: + * Output pointer of 'struct iscsi_node' pointer array. Its memory + * should be freed by iscsi_nodes_free(). + * If this pointer is NULL, your program will be terminated by assert. + * @node_count: + * Output pointer of uint32_t. Will store the size of + * 'struct iscsi_node' pointer array. + * + * Return: + * int. Valid error codes are: + * + * * LIBISCSI_OK + * + * * LIBISCSI_ERR_BUG + * + * * LIBISCSI_ERR_NOMEM + * + * * LIBISCSI_ERR_ACCESS + * + * * LIBISCSI_ERR_SYSFS_LOOKUP + * + * * LIBISCSI_ERR_IDBM + * + * Error number could be converted to string by iscsi_strerror(). + */ +__DLL_EXPORT int iscsi_nodes_get(struct iscsi_context *ctx, + struct iscsi_node ***nodes, + uint32_t *node_count); + +/** + * iscsi_nodes_free() - Free the memory of 'struct iscsi_node' pointer + * array + * + * Free the memory of 'iscsi_node' pointer array generated by + * 'iscsi_nodes_get()'. + * If provided 'nodes' pointer is NULL or 'node_count' is 0, do nothing. + * + * @nodes: + * Pointer of 'struct iscsi_node' pointer array. + * @node_count: + * uint32_t, the size of 'struct iscsi_node' pointer array. + * + * Return: + * void + */ +__DLL_EXPORT void iscsi_nodes_free(struct iscsi_node **nodes, + uint32_t node_count); + +#ifdef __cplusplus +} /* End of extern "C" */ +#endif + +#endif /* End of _LIB_OPEN_ISCSI_USR_H_ */ diff --git a/libopeniscsiusr/libopeniscsiusr/libopeniscsiusr_common.h b/libopeniscsiusr/libopeniscsiusr/libopeniscsiusr_common.h new file mode 100644 index 0000000..7ae2906 --- /dev/null +++ b/libopeniscsiusr/libopeniscsiusr/libopeniscsiusr_common.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ + + +#ifndef _LIB_OPEN_ISCSI_USR_COMMON_H_ +#define _LIB_OPEN_ISCSI_USR_COMMON_H_ + +#include + +/* Below error numbers should align with 'open-iscsi/include/iscsi_err.h' */ +#define LIBISCSI_OK 0 +/* ^ No error */ + +#define LIBISCSI_ERR_BUG 1 +/* ^ Bug of library */ + +#define LIBISCSI_ERR_SESS_NOT_FOUND 2 +/* ^ session could not be found */ + +#define LIBISCSI_ERR_NOMEM 3 +/* ^ Could not allocate resource for operation */ + +#define LIBISCSI_ERR_IDBM 6 +/* ^ Error accessing/managing iSCSI DB */ + +#define LIBISCSI_ERR_INVAL 7 +/* ^ Invalid argument */ + +#define LIBISCSI_ERR_TRANS_NOT_FOUND 12 +/* ^ iSCSI transport module not loaded in kernel or iscsid */ + +#define LIBISCSI_ERR_ACCESS 13 +/* ^ Permission denied */ + +#define LIBISCSI_ERR_SYSFS_LOOKUP 22 +/* ^ Could not lookup object in sysfs */ + +/* + * Use the syslog severity level as log priority + */ +#define LIBISCSI_LOG_PRIORITY_ERROR 3 +#define LIBISCSI_LOG_PRIORITY_WARNING 4 +#define LIBISCSI_LOG_PRIORITY_INFO 6 +#define LIBISCSI_LOG_PRIORITY_DEBUG 7 + +#define LIBISCSI_LOG_PRIORITY_DEFAULT LIBISCSI_LOG_PRIORITY_WARNING + +#define __DLL_EXPORT __attribute__ ((visibility ("default"))) +/* ^ Mark function or struct as external use. + * Check https://gcc.gnu.org/wiki/Visibility for detail + */ +#define __DLL_LOCAL __attribute__ ((visibility ("hidden"))) +/* ^ Mark function or struct as internal use only. + * Check https://gcc.gnu.org/wiki/Visibility for detail + */ + +struct __DLL_EXPORT iscsi_context; + +struct __DLL_EXPORT iscsi_session; + +struct __DLL_EXPORT iscsi_iface; + +struct __DLL_EXPORT iscsi_node; + +#endif /* End of _LIB_OPEN_ISCSI_USR_COMMON_H_ */ diff --git a/libopeniscsiusr/libopeniscsiusr/libopeniscsiusr_iface.h b/libopeniscsiusr/libopeniscsiusr/libopeniscsiusr_iface.h new file mode 100644 index 0000000..a1a2552 --- /dev/null +++ b/libopeniscsiusr/libopeniscsiusr/libopeniscsiusr_iface.h @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ + +#ifndef _LIB_OPEN_ISCSI_USR_IFACE_H_ +#define _LIB_OPEN_ISCSI_USR_IFACE_H_ + +#include +#include + +#include "libopeniscsiusr_common.h" + +/** + * iscsi_iface_ipaddress_get() - Retrieve IP address of specified + * iSCSI interface + * + * Retrieve the IP address of specified iSCSI interface. + * + * @iface: + * Pointer of 'struct iscsi_iface'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. + * No need to free this memory, the resources will get freed by + * iscsi_session_free() or iscsi_sessions_free(). + */ +__DLL_EXPORT const char *iscsi_iface_ipaddress_get(struct iscsi_iface *iface); + +/** + * iscsi_iface_hwaddress_get() - Retrieve hardware address of specified + * iSCSI interface + * + * Retrieve the hardware address of specified iSCSI interface. + * + * @iface: + * Pointer of 'struct iscsi_iface'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. + * No need to free this memory, the resources will get freed by + * iscsi_session_free() or iscsi_sessions_free(). + */ +__DLL_EXPORT const char *iscsi_iface_hwaddress_get(struct iscsi_iface *iface); + +/** + * iscsi_iface_netdev_get() - Retrieve network device name of specified + * iSCSI interface + * + * Retrieve the network device name of specified iSCSI interface. + * + * @iface: + * Pointer of 'struct iscsi_iface'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. + * No need to free this memory, the resources will get freed by + * iscsi_session_free() or iscsi_sessions_free(). + */ +__DLL_EXPORT const char *iscsi_iface_netdev_get(struct iscsi_iface *iface); + +/** + * iscsi_iface_transport_name_get() - Retrieve transport name of specified + * iSCSI interface + * + * Retrieve the transport name of specified iSCSI interface. + * Examples: + * + * * "tcp" (Software iSCSI over TCP/IP) + * * "iser" (Software iSCSI over infinniband + * * "qla4xxx" (Qlogic QLA4XXX HBAs) + * * "bnx2i" (Broadcom bnx iSCSI HBAs); + * * "cxgb3i" (Chelsio cxgb S3 iSCSI HBAs); + * + * @iface: + * Pointer of 'struct iscsi_iface'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. + * No need to free this memory, the resources will get freed by + * iscsi_session_free() or iscsi_sessions_free(). + */ +__DLL_EXPORT const char *iscsi_iface_transport_name_get + (struct iscsi_iface *iface); + +/** + * iscsi_iface_iname_get() - Retrieve initiator name of specified + * iSCSI interface + * + * Retrieve the initiator name of specified iSCSI interface. + * + * @iface: + * Pointer of 'struct iscsi_iface'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. + * No need to free this memory, the resources will get freed by + * iscsi_session_free() or iscsi_sessions_free(). + */ +__DLL_EXPORT const char *iscsi_iface_iname_get(struct iscsi_iface *iface); + +/** + * iscsi_iface_port_state_get() - Retrieve network port state of specified + * iSCSI interface + * + * Retrieve the network port state of specified iSCSI interface. + * + * @iface: + * Pointer of 'struct iscsi_iface'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. Possible values are : + * + * * "LINK_UP" + * + * * "LINK_DOWN" + * + * * "unknown" + * + * No need to free this memory, the resources will get freed by + * iscsi_session_free() or iscsi_sessions_free(). + */ +__DLL_EXPORT const char *iscsi_iface_port_state_get(struct iscsi_iface *iface); + +/** + * iscsi_iface_port_speed_get() - Retrieve network port speed of specified + * iSCSI interface + * + * Retrieve the network port speed of specified iSCSI interface. + * Returned string format is '[0-9]+ [MGT]bps', example: '10 Mbps' or '10 Gbps'. + * + * @iface: + * Pointer of 'struct iscsi_iface'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. Set to "unknown" if unknown. + * No need to free this memory, the resources will get freed by + * iscsi_session_free() or iscsi_sessions_free(). + */ +__DLL_EXPORT const char *iscsi_iface_port_speed_get(struct iscsi_iface *iface); + +/** + * iscsi_iface_port_speed_get() - Retrieve name of specified iSCSI interface + * + * Retrieve the name of specified iSCSI interface. + * + * @iface: + * Pointer of 'struct iscsi_iface'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. + * No need to free this memory, the resources will get freed by + * iscsi_session_free() or iscsi_sessions_free(). + */ +__DLL_EXPORT const char *iscsi_iface_name_get(struct iscsi_iface *iface); + +/** + * iscsi_iface_dump_config() - Dump all configurations of specified iSCSI + * interface. + * + * Dump all configurations of specified iSCSI interface. Will skip empty + * configuration so that output string could be saved directly to + * /etc/iscsi/ifaces/ file. + * + * @iface: + * Pointer of 'struct iscsi_iface'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. + * Need to free this memory by free(). + */ +__DLL_EXPORT const char *iscsi_iface_dump_config(struct iscsi_iface *iface); + +/** + * iscsi_iface_dump_config() - Print all configurations of specified iSCSI + * interface to STDOUT. + * + * Print all configurations of specified iSCSI interface. + * For empty configuration, it will be shown as "name = ". + * + * @iface: + * Pointer of 'struct iscsi_iface'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * void + */ +__DLL_EXPORT void iscsi_iface_print_config(struct iscsi_iface *iface); + +/** + * iscsi_is_default_iface() - Whether specified iSCSI interface is default + * interface. + * + * Check whether specified iSCSI interface is one of the default interfaces. + * Currently, default interfaces are : + * + * * Interface 'default' using 'iscsi_tcp' kernel module. + * + * * Interface 'iser' is using 'ib_iser' kernel module. + * + * @iface: + * Pointer of 'struct iscsi_iface'. + * + * Return: + * bool. + */ +__DLL_EXPORT bool iscsi_is_default_iface(struct iscsi_iface *iface); + +#endif /* End of _LIB_OPEN_ISCSI_USR_IFACE_H_ */ diff --git a/libopeniscsiusr/libopeniscsiusr/libopeniscsiusr_node.h b/libopeniscsiusr/libopeniscsiusr/libopeniscsiusr_node.h new file mode 100644 index 0000000..e093d5a --- /dev/null +++ b/libopeniscsiusr/libopeniscsiusr/libopeniscsiusr_node.h @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ + +#ifndef _LIB_OPEN_ISCSI_USR_NODE_H_ +#define _LIB_OPEN_ISCSI_USR_NODE_H_ + +#include "libopeniscsiusr_common.h" + +/** + * iscsi_node_dump_config() - Dump all configurations of specified iSCSI + * node. + * + * Dump all configurations of specified iSCSI node. Will skip empty + * configuration. + * + * @node: + * Pointer of 'struct iscsi_node'. + * If this pointer is NULL, your program will be terminated by assert. + * @show_secret: + * Whether show CHAP secret. If set as false, will show password as + * "********" + * + * Return: + * const char *. + * Need to free this memory by free(). + */ +__DLL_EXPORT const char *iscsi_node_dump_config(struct iscsi_node *node, + bool show_secret); + +/** + * iscsi_node_dump_config() - Print all configurations of specified iSCSI + * node to STDOUT. + * + * Print all configurations of specified iSCSI node. + * For empty configuration, it will be shown as "name = ". + * + * @node: + * Pointer of 'struct iscsi_node'. + * If this pointer is NULL, your program will be terminated by assert. + * @show_secret: + * Whether show CHAP secret. If set as false, will show password as + * "********" + * + * Return: + * void + */ +__DLL_EXPORT void iscsi_node_print_config(struct iscsi_node *node, + bool show_secret); + +/** + * iscsi_node_target_name_get() - Retrieve target name of specified iSCSI node. + * + * Retrieve the target name of specified iSCSI node. + * Examples: "iqn.2003-01.org.linux-iscsi.org:iscsi-targetcli" + * + * @node: + * Pointer of 'struct iscsi_node'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. + * No need to free this memory, the resources will get freed by + * iscsi_node_free() or iscsi_nodes_free(). + */ +__DLL_EXPORT const char *iscsi_node_target_name_get + (struct iscsi_node *node); + +/** + * iscsi_node_conn_is_ipv6() - Check whether specified node is using ipv6 + * connection. + * + * Check whether specified node is using ipv6 connection. + * + * @node: + * Pointer of 'struct iscsi_node'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * bool + */ +__DLL_EXPORT bool iscsi_node_conn_is_ipv6(struct iscsi_node *node); + +/** + * iscsi_node_conn_address_get() - Retrieve connection address of specified + * iSCSI node. + * + * Retrieve the iscsi connection target address of specified iSCSI node. + * Examples: "192.168.1.1" + * + * @node: + * Pointer of 'struct iscsi_node'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. + * No need to free this memory, the resources will get freed by + * iscsi_node_free() or iscsi_nodes_free(). + */ +__DLL_EXPORT const char *iscsi_node_conn_address_get(struct iscsi_node *node); + +/** + * iscsi_node_conn_port_get() - Retrieve connection port of specified iSCSI + * node. + * + * Retrieve the iscsi connection target port of specified iSCSI node. + * Examples: "3260" + * + * @node: + * Pointer of 'struct iscsi_node'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * uint32_t + */ +__DLL_EXPORT uint32_t iscsi_node_conn_port_get(struct iscsi_node *node); + +/** + * iscsi_node_portal_get() - Retrieve connection portal of specified + * iSCSI node. + * + * Retrieve the iscsi connection target portal of specified iSCSI node. + * Just a combination of iscsi_node_conn_address_get() and + * iscsi_node_conn_port_get(). + * Examples: "192.168.1.1:3260" and "[::1]:3260" + * + * @node: + * Pointer of 'struct iscsi_node'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. + * No need to free this memory, the resources will get freed by + * iscsi_node_free() or iscsi_nodes_free(). + */ +__DLL_EXPORT const char *iscsi_node_portal_get(struct iscsi_node *node); + +/** + * iscsi_node_tpgt_get() - Retrieve target portal group tag of specified + * iSCSI node. + * + * Retrieve the target portal group tag of specified iSCSI node. + * + * @node: + * Pointer of 'struct iscsi_node'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * int32_t. -1 for unknown. + */ +__DLL_EXPORT int32_t iscsi_node_tpgt_get(struct iscsi_node *node); + +/** + * iscsi_node_iface_name_get() - Retrieve interface name of specified iSCSI + * node. + * + * Retrieve the interface name of specified iSCSI node. + * Examples: "default" for iscsi tcp interface. + * + * @node: + * Pointer of 'struct iscsi_node'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. + * No need to free this memory, the resources will get freed by + * iscsi_node_free() or iscsi_nodes_free(). + */ +__DLL_EXPORT const char *iscsi_node_iface_name_get(struct iscsi_node *node); + +#endif /* End of _LIB_OPEN_ISCSI_USR_NODE_H_ */ diff --git a/libopeniscsiusr/libopeniscsiusr/libopeniscsiusr_session.h b/libopeniscsiusr/libopeniscsiusr/libopeniscsiusr_session.h new file mode 100644 index 0000000..30f8d7a --- /dev/null +++ b/libopeniscsiusr/libopeniscsiusr/libopeniscsiusr_session.h @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ + +#ifndef _LIB_OPEN_ISCSI_USR_SESSION_H_ +#define _LIB_OPEN_ISCSI_USR_SESSION_H_ + +#include "libopeniscsiusr_common.h" + +#include + +/** + * iscsi_session_sid_get() - Retrieve iSCSI session ID of specified session. + * + * Retrieve iSCSI session ID. The session ID here is the integer used + * in '/sys/class/iscsi_session/session/' + * + * @se: + * Pointer of 'struct iscsi_session'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * uint32_t. + */ +__DLL_EXPORT uint32_t iscsi_session_sid_get(struct iscsi_session *se); + +/** + * iscsi_session_persistent_address_get() - Retrieve iSCSI target persistent + * address of specified session + * + * Retrieve the iSCSI target persistent address of specified iSCSI session. + * The 'persistent address' is the network address where iSCSI initiator send + * initial request. When iSCSI redirection in use, this address might not be + * the network address used for actual iSCSI transaction. + * Please use `iscsi_session_address_get()` for target network address of + * iSCSI transaction. + * + * @se: + * Pointer of 'struct iscsi_session'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. Empty string if not supported. + * No need to free this memory, the resources will get freed by + * iscsi_session_free() or iscsi_sessions_free(). + */ +__DLL_EXPORT const char *iscsi_session_persistent_address_get + (struct iscsi_session *se); + +/** + * iscsi_session_persistent_port_get() - Retrieve iSCSI target persistent + * port of specified session + * + * Retrieve the iSCSI target persistent port of specified iSCSI session. + * The 'persistent port' is the network port where iSCSI initiator send + * initial request. When iSCSI redirection in use, this port might not be + * the network port used for actual iSCSI transaction. + * Please use `iscsi_session_port_get()` for target network address of + * iSCSI transaction. + * + * @se: + * Pointer of 'struct iscsi_session'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * int32_t. -1 if not supported. + */ +__DLL_EXPORT int32_t iscsi_session_persistent_port_get + (struct iscsi_session *se); + +/** + * iscsi_session_target_name_get() - Retrieve iSCSI target name of specified + * session + * + * Retrieve the iSCSI target name of specified iSCSI session. + * The iSCSI Target Name specifies the worldwide unique name of the target. + * + * @se: + * Pointer of 'struct iscsi_session'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. + * No need to free this memory, the resources will get freed by + * iscsi_session_free() or iscsi_sessions_free(). + */ +__DLL_EXPORT const char *iscsi_session_target_name_get + (struct iscsi_session *se); + +/** + * iscsi_session_username_get() - Retrieve authentication username of specified + * session. + * + * Retrieve the authentication username of specified iSCSI session. + * Currently open-iscsi only support CHAP authentication method. + * It's controlled this setting in iscsid.conf: + * 'node.session.auth.username' + * + * @se: + * Pointer of 'struct iscsi_session'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. Empty string if not using CHAP authentication or failed + * to read authentication information. + * No need to free this memory, the resources will get freed by + * iscsi_session_free() or iscsi_sessions_free(). + */ +__DLL_EXPORT const char *iscsi_session_username_get(struct iscsi_session *se); + +/** + * iscsi_session_password_get() - Retrieve authentication password of specified + * session. + * + * Retrieve the authentication password of specified iSCSI session. + * Currently open-iscsi only support CHAP authentication method. + * It's controlled this setting in iscsid.conf: + * 'node.session.auth.password' + * + * @se: + * Pointer of 'struct iscsi_session'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. Empty string if not using CHAP authentication or failed + * to read authentication information. + * No need to free this memory, the resources will get freed by + * iscsi_session_free() or iscsi_sessions_free(). + */ +__DLL_EXPORT const char *iscsi_session_password_get(struct iscsi_session *se); + +/** + * iscsi_session_username_in_get() - Retrieve authentication username of + * specified session. + * + * Retrieve the inbound authentication username of specified iSCSI session. + * Currently open-iscsi only support CHAP authentication method. + * The inbound authentication here means the iSCSI initiator authenticates the + * iSCSI target using CHAP. + * It's controlled this setting in iscsid.conf: + * 'node.session.auth.username_in' + * + * @se: + * Pointer of 'struct iscsi_session'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. Empty string if not using inbound CHAP authentication or + * failed to read authentication information. + * No need to free this memory, the resources will get freed by + * iscsi_session_free() or iscsi_sessions_free(). + */ +__DLL_EXPORT const char *iscsi_session_username_in_get + (struct iscsi_session *se); + +/** + * iscsi_session_password_in_get() - Retrieve authentication password of + * specified session. + * + * Retrieve the inbound authentication password of specified iSCSI session. + * Currently open-iscsi only support CHAP authentication method. + * The inbound authentication here means the iSCSI initiator authenticates the + * iSCSI target using CHAP. + * It's controlled this setting in iscsid.conf: + * 'node.session.auth.password_in' + * + * @se: + * Pointer of 'struct iscsi_session'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. Empty string if not using inbound CHAP authentication or + * failed to read authentication information. + * No need to free this memory, the resources will get freed by + * iscsi_session_free() or iscsi_sessions_free(). + */ +__DLL_EXPORT const char *iscsi_session_password_in_get + (struct iscsi_session *se); + +/** + * iscsi_session_recovery_tmo_get() - Retrieve recovery timeout value of + * specified session + * + * Retrieve the recovery timeout value of specified iSCSI session. + * The recovery timeout here means the seconds of time to wait for session + * re-establishment before failing SCSI commands back to the application when + * running the Linux SCSI Layer error handler. + * It could be controlled via this setting in iscsid.conf: + * 'node.session.timeo.replacement_timeout'. + * + * @se: + * Pointer of 'struct iscsi_session'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * int32_t. If the value is 0, IO will be failed immediately. If the value + * is less than 0, IO will remain queued until the session is logged back + * in, or until the user runs the logout command. + */ +__DLL_EXPORT int32_t iscsi_session_recovery_tmo_get(struct iscsi_session *se); + +/** + * iscsi_session_lu_reset_tmo_get() - Retrieve logical unit timeout value of + * specified session + * + * Retrieve the logical unit timeout value of specified iSCSI session. + * The logical unit timeout here means the seconds of time to wait for a logical + * unit response before before failing the operation and trying session + * re-establishment. + * It could be controlled via this setting in iscsid.conf: + * 'node.session.err_timeo.lu_reset_timeout' + * + * @se: + * Pointer of 'struct iscsi_session'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * int32_t. -1 if not supported. + */ +__DLL_EXPORT int32_t iscsi_session_lu_reset_tmo_get(struct iscsi_session *se); + +/** + * iscsi_session_tgt_reset_tmo_get() - Retrieve target response timeout value of + * of specified session + * + * Retrieve the target response timeout value of specified iSCSI session. + * The target response timeout here means the seconds of time to wait for a + * target response before before failing the operation and trying session + * re-establishment. + * It could be controlled via this setting in iscsid.conf: + * 'node.session.err_timeo.tgt_reset_timeout'. + * + * @se: + * Pointer of 'struct iscsi_session'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * int32_t. -1 if not supported. + */ +__DLL_EXPORT int32_t iscsi_session_tgt_reset_tmo_get(struct iscsi_session *se); + +/** + * iscsi_session_abort_tmo_get() - Retrieve abort response timeout value of + * specified session + * + * Retrieve the abort response timeout value of specified iSCSI session. + * The abort response timeout here means the seconds of time to wait for a + * abort response before before failing the operation and trying session + * re-establishment. + * It could be controlled via this setting in iscsid.conf: + * 'node.session.err_timeo.abort_timeout'. + * + * @se: + * Pointer of 'struct iscsi_session'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * int32_t. -1 if not supported. + */ +__DLL_EXPORT int32_t iscsi_session_abort_tmo_get(struct iscsi_session *se); + +/** + * iscsi_session_tpgt_get() - Retrieve target portal group tag of specified + * session + * + * Retrieve the target portal group tag of specified iSCSI session. + * + * The target portal group tag is a value that uniquely identifies a portal + * group within an iSCSI target node. This key carries the value of the tag of + * the portal group that is servicing the Login request. The iSCSI target + * returns this key to the initiator in the Login Response PDU to the first + * Login Request PDU that has the C bit set to 0 when TargetName is given by the + * initiator. + * + * @se: + * Pointer of 'struct iscsi_session'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * int32_t. -1 if not supported. + */ +__DLL_EXPORT int32_t iscsi_session_tpgt_get(struct iscsi_session *se); + +/** + * iscsi_session_address_get() - Retrieve iSCSI target address of specified + * session + * + * Retrieve the iSCSI target network address of specified iSCSI session. + * + * @se: + * Pointer of 'struct iscsi_session'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * const char *. Empty string if not supported. + * No need to free this memory, the resources will get freed by + * iscsi_session_free() or iscsi_sessions_free(). + */ +__DLL_EXPORT const char *iscsi_session_address_get + (struct iscsi_session *se); + +/** + * iscsi_session_port_get() - Retrieve iSCSI target port of specified session + * + * Retrieve the iSCSI target port of specified iSCSI session. + * + * @se: + * Pointer of 'struct iscsi_session'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * int32_t. -1 if not supported. + */ +__DLL_EXPORT int32_t iscsi_session_port_get(struct iscsi_session *se); + +/** + * iscsi_session_address_get() - Retrieve iSCSI interface information of + * specified session + * + * Retrieve the iSCSI interface information of specified iSCSI session. + * For the properties of 'struct iscsi_iface', please refer to the functions + * defined in 'libopeniscsiusr_iface.h' file. + * + * @se: + * Pointer of 'struct iscsi_session'. + * If this pointer is NULL, your program will be terminated by assert. + * + * Return: + * Pointer of 'struct iscsi_iface'. NULL if not supported. + * No need to free this memory, the resources will get freed by + * iscsi_session_free() or iscsi_sessions_free(). + */ +__DLL_EXPORT struct iscsi_iface *iscsi_session_iface_get + (struct iscsi_session *se); + +#endif /* End of _LIB_OPEN_ISCSI_USR_SESSION_H_ */ diff --git a/libopeniscsiusr/misc.c b/libopeniscsiusr/misc.c new file mode 100644 index 0000000..9111966 --- /dev/null +++ b/libopeniscsiusr/misc.c @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libopeniscsiusr/libopeniscsiusr.h" +#include "misc.h" +#include "context.h" + +#define _UNUSED(x) (void)(x) + +#define _ISCSI_LOG_STRERR_ALIGN_WIDTH 80 +/* ^ Only used in _iscsi_log_stderr() for pretty log output. + * When provided log message is less than 80 bytes, fill it with space, then + * print code file name, function name, line after the 80th bytes. + */ + +struct _num_str_conv { + const uint32_t value; + const char *str; +}; + +#define _iscsi_str_func_gen(func_name, var_type, var, conv_array) \ +const char *func_name(var_type var) { \ + size_t i = 0; \ + uint32_t tmp_var = var & UINT32_MAX; \ + errno = 0; \ + /* In the whole libopeniscsiusr, we don't have negative value */ \ + for (; i < sizeof(conv_array)/sizeof(conv_array[0]); ++i) { \ + if ((conv_array[i].value) == tmp_var) \ + return conv_array[i].str; \ + } \ + errno = EINVAL; \ + return "Invalid argument"; \ +} + +static const struct _num_str_conv _ISCSI_RC_MSG_CONV[] = { + {LIBISCSI_OK, "OK"}, + {LIBISCSI_ERR_BUG, "BUG of libopeniscsiusr library"}, + {LIBISCSI_ERR_SESS_NOT_FOUND, "Specified iSCSI session not found"}, + {LIBISCSI_ERR_ACCESS, "Permission deny"}, + {LIBISCSI_ERR_NOMEM, "Out of memory"}, + {LIBISCSI_ERR_SYSFS_LOOKUP, "Could not lookup object in sysfs"}, + {LIBISCSI_ERR_IDBM, "Error accessing/managing iSCSI DB"}, + {LIBISCSI_ERR_TRANS_NOT_FOUND, + "iSCSI transport module not loaded in kernel or iscsid"}, + {LIBISCSI_ERR_INVAL, "Invalid argument"}, +}; + +_iscsi_str_func_gen(iscsi_strerror, int, rc, _ISCSI_RC_MSG_CONV); + +static const struct _num_str_conv _ISCSI_PRI_CONV[] = { + {LIBISCSI_LOG_PRIORITY_DEBUG, "DEBUG"}, + {LIBISCSI_LOG_PRIORITY_INFO, "INFO"}, + {LIBISCSI_LOG_PRIORITY_WARNING, "WARNING"}, + {LIBISCSI_LOG_PRIORITY_ERROR, "ERROR"}, +}; + +_iscsi_str_func_gen(iscsi_log_priority_str, int, priority, _ISCSI_PRI_CONV); + +void _iscsi_log_stderr(struct iscsi_context *ctx, int priority, + const char *file, int line, const char *func_name, + const char *format, va_list args) +{ + int printed_bytes = 0; + + _UNUSED(ctx); + + printed_bytes += fprintf(stderr, "iSCSI %s: ", + iscsi_log_priority_str(priority)); + printed_bytes += vfprintf(stderr, format, args); + + if (printed_bytes < _ISCSI_LOG_STRERR_ALIGN_WIDTH) { + fprintf(stderr, "%*s # %s:%s():%d\n", + _ISCSI_LOG_STRERR_ALIGN_WIDTH - printed_bytes, "", file, + func_name, line); + } else { + fprintf(stderr, " # %s:%s():%d\n", file, func_name, line); + } +} + +void _iscsi_log(struct iscsi_context *ctx, int priority, const char *file, + int line, const char *func_name, const char *format, ...) +{ + va_list args; + + if (ctx->log_func == NULL) + return; + + va_start(args, format); + ctx->log_func(ctx, priority, file, line, func_name, format, args); + va_end(args); +} + +int _scan_filter_skip_dot(const struct dirent *dir) +{ + return strcmp(dir->d_name, ".") && strcmp(dir->d_name, ".."); +} + +bool _file_exists(const char *path) +{ + if (access(path, F_OK) == 0) + return true; + else + return false; +} + +static bool _is_eth(struct iscsi_context *ctx, const char *if_name) +{ + struct ifreq ifr; + int sockfd = -1; + char strerr_buff[_STRERR_BUFF_LEN]; + + assert(if_name != NULL); + + memset(&ifr, 0, sizeof(ifr)); + + _strncpy(ifr.ifr_name, if_name, IFNAMSIZ); + + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + _warn(ctx, "Failed to create SOCK_DGRAM AF_INET socket: %d %s", + errno, _strerror(errno, strerr_buff)); + return false; + } + + if (ioctl(sockfd, SIOCGIFHWADDR, &ifr) != 0) { + _warn(ctx, "IOCTL SIOCGIFHWADDR to %s failed: %d %s", if_name, + errno, _strerror(errno, strerr_buff)); + close(sockfd); + return false; + } + + close(sockfd); + + if (ifr.ifr_hwaddr.sa_family == ARPHRD_ETHER) + return true; + + return false; +} + +/* + * driver_name should be char[_ETH_DRIVER_NAME_MAX_LEN] + */ +static int _eth_driver_get(struct iscsi_context *ctx, const char *if_name, + char *driver_name) +{ + int sockfd = -1; + struct ethtool_drvinfo drvinfo; + struct ifreq ifr; + char strerr_buff[_STRERR_BUFF_LEN]; + + assert(ctx != NULL); + assert(if_name != NULL); + assert(driver_name != NULL); + + memset(&ifr, 0, sizeof(ifr)); + memset(&drvinfo, 0, sizeof(drvinfo)); + + _strncpy(ifr.ifr_name, if_name, IFNAMSIZ); + drvinfo.cmd = ETHTOOL_GDRVINFO; + ifr.ifr_data = (caddr_t) &drvinfo; + + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + _error(ctx, "Failed to create SOCK_DGRAM AF_INET socket: %d %s", + errno, _strerror(errno, strerr_buff)); + return LIBISCSI_ERR_BUG; + } + + if (ioctl(sockfd, SIOCETHTOOL, &ifr) != 0) { + _warn(ctx, "IOCTL SIOCETHTOOL to %s failed: %d %s", if_name, + errno, _strerror(errno, strerr_buff)); + close(sockfd); + return LIBISCSI_ERR_BUG; + } + close(sockfd); + snprintf(driver_name, _ETH_DRIVER_NAME_MAX_LEN, "%s", drvinfo.driver); + + return LIBISCSI_OK; +} + +int _eth_ifs_get(struct iscsi_context *ctx, struct _eth_if ***eifs, + uint32_t *eif_count) +{ + int rc = LIBISCSI_OK; + struct if_nameindex *if_ni = NULL; + struct if_nameindex *if_i = NULL; + struct _eth_if *eif = NULL; + uint32_t tmp_count = 0; + + assert(ctx != NULL); + assert(eifs != NULL); + assert(eif_count != NULL); + + *eifs = NULL; + *eif_count = 0; + + if_ni = if_nameindex(); + _alloc_null_check(ctx, if_ni, rc, out); + + for (if_i = if_ni; if_i && if_i->if_index && if_i->if_name; ++if_i) + tmp_count++; + + if (tmp_count == 0) + goto out; + + *eifs = calloc(tmp_count, sizeof(struct _eth_if *)); + _alloc_null_check(ctx, *eifs, rc, out); + + for (if_i = if_ni; if_i && if_i->if_index && if_i->if_name; ++if_i) { + if (! _is_eth(ctx, if_i->if_name)) + continue; + eif = calloc(1, sizeof(struct _eth_if)); + _alloc_null_check(ctx, eif, rc, out); + (*eifs)[(*eif_count)++] = eif; + snprintf(eif->if_name, sizeof(eif->if_name)/sizeof(char), + "%s", if_i->if_name); + _good(_eth_driver_get(ctx, eif->if_name, eif->driver_name), + rc, out); + } + +out: + if (rc != LIBISCSI_OK) { + _eth_ifs_free(*eifs, *eif_count); + *eifs = NULL; + *eif_count = 0; + } + if (if_ni != NULL) + if_freenameindex(if_ni); + return rc; +} + +void _eth_ifs_free(struct _eth_if **eifs, uint32_t eif_count) +{ + uint32_t i = 0; + + if ((eif_count == 0) || (eifs == NULL)) + return; + + for (; i < eif_count; ++i) + free(eifs[i]); + free(eifs); +} + +void _scandir_free(struct dirent **namelist, int count) +{ + int i = 0; + + if ((namelist == NULL) || (count == 0)) + return; + + for (i = count - 1; i >= 0; --i) + free(namelist[i]); + free(namelist); +} + +int _scandir(struct iscsi_context *ctx, const char *dir_path, + struct dirent ***namelist, int *count) +{ + int rc = LIBISCSI_OK; + int errno_save = 0; + + assert(ctx != NULL); + assert(dir_path != NULL); + assert(namelist != NULL); + assert(count != NULL); + + *namelist = NULL; + *count = 0; + + *count = scandir(dir_path, namelist, _scan_filter_skip_dot, alphasort); + if (*count < 0) { + errno_save = errno; + if (errno_save == ENOENT) { + *count = 0; + goto out; + } + if (errno_save == ENOMEM) { + rc = LIBISCSI_ERR_NOMEM; + goto out; + } + if (errno_save == ENOTDIR) { + rc = LIBISCSI_ERR_BUG; + _error(ctx, "Got ENOTDIR error when scandir %s", + dir_path); + goto out; + } + rc = LIBISCSI_ERR_BUG; + _error(ctx, "Got unexpected error %d when scandir %s", + errno_save, dir_path); + goto out; + } + +out: + if (rc != LIBISCSI_OK) { + _scandir_free(*namelist, *count); + *namelist = NULL; + *count = 0; + } + + return rc; +} diff --git a/libopeniscsiusr/misc.h b/libopeniscsiusr/misc.h new file mode 100644 index 0000000..ca46726 --- /dev/null +++ b/libopeniscsiusr/misc.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ +#ifndef __ISCSI_USR_MISC_H__ +#define __ISCSI_USR_MISC_H__ + +#include +#include +#include +#include +#include +#include + +#include "libopeniscsiusr/libopeniscsiusr.h" + +#define _good(rc, rc_val, out) \ + do { \ + rc_val = rc; \ + if (rc_val != LIBISCSI_OK) \ + goto out; \ + } while(0) + +#define _asprintf(...) \ + (asprintf(__VA_ARGS__) == -1 ? LIBISCSI_ERR_NOMEM : LIBISCSI_OK) + +__DLL_LOCAL void _iscsi_log(struct iscsi_context *ctx, int priority, + const char *file, int line, const char *func_name, + const char *format, ...); +__DLL_LOCAL void _iscsi_log_stderr(struct iscsi_context *ctx, int priority, + const char *file, int line, + const char *func_name, const char *format, + va_list args); + +#define _iscsi_log_cond(ctx, prio, arg...) \ + do { \ + if ((ctx != NULL) && \ + (iscsi_context_log_priority_get(ctx) >= prio)) \ + _iscsi_log(ctx, prio, __FILE__, __LINE__, \ + __FUNCTION__, ## arg); \ + } while (0) + +#define _debug(ctx, arg...) \ + _iscsi_log_cond(ctx, LIBISCSI_LOG_PRIORITY_DEBUG, ## arg) +#define _info(ctx, arg...) \ + _iscsi_log_cond(ctx, LIBISCSI_LOG_PRIORITY_INFO, ## arg) +#define _warn(ctx, arg...) \ + _iscsi_log_cond(ctx, LIBISCSI_LOG_PRIORITY_WARNING, ## arg) +#define _error(ctx, arg...) \ + _iscsi_log_cond(ctx, LIBISCSI_LOG_PRIORITY_ERROR, ## arg) + +#define _iscsi_getter_func_gen(struct_name, prop_name, prop_type) \ + prop_type struct_name##_##prop_name##_get(struct struct_name *d) \ + { \ + assert(d != NULL); \ + return d->prop_name; \ + } + +/* + * Check pointer returned by malloc() or strdup() or calloc(), if NULL, set + * rc as LIBISCSI_ERR_NO_MEMORY, report error and goto goto_out. + */ +#define _alloc_null_check(ctx, ptr, rc, goto_out) \ + do { \ + if (ptr == NULL) { \ + rc = LIBISCSI_ERR_NOMEM; \ + _error(ctx, iscsi_strerror(rc)); \ + goto goto_out; \ + } \ + } while(0) + +#define _STRERR_BUFF_LEN 1024 +#define _strerror(err_no, buff) \ + strerror_r(err_no, buff, _STRERR_BUFF_LEN) + +/* Workaround for suppress GCC 8 `stringop-truncation` warnings. */ +#define _strncpy(dst, src, size) \ + do { \ + memcpy(dst, src, \ + (size_t) size > strlen(src) ? \ + strlen(src) : (size_t) size); \ + * (char *) (dst + \ + ((size_t) size - 1 > strlen(src) ? \ + strlen(src) : (size_t) (size - 1))) = '\0'; \ + } while(0) + +__DLL_LOCAL int _scan_filter_skip_dot(const struct dirent *dir); + +__DLL_LOCAL bool _file_exists(const char *path); + + +#define _ETH_DRIVER_NAME_MAX_LEN 32 +/* ^ Defined in linux/ethtool.h `struct ethtool_drvinfo`. */ + +struct _eth_if { + char driver_name[_ETH_DRIVER_NAME_MAX_LEN]; + char if_name[IF_NAMESIZE]; +}; + +__DLL_LOCAL int _eth_ifs_get(struct iscsi_context *ctx, + struct _eth_if ***eifs, uint32_t *eif_count); + +__DLL_LOCAL void _eth_ifs_free(struct _eth_if **eifs, uint32_t eif_count); + +__DLL_LOCAL int _scandir(struct iscsi_context *ctx, const char *dir_path, + struct dirent ***namelist, int *count); +__DLL_LOCAL void _scandir_free(struct dirent **namelist, int count); + +#endif /* End of __ISCSI_USR_MISC_H__ */ diff --git a/libopeniscsiusr/node.c b/libopeniscsiusr/node.c new file mode 100644 index 0000000..6bec201 --- /dev/null +++ b/libopeniscsiusr/node.c @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2018 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +/* ^ For strerror_r() */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libopeniscsiusr/libopeniscsiusr.h" +#include "misc.h" +#include "node.h" + +/* ptr is both input and output pointer. + * count is both input and output pointer. + * When success, both count and ptr will be updated. + * If fail, return LIBISCSI_ERR_NOMEM and no touch to old memory. + */ +static int _grow_node_array(struct iscsi_context *ctx, + struct iscsi_node ***nodes, uint32_t *count) +{ + int rc = LIBISCSI_OK; + struct iscsi_node **tmp = NULL; + uint32_t i = 0; + + _debug(ctx, "Growing node array from size %" PRIu32 " to %" PRIu32, + *count, *count * 2); + + tmp = realloc(*nodes, *count * 2 * sizeof(struct iscsi_node *)); + _alloc_null_check(ctx, tmp, rc, out); + for (i = *count; i < *count * 2; ++i) + tmp[i] = NULL; + + *count *= 2; + *nodes = tmp; + +out: + return rc; +} + +static int nodes_append(struct iscsi_context *ctx, struct iscsi_node ***nodes, + uint32_t *real_node_count, uint32_t *array_size, + struct iscsi_node *node) +{ + int rc = LIBISCSI_OK; + if (*real_node_count >= *array_size) + _good(_grow_node_array(ctx, nodes, array_size), rc, out); + + (*nodes)[(*real_node_count)++] = node; + +out: + return rc; +} + +int iscsi_nodes_get(struct iscsi_context *ctx, struct iscsi_node ***nodes, + uint32_t *node_count) +{ + int rc = LIBISCSI_OK; + struct dirent **namelist = NULL; + int n = 0; + int i = 0; + int j = 0; + int k = 0; + struct iscsi_node *node = NULL; + uint32_t real_node_count = 0; + const char *target_name = NULL; + const char *portal = NULL; + const char *iface_name = NULL; + struct dirent **namelist_portals = NULL; + int p = 0; + struct dirent **namelist_ifaces = NULL; + int f = 0; + char *target_path = NULL; + char *path = NULL; + struct stat path_stat; + char strerr_buff[_STRERR_BUFF_LEN]; + + assert(ctx != NULL); + assert(nodes != NULL); + assert(node_count != NULL); + + *nodes = NULL; + *node_count = 0; + + _good(_idbm_lock(ctx), rc, out); + + _good(_scandir(ctx, NODE_CONFIG_DIR, &namelist, &n), rc, out); + _debug(ctx, "Got %d target from %s nodes folder", n, NODE_CONFIG_DIR); + *node_count = n & UINT32_MAX; + *nodes = (struct iscsi_node **) calloc(*node_count, + sizeof(struct iscsi_node *)); + _alloc_null_check(ctx, *nodes, rc, out); + + // New style of nodes folder: + // /
,,/ + // Old style of nodes folder: + // /
, + + for (i = 0; i < n; ++i) { + target_name = namelist[i]->d_name; + _good(_asprintf(&target_path, "%s/%s", NODE_CONFIG_DIR, + target_name), rc, out); + _good(_scandir(ctx, target_path, &namelist_portals, &p), + rc, out); + _debug(ctx, "Got %d portals from %s folder", p, target_path); + free(target_path); + target_path = NULL; + for (j = 0; j < p; ++j) { + portal = namelist_portals[j]->d_name; + _good(_asprintf(&path, "%s/%s/%s", NODE_CONFIG_DIR, + target_name, portal), rc, out); + if (stat(path, &path_stat) != 0) { + _warn(ctx, "Cannot stat path '%s': %d, %s", + path, errno, + _strerror(errno, strerr_buff)); + continue; + } + if (S_ISREG(path_stat.st_mode)) { + // Old style of node + _good(_idbm_node_get(ctx, target_name, portal, + NULL, &node), + rc, out); + _good(nodes_append(ctx, nodes, + &real_node_count, + node_count, node), + rc, out); + continue; + } + if (! S_ISDIR(path_stat.st_mode)) { + _warn(ctx, "Invalid iSCSI node configuration " + "file %s, it should be a file or " + "directory.", path); + rc = LIBISCSI_ERR_IDBM; + goto out; + } + _good(_scandir(ctx, path, &namelist_ifaces, &f), rc, + out); + _debug(ctx, "Got %d ifaces from %s folder", f, path); + for (k = 0; k < f; ++k) { + iface_name = namelist_ifaces[k]->d_name; + _good(_idbm_node_get(ctx, target_name, portal, + iface_name, &node), + rc, out); + _good(nodes_append(ctx, nodes, + &real_node_count, + node_count, node), + rc, out); + } + free(path); + path = NULL; + _scandir_free(namelist_ifaces, f); + namelist_ifaces = NULL; + f = 0; + } + _scandir_free(namelist_portals, p); + namelist_portals = NULL; + p = 0; + } + + *node_count = real_node_count; + +out: + free(path); + free(target_path); + _scandir_free(namelist, n); + _scandir_free(namelist_portals, p); + _scandir_free(namelist_ifaces, f); + _idbm_unlock(ctx); + if (rc != LIBISCSI_OK) { + iscsi_nodes_free(*nodes, *node_count); + *nodes = NULL; + *node_count = 0; + } + return rc; +} + +void iscsi_nodes_free(struct iscsi_node **nodes, uint32_t node_count) +{ + uint32_t i = 0; + + if ((nodes == NULL) || (node_count == 0)) + return; + + for (i = 0; i < node_count; ++i) + iscsi_node_free(nodes[i]); + free (nodes); +} + +void iscsi_node_free(struct iscsi_node *node) +{ + free(node); +} + +const char *iscsi_node_dump_config(struct iscsi_node *node, bool show_secret) +{ + FILE *f = NULL; + char *buff = NULL; + + assert(node != NULL); + + buff = calloc(1, IDBM_DUMP_SIZE); + if (buff == NULL) + return NULL; + + f = fmemopen(buff, IDBM_DUMP_SIZE - 1, "w"); + if (f == NULL) { + free(buff); + return NULL; + } + + _idbm_node_print(node, f, show_secret); + + fclose(f); + + return buff; +} + +void iscsi_node_print_config(struct iscsi_node *node, bool show_secret) +{ + assert(node != NULL); + _idbm_node_print(node, stdout, show_secret); +} + +// TODO(Gris Ge): Convert below duplicated codes to macros. +bool iscsi_node_conn_is_ipv6(struct iscsi_node *node) +{ + assert(node != NULL); + return node->conn.is_ipv6; +} + +const char *iscsi_node_conn_address_get(struct iscsi_node *node) +{ + assert(node != NULL); + return node->conn.address; +} + +uint32_t iscsi_node_conn_port_get(struct iscsi_node *node) +{ + assert(node != NULL); + return node->conn.port; +} + +int32_t iscsi_node_tpgt_get(struct iscsi_node *node) +{ + assert(node != NULL); + return node->tpgt; +} + +const char *iscsi_node_target_name_get(struct iscsi_node *node) +{ + assert(node != NULL); + return node->target_name; +} + +const char *iscsi_node_iface_name_get(struct iscsi_node *node) +{ + assert(node != NULL); + return node->iface.name; +} + +const char *iscsi_node_portal_get(struct iscsi_node *node) +{ + assert(node != NULL); + return node->portal; +} diff --git a/libopeniscsiusr/node.h b/libopeniscsiusr/node.h new file mode 100644 index 0000000..39e07b3 --- /dev/null +++ b/libopeniscsiusr/node.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE /* For NI_MAXHOST */ +#endif + +#ifndef __ISCSI_USR_NODE_H__ +#define __ISCSI_USR_NODE_H__ + +#include +#include + +#include "idbm.h" +#include "iface.h" + +struct iscsi_node { + char target_name[TARGET_NAME_MAXLEN]; + int32_t tpgt; + enum iscsi_startup_type startup; + enum leading_login_type leading_login; + struct iscsi_session_idbm session; + struct iscsi_conn conn; + struct iscsi_iface iface; + enum discovery_type disc_type; + char disc_address[NI_MAXHOST]; + int32_t disc_port; + char portal[NI_MAXHOST * 2]; +}; + +#define NODE_CONFIG_DIR ISCSI_CONFIG_ROOT"nodes" + +/* Might be public in the future */ +__DLL_LOCAL void iscsi_node_free(struct iscsi_node *node); + +#endif /* End of __ISCSI_USR_NODE_H__ */ diff --git a/libopeniscsiusr/rfc.h b/libopeniscsiusr/rfc.h new file mode 100644 index 0000000..8f60652 --- /dev/null +++ b/libopeniscsiusr/rfc.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ + + +#ifndef _LIB_OPEN_ISCSI_USR_RFC_H_ +#define _LIB_OPEN_ISCSI_USR_RFC_H_ + +#define ISCSI_DEFAULT_PORT 3260 +#define ISCSI_DEF_TIME2WAIT 2 + +#endif /* End of _LIB_OPEN_ISCSI_USR_RFC_H_ */ diff --git a/libopeniscsiusr/session.c b/libopeniscsiusr/session.c new file mode 100644 index 0000000..98601dc --- /dev/null +++ b/libopeniscsiusr/session.c @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE /* For NI_MAXHOST */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libopeniscsiusr/libopeniscsiusr.h" +#include "misc.h" +#include "sysfs.h" +#include "iface.h" + +#define _ISCSI_NAME_MAX_LEN 223 +/* ^ RFC 3720: + * Each iSCSI node, whether an initiator or target, MUST have an iSCSI + * name. + * + * Initiators and targets MUST support the receipt of iSCSI names of up + * to the maximum length of 223 bytes. + */ + +#define _ISCSI_CHAP_AUTH_STR_MAX_LEN 256 +/* ^ No official document found for this value, just copy from usr/auth.h + */ + +struct iscsi_session { + uint32_t sid; + /* ^ It's actually a int according to Linux kernel code but + * the dev_set_name() in iscsi_add_session() of scsi_transport_iscsi.c + * are using %u to output this. + */ + char persistent_address[NI_MAXHOST + 1]; + int32_t persistent_port; + char target_name[_ISCSI_NAME_MAX_LEN + 1]; + char username[_ISCSI_CHAP_AUTH_STR_MAX_LEN]; + char password[_ISCSI_CHAP_AUTH_STR_MAX_LEN]; + char username_in[_ISCSI_CHAP_AUTH_STR_MAX_LEN]; + char password_in[_ISCSI_CHAP_AUTH_STR_MAX_LEN]; + int32_t recovery_tmo; + /* ^ It's actually a int according to Linux kernel code. + */ + int32_t lu_reset_tmo; + /* ^ It's actually a int according to Linux kernel code. + */ + int32_t tgt_reset_tmo; + /* ^ It's actually a int according to Linux kernel code. + */ + int32_t abort_tmo; + /* ^ It's actually a int according to Linux kernel code. + */ + int32_t tpgt; + /* ^ It's actually a int according to Linux kernel code. + */ + char address[NI_MAXHOST + 1]; + + int32_t port; + struct iscsi_iface *iface; +}; + +_iscsi_getter_func_gen(iscsi_session, sid, uint32_t); +_iscsi_getter_func_gen(iscsi_session, persistent_address, const char *); +_iscsi_getter_func_gen(iscsi_session, persistent_port, int32_t); +_iscsi_getter_func_gen(iscsi_session, target_name, const char *); +_iscsi_getter_func_gen(iscsi_session, username, const char *); +_iscsi_getter_func_gen(iscsi_session, password, const char *); +_iscsi_getter_func_gen(iscsi_session, username_in, const char *); +_iscsi_getter_func_gen(iscsi_session, password_in, const char *); +_iscsi_getter_func_gen(iscsi_session, recovery_tmo, int32_t); +_iscsi_getter_func_gen(iscsi_session, lu_reset_tmo, int32_t); +_iscsi_getter_func_gen(iscsi_session, tgt_reset_tmo, int32_t); +_iscsi_getter_func_gen(iscsi_session, abort_tmo, int32_t); +_iscsi_getter_func_gen(iscsi_session, tpgt, int32_t); +_iscsi_getter_func_gen(iscsi_session, address, const char *); +_iscsi_getter_func_gen(iscsi_session, port, int32_t); +_iscsi_getter_func_gen(iscsi_session, iface, struct iscsi_iface *); + +int iscsi_session_get(struct iscsi_context *ctx, uint32_t sid, + struct iscsi_session **se) +{ + int rc = LIBISCSI_OK; + char *sysfs_se_dir_path = NULL; + char *sysfs_con_dir_path = NULL; + uint32_t host_id = 0; + + assert(ctx != NULL); + assert(se != NULL); + + _debug(ctx, "Querying iSCSI session for sid %" PRIu32, sid); + + _good(_asprintf(&sysfs_se_dir_path, "%s/session%" PRIu32, + _ISCSI_SYS_SESSION_DIR, sid), rc, out); + _good(_asprintf(&sysfs_con_dir_path, "%s/connection%" PRIu32 ":0", + _ISCSI_SYS_CONNECTION_DIR, sid), rc, out); + /* ^ BUG(Gris Ge): ':0' here in kernel is referred as connection id. + * but the open-iscsi assuming it's always 0, need + * investigation. + */ + + *se = (struct iscsi_session *) calloc(1, sizeof(struct iscsi_session)); + _alloc_null_check(ctx, *se , rc, out); + + if (! _file_exists(sysfs_se_dir_path)) { + _info(ctx, "Sysfs path '%s' does not exists", + sysfs_se_dir_path); + rc = LIBISCSI_ERR_SESS_NOT_FOUND; + } + if (! _file_exists(sysfs_con_dir_path)) { + _info(ctx, "Sysfs path '%s' does not exists", + sysfs_se_dir_path); + rc = LIBISCSI_ERR_SESS_NOT_FOUND; + } + if (rc == LIBISCSI_ERR_SESS_NOT_FOUND) { + _error(ctx, "Specified SID %" PRIu32, "does not exists", + sid); + goto out; + } + + (*se)->sid = sid; + _good(_sysfs_prop_get_str(ctx, sysfs_se_dir_path, "targetname", + (*se)->target_name, + sizeof((*se)->target_name) / sizeof(char), + NULL), rc, out); + + _good(_sysfs_prop_get_str(ctx, sysfs_se_dir_path, "username", + (*se)->username, + sizeof((*se)->username) / sizeof(char), + ""), + rc, out); + + _good(_sysfs_prop_get_str(ctx, sysfs_se_dir_path, "password", + (*se)->password, + sizeof((*se)->password) / sizeof(char), + ""), + rc, out); + + _good(_sysfs_prop_get_str(ctx, sysfs_se_dir_path, "username_in", + (*se)->username_in, + sizeof((*se)->username_in) / sizeof(char), + ""), + rc, out); + + _good(_sysfs_prop_get_str(ctx, sysfs_se_dir_path, "password_in", + (*se)->password_in, + sizeof((*se)->password_in) / sizeof(char), + ""), + rc, out); + + _good(_sysfs_prop_get_i32(ctx, sysfs_se_dir_path, "recovery_tmo", + &((*se)->recovery_tmo), -1, true), + rc, out); + + _good(_sysfs_prop_get_i32(ctx, sysfs_se_dir_path, "lu_reset_tmo", + &((*se)->lu_reset_tmo), -1, true), + rc, out); + + _good(_sysfs_prop_get_i32(ctx, sysfs_se_dir_path, "tgt_reset_tmo", + &((*se)->tgt_reset_tmo), -1, true), + rc, out); + + _good(_sysfs_prop_get_i32(ctx, sysfs_se_dir_path, "abort_tmo", + &((*se)->abort_tmo), -1, true), + rc, out); + + _good(_sysfs_prop_get_i32(ctx, sysfs_se_dir_path, "tpgt", + &((*se)->tpgt), -1, true), + rc, out); + + _good(_sysfs_prop_get_str(ctx, sysfs_con_dir_path, "persistent_address", + (*se)->persistent_address, + sizeof((*se)->persistent_address) / + sizeof(char), ""), + rc, out); + + _good(_sysfs_prop_get_i32(ctx, sysfs_con_dir_path, "persistent_port", + &((*se)->persistent_port), -1, true), + rc, out); + + _sysfs_prop_get_str(ctx, sysfs_con_dir_path, "address", (*se)->address, + sizeof((*se)->address) / sizeof(char), ""); + + _sysfs_prop_get_i32(ctx, sysfs_con_dir_path, "port", + &((*se)->port), -1, true); + + if ((strcmp((*se)->address, "") != 0) && + (strcmp((*se)->persistent_address, "") == 0)) + _strncpy((*se)->persistent_address, (*se)->address, + sizeof((*se)->persistent_address) / sizeof(char)); + else if ((strcmp((*se)->address, "") == 0) && + (strcmp((*se)->persistent_address, "") != 0)) + _strncpy((*se)->address, (*se)->persistent_address, + sizeof((*se)->address) / sizeof(char)); + + if (((*se)->persistent_port == -1) && + ((*se)->port != -1)) + (*se)->persistent_port = (*se)->port; + else if (((*se)->persistent_port != -1) && + ((*se)->port == -1)) + (*se)->port = (*se)->persistent_port; + + _good(_iscsi_host_id_of_session(ctx, sid, &host_id), rc, out); + + /* does this need to the correct iface_kern_id for the session? */ + _good(_iscsi_iface_get_from_sysfs(ctx, host_id, sid, NULL, &((*se)->iface)), + rc, out); + +out: + if (rc != LIBISCSI_OK) { + iscsi_session_free(*se); + *se = NULL; + } + free(sysfs_se_dir_path); + free(sysfs_con_dir_path); + return rc; +} + +int iscsi_sessions_get(struct iscsi_context *ctx, + struct iscsi_session ***sessions, + uint32_t *session_count) +{ + int rc = LIBISCSI_OK; + uint32_t i = 0; + uint32_t *sids = NULL; + + assert(ctx != NULL); + assert(sessions != NULL); + assert(session_count != NULL); + + *sessions = NULL; + *session_count = 0; + + _good(_iscsi_sids_get(ctx, &sids, session_count), rc ,out); + + *sessions = calloc (*session_count, sizeof(struct iscsi_session *)); + _alloc_null_check(ctx, *sessions, rc, out); + + for (i = 0; i < *session_count; ++i) { + _debug(ctx, "sid %" PRIu32, sids[i]); + _good(iscsi_session_get(ctx, sids[i], &((*sessions)[i])), + rc, out); + } + +out: + free(sids); + if (rc != LIBISCSI_OK) { + iscsi_sessions_free(*sessions, *session_count); + *sessions = NULL; + *session_count = 0; + } + return rc; +} + +void iscsi_session_free(struct iscsi_session *se) +{ + if (se != NULL) + iscsi_iface_free(se->iface); + free(se); +} + +void iscsi_sessions_free(struct iscsi_session **ses, uint32_t se_count) +{ + uint32_t i = 0; + + if ((ses == NULL) || (se_count == 0)) + return; + + for (i = 0; i < se_count; ++i) + iscsi_session_free(ses[i]); + free (ses); +} diff --git a/libopeniscsiusr/sysfs.c b/libopeniscsiusr/sysfs.c new file mode 100644 index 0000000..2c3f077 --- /dev/null +++ b/libopeniscsiusr/sysfs.c @@ -0,0 +1,570 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libopeniscsiusr/libopeniscsiusr_common.h" +#include "sysfs.h" +#include "misc.h" + +#define _INT32_STR_MAX_LEN 12 +/* ^ The max uint32_t is 4294967296 which requires 11 bytes for string. + * The max/min in32_t is 2147483647 or -2147483646 which requires 12 bytes. + */ + +#define _SYS_NULL_STR "(null)" + +#define _sysfs_prop_get_uint_func_gen(func_name, out_type, type_max_value) \ + int func_name(struct iscsi_context *ctx, const char *dir_path, \ + const char *prop_name, out_type *val, \ + out_type default_value, bool ignore_error) \ + { \ + long long int tmp_val = 0; \ + int rc = LIBISCSI_OK; \ + long long int dv = default_value; \ + rc = iscsi_sysfs_prop_get_ll(ctx, dir_path, prop_name, \ + &tmp_val, (long long int) dv, \ + ignore_error); \ + if (rc == LIBISCSI_OK) \ + *val = tmp_val & type_max_value; \ + return rc; \ + } + +#define _sysfs_prop_get_int_func_gen(func_name, out_type, type_min_value, type_max_value) \ + int func_name(struct iscsi_context *ctx, const char *dir_path, \ + const char *prop_name, out_type *val, \ + out_type default_value, bool ignore_error) \ + { \ + long long int tmp_val = 0; \ + int rc = LIBISCSI_OK; \ + long long int dv = default_value; \ + rc = iscsi_sysfs_prop_get_ll(ctx, dir_path, prop_name, \ + &tmp_val, (long long int) dv, \ + ignore_error); \ + if (rc == LIBISCSI_OK) { \ + if (tmp_val > type_max_value) \ + *val = type_max_value; \ + else if (tmp_val < type_min_value) \ + *val = type_min_value; \ + else \ + *val = tmp_val; \ + } \ + return rc; \ + } + + +enum _sysfs_dev_class { + _SYSFS_DEV_CLASS_ISCSI_SESSION, + _SYSFS_DEV_CLASS_ISCSI_HOST, +}; + +static int sysfs_read_file(const char *path, uint8_t *buff, size_t buff_size); +static int iscsi_sysfs_prop_get_ll(struct iscsi_context *ctx, + const char *dir_path, const char *prop_name, + long long int *val, + long long int default_value, + bool ignore_error); + +/* + * dev_path needs to be freed by the caller on success + */ +static int sysfs_get_dev_path(struct iscsi_context *ctx, const char *path, + enum _sysfs_dev_class class, char **dev_path); + +_sysfs_prop_get_uint_func_gen(_sysfs_prop_get_u8, uint8_t, UINT8_MAX); +_sysfs_prop_get_uint_func_gen(_sysfs_prop_get_u16, uint16_t, UINT16_MAX); +_sysfs_prop_get_int_func_gen(_sysfs_prop_get_i32, int32_t, INT32_MIN, INT32_MAX); +_sysfs_prop_get_uint_func_gen(_sysfs_prop_get_u32, uint32_t, UINT32_MAX); + +static int sysfs_read_file(const char *path, uint8_t *buff, size_t buff_size) +{ + int fd = -1; + int errno_save = 0; + ssize_t readed = 0; + ssize_t i = 0; + + assert(path != NULL); + assert(buff != NULL); + assert(buff_size != 0); + + memset(buff, 0, buff_size); + + fd = open(path, O_RDONLY); + if (fd < 0) + return errno; + readed = read(fd, buff, buff_size); + errno_save = errno; + close(fd); + + if (readed < 0) { + buff[0] = '\0'; + return errno_save; + } + + buff[buff_size - 1] = '\0'; + /* Remove the trailing \n */ + for (i = readed - 1; i >= 0; --i) { + if (buff[i] == '\n') { + buff[i] = '\0'; + break; + } + } + + if (strcmp((char *) buff, _SYS_NULL_STR) == 0) + buff[0] = '\0'; + + return 0; +} + +int _sysfs_prop_get_str(struct iscsi_context *ctx, const char *dir_path, + const char *prop_name, char *buff, size_t buff_size, + const char *default_value) +{ + char *file_path = NULL; + int rc = LIBISCSI_OK; + int errno_save = 0; + + assert(dir_path != NULL); + assert(prop_name != NULL); + assert(buff != NULL); + + _good(_asprintf(&file_path, "%s/%s", dir_path, prop_name), rc, out); + + errno_save = sysfs_read_file(file_path, (uint8_t *) buff, buff_size); + if (errno_save != 0) { + if (errno_save == ENOENT) { + if (default_value == NULL) { + rc = LIBISCSI_ERR_SYSFS_LOOKUP; + _error(ctx, "Failed to read '%s': " + "file '%s' does not exists", prop_name, + file_path); + } else { + _info(ctx, "Failed to read '%s': " + "file '%s' does not exists, " + "using default value %s", prop_name, + file_path, default_value); + memcpy(buff, (void *) default_value, + strlen(default_value) + 1); + } + } else if (errno_save == EACCES) { + rc = LIBISCSI_ERR_ACCESS; + _error(ctx, "Failed to read '%s': " + "permission deny when reading '%s'", prop_name, + file_path); + } else if (errno_save == ENOTCONN) { + if (default_value == NULL) { + rc = LIBISCSI_ERR_SYSFS_LOOKUP; + _error(ctx, "Failed to read '%s': " + "error when reading '%s': " + "Target unavailable", + prop_name, file_path); + } else { + _info(ctx, "Failed to read '%s': " + "error when reading '%s': " + "Target unavailable, using default value '%s'", + prop_name, file_path, default_value); + memcpy(buff, (void *) default_value, + strlen(default_value) + 1); + } + } else { + rc = LIBISCSI_ERR_BUG; + _error(ctx, "Failed to read '%s': " + "error when reading '%s': %d", prop_name, + file_path, errno_save); + } + } else { + if ((buff[0] == '\0') && (default_value != NULL)) { + memcpy(buff, (void *) default_value, + strlen(default_value) + 1); + _debug(ctx, "Open '%s', got NULL, using default value", + file_path, default_value); + } else + _debug(ctx, "Open '%s', got '%s'", file_path, buff); + } +out: + free(file_path); + return rc; +} + +static int iscsi_sysfs_prop_get_ll(struct iscsi_context *ctx, + const char *dir_path, const char *prop_name, + long long int *val, + long long int default_value, bool ignore_error) +{ + char *file_path = NULL; + int rc = LIBISCSI_OK; + int errno_save = 0; + uint8_t buff[_INT32_STR_MAX_LEN]; + long long int tmp_val = 0; + + assert(dir_path != NULL); + assert(prop_name != NULL); + assert(val != NULL); + + *val = 0; + + _good(_asprintf(&file_path, "%s/%s", dir_path, prop_name), rc, out); + + errno_save = sysfs_read_file(file_path, buff, _INT32_STR_MAX_LEN); + if (errno_save != 0) { + if (errno_save == ENOENT) { + if (! ignore_error) { + rc = LIBISCSI_ERR_SYSFS_LOOKUP; + _error(ctx, "Failed to read '%s': " + "file '%s' does not exists", + prop_name, file_path); + goto out; + } else { + _info(ctx, + "Failed to read '%s': " + "File '%s' does not exists, using ", + "default value %lld", + prop_name, file_path, default_value); + *val = default_value; + goto out; + } + } else if (errno_save == EACCES) { + rc = LIBISCSI_ERR_ACCESS; + _error(ctx, "Permission deny when reading '%s'", + file_path); + goto out; + } else if (errno_save == ENOTCONN) { + if (!ignore_error) { + rc = LIBISCSI_ERR_SYSFS_LOOKUP; + _error(ctx, "Failed to read '%s': " + "error when reading '%s': " + "Target unavailable", + prop_name, file_path); + goto out; + } else { + _info(ctx, "Failed to read '%s': " + "error when reading '%s': " + "Target unavailable, using default value %lld", + prop_name, file_path, default_value); + *val = default_value; + goto out; + } + } else { + rc = LIBISCSI_ERR_BUG; + _error(ctx, "Error when reading '%s': %d", file_path, + errno_save); + goto out; + } + } + + errno = 0; + tmp_val = strtoll((const char *) buff, NULL, 10 /* base */); + errno_save = errno; + if ((errno_save != 0) && (! ignore_error)) { + rc = LIBISCSI_ERR_BUG; + _error(ctx, "Sysfs: %s: Error when converting '%s' " + "to number", file_path, (char *) buff, errno_save); + goto out; + } + + *val = tmp_val; + + _debug(ctx, "Open '%s', got %lld", file_path, tmp_val); +out: + free(file_path); + return rc; +} + +static int sysfs_get_dev_path(struct iscsi_context *ctx, const char *path, + enum _sysfs_dev_class class, char **dev_path) +{ + int rc = LIBISCSI_OK; + int errno_save = 0; + regex_t regex; + regmatch_t reg_match[2]; + int reg_rc = 0; + int need_free_reg = 0; + + assert(ctx != NULL); + assert(path != NULL); + assert(dev_path != NULL); + + *dev_path = realpath(path, NULL); + if (*dev_path == NULL) { + errno_save = errno; + rc = LIBISCSI_ERR_SYSFS_LOOKUP; + _error(ctx, "realpath() failed on %s with error %d", path, + errno_save); + goto out; + } + + switch (class) { + case _SYSFS_DEV_CLASS_ISCSI_SESSION: + reg_rc = regcomp(®ex, + "\\(.\\{1,\\}/devices/.\\{1,\\}/" + "host[0-9]\\{1,\\}\\)/" + "session[0-9]\\{1,\\}/iscsi_session/", + 0 /* no flag */); + break; + case _SYSFS_DEV_CLASS_ISCSI_HOST: + reg_rc = regcomp(®ex, + "\\(.\\{1,\\}/devices/.\\{1,\\}/" + "host[0-9]\\{1,\\}\\)/" + "iscsi_host/", + 0 /* no flag */); + break; + default: + rc = LIBISCSI_ERR_BUG; + _error(ctx, "BUG: sysfs_get_dev_path(): got unknown class %d", + class); + goto out; + } + if (reg_rc != 0) { + rc = LIBISCSI_ERR_SYSFS_LOOKUP; + _error(ctx, "regcomp() failed %d", reg_rc); + goto out; + } + need_free_reg = 1; + if (regexec(®ex, *dev_path, 2 /* count of max matches */, + reg_match, 0 /* no flags */) != 0) { + rc = LIBISCSI_ERR_SYSFS_LOOKUP; + _error(ctx, "regexec() not match for %s", *dev_path); + goto out; + } + + *(*dev_path + reg_match[1].rm_eo ) = '\0'; + + _debug(ctx, "Got dev path of '%s': '%s'", path, *dev_path); + +out: + if (need_free_reg) + regfree(®ex); + if (rc != LIBISCSI_OK) { + free(*dev_path); + *dev_path = NULL; + } + return rc; +} + +int _iscsi_host_id_of_session(struct iscsi_context *ctx, uint32_t sid, + uint32_t *host_id) +{ + int rc = LIBISCSI_OK; + char *sys_se_dir_path = NULL; + char *sys_dev_path = NULL; + char *sys_scsi_host_dir_path = NULL; + struct dirent **namelist = NULL; + int n = 0; + const char *host_id_str = NULL; + const char iscsi_host_dir_str[] = "/iscsi_host/"; + + assert(ctx != NULL); + assert(sid != 0); + assert(host_id != NULL); + + _good(_asprintf(&sys_se_dir_path, "%s/session%" PRIu32, + _ISCSI_SYS_SESSION_DIR, sid), rc, out); + + *host_id = 0; + + _good(sysfs_get_dev_path(ctx, sys_se_dir_path, + _SYSFS_DEV_CLASS_ISCSI_SESSION, &sys_dev_path), + rc, out); + + _good(_asprintf(&sys_scsi_host_dir_path, "%s%s", + sys_dev_path, iscsi_host_dir_str), rc, out); + + _good(_scandir(ctx, sys_scsi_host_dir_path, &namelist, &n), rc, out); + + if (n != 1) { + rc = LIBISCSI_ERR_SYSFS_LOOKUP; + _error(ctx, "Got unexpected(should be 1) file in folder %s", + sys_scsi_host_dir_path); + goto out; + } + host_id_str = namelist[0]->d_name; + + if (sscanf(host_id_str, "host%" SCNu32, host_id) != 1) { + rc = LIBISCSI_ERR_SYSFS_LOOKUP; + _error(ctx, "sscanf() failed on string %s", host_id_str); + goto out; + } + +out: + _scandir_free(namelist, n); + free(sys_se_dir_path); + free(sys_dev_path); + free(sys_scsi_host_dir_path); + return rc; +} + +static int _iscsi_ids_get(struct iscsi_context *ctx, + uint32_t **ids, uint32_t *id_count, + const char *dir_path, const char *file_prefix) +{ + int rc = LIBISCSI_OK; + struct dirent **namelist = NULL; + int n = 0; + uint32_t i = 0; + const char *id_str = NULL; + char fmt_buff[128]; + + assert(ctx != NULL); + assert(ids != 0); + assert(id_count != NULL); + + *ids = NULL; + *id_count = 0; + + _good(_scandir(ctx, dir_path, &namelist, &n), rc, out); + _debug(ctx, "Got %d iSCSI %s", n, file_prefix); + + *id_count = n & UINT32_MAX; + + *ids = calloc(*id_count, sizeof(uint32_t)); + _alloc_null_check(ctx, *ids, rc, out); + + snprintf(fmt_buff, sizeof(fmt_buff)/sizeof(char), "%s%%" SCNu32, + file_prefix); + + for (i = 0; i < *id_count; ++i) { + id_str = namelist[i]->d_name; + if (sscanf(id_str, fmt_buff, &((*ids)[i])) != 1) { + rc = LIBISCSI_ERR_SYSFS_LOOKUP; + _error(ctx, "sscanf() failed on string %s", + id_str); + goto out; + } + _debug(ctx, "Got iSCSI %s id %" PRIu32, file_prefix, (*ids)[i]); + } + +out: + _scandir_free(namelist, n); + if (rc != LIBISCSI_OK) { + free(*ids); + *ids = NULL; + *id_count = 0; + } + return rc; +} + +int _iscsi_sids_get(struct iscsi_context *ctx, uint32_t **sids, + uint32_t *sid_count) +{ + return _iscsi_ids_get(ctx, sids, sid_count, _ISCSI_SYS_SESSION_DIR, + "session"); +} + +int _iscsi_hids_get(struct iscsi_context *ctx, uint32_t **hids, + uint32_t *hid_count) +{ + return _iscsi_ids_get(ctx, hids, hid_count, _ISCSI_SYS_HOST_DIR, + "host"); +} + +bool _iscsi_transport_is_loaded(const char *transport_name) +{ + int rc = LIBISCSI_OK; + char *path = NULL; + + if (transport_name == NULL) + return false; + + _good(_asprintf(&path, "%s/%s", _ISCSI_SYS_TRANSPORT_DIR, + transport_name), rc, out); + + if (access(path, F_OK) == 0) { + free(path); + return true; + } +out: + free(path); + return false; +} + +int _iscsi_iface_kern_ids_of_host_id(struct iscsi_context *ctx, + uint32_t host_id, + char ***iface_kern_ids, + uint32_t *iface_count) +{ + char *sysfs_sh_path = NULL; + char *dev_path = NULL; + char *sysfs_iface_path = NULL; + int rc = LIBISCSI_OK; + struct dirent **namelist = NULL; + int n = 0; + uint32_t i = 0; + + _good(_asprintf(&sysfs_sh_path, "%s/host%" PRIu32, + _ISCSI_SYS_HOST_DIR, host_id), rc, out); + + _good(sysfs_get_dev_path(ctx, sysfs_sh_path, + _SYSFS_DEV_CLASS_ISCSI_HOST, &dev_path), + rc, out); + + _good(_asprintf(&sysfs_iface_path, "%s/iscsi_iface", dev_path), + rc, out); + + _good(_scandir(ctx, sysfs_iface_path, &namelist, &n), rc, out); + + if (n == 0) { + /* this is OK, and needed for transport drivers like + * bnx2i and qedi */ + rc = LIBISCSI_OK; + _debug(ctx, "No iSCSI interface for iSCSI host %" PRIu32, + host_id); + goto out; + } + + *iface_count = n; + *iface_kern_ids = calloc(*iface_count, sizeof(char *)); + _alloc_null_check(ctx, *iface_kern_ids, rc, out); + for (i = 0; i < *iface_count; i++) { + (*iface_kern_ids)[i] = strdup(namelist[i]->d_name); + _alloc_null_check(ctx, (*iface_kern_ids)[i], rc, out); + _debug(ctx, "Found iSCSI iface '%s' for iSCSI host %" PRIu32, + (*iface_kern_ids)[i], host_id); + } +out: + if (rc != LIBISCSI_OK) { + for (i = 0; i < *iface_count; i++ ) { + free((*iface_kern_ids)[i]); + } + free(*iface_kern_ids); + *iface_kern_ids = NULL; + *iface_count = 0; + } + _scandir_free(namelist, n); + free(sysfs_sh_path); + free(dev_path); + free(sysfs_iface_path); + return rc; +} diff --git a/libopeniscsiusr/sysfs.h b/libopeniscsiusr/sysfs.h new file mode 100644 index 0000000..768b989 --- /dev/null +++ b/libopeniscsiusr/sysfs.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ +#ifndef __ISCSI_USR_SYSFS_H__ +#define __ISCSI_USR_SYSFS_H__ + +#include +#include + +#include "libopeniscsiusr/libopeniscsiusr_common.h" + +#define _ISCSI_SYS_SESSION_DIR "/sys/class/iscsi_session" +#define _ISCSI_SYS_CONNECTION_DIR "/sys/class/iscsi_connection" +#define _ISCSI_SYS_HOST_DIR "/sys/class/iscsi_host" +#define _ISCSI_SYS_IFACE_DIR "/sys/class/iscsi_iface" +#define _ISCSI_SYS_TRANSPORT_DIR "/sys/class/iscsi_transport" +#define _SCSI_SYS_HOST_DIR "/sys/class/scsi_host" + +/* + * When default_value == NULL, treat no such file as LIB_BUG. + */ +__DLL_LOCAL int _sysfs_prop_get_str(struct iscsi_context *ctx, + const char *dir_path, const char *prop_name, + char *buff, size_t buff_size, + const char *default_value); + +int _sysfs_prop_get_u8(struct iscsi_context *ctx, const char *dir_path, + const char *prop_name, uint8_t *val, + uint8_t default_value, bool ignore_error); + +int _sysfs_prop_get_u16(struct iscsi_context *ctx, const char *dir_path, + const char *prop_name, uint16_t *val, + uint16_t default_value, bool ignore_error); + +/* + * When default_value == UINT32_MAX, treat no such file as LIB_BUG. + */ +__DLL_LOCAL int _sysfs_prop_get_u32(struct iscsi_context *ctx, + const char *dir_path, const char *prop_name, + uint32_t *val, uint32_t default_value, + bool ignore_error); + +/* + * When default_value == INT32_MAX, treat no such file as LIB_BUG. + */ +__DLL_LOCAL int _sysfs_prop_get_i32(struct iscsi_context *ctx, + const char *dir_path, const char *prop_name, + int32_t *val, int32_t default_value, + bool ignore_error); + +__DLL_LOCAL int _iscsi_host_id_of_session(struct iscsi_context *ctx, + uint32_t sid, uint32_t *host_id); + +/* + * iface_kern_id returns an allocated (char *)[iface_count] + * that needs to be freed by the caller + */ +__DLL_LOCAL int _iscsi_iface_kern_ids_of_host_id(struct iscsi_context *ctx, + uint32_t host_id, + char ***iface_kern_ids, + uint32_t *iface_count); + +/* + * The memory of (uint32_t *sids) should be freed by free(). + */ +__DLL_LOCAL int _iscsi_sids_get(struct iscsi_context *ctx, + uint32_t **sids, uint32_t *sid_count); + +/* + * The memory of (uint32_t *hids) should be freed by free(). + */ +__DLL_LOCAL int _iscsi_hids_get(struct iscsi_context *ctx, uint32_t **hids, + uint32_t *hid_count); + +__DLL_LOCAL bool _iscsi_transport_is_loaded(const char *transport_name); + +#endif /* End of __ISCSI_USR_SYSFS_H__ */ diff --git a/libopeniscsiusr/tests/runtest.sh b/libopeniscsiusr/tests/runtest.sh new file mode 100755 index 0000000..a4cedf2 --- /dev/null +++ b/libopeniscsiusr/tests/runtest.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +if [ "CHK$TESTS" == "CHK" ];then + echo "# No test cases defined" + exit 1 +fi + +VALGRIND_ERR_RC=2 +VALGRIND_OPTS="--quiet --leak-check=full \ + --show-reachable=no --show-possibly-lost=no \ + --trace-children=yes --error-exitcode=$VALGRIND_ERR_RC" + +TEST_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +for TEST in $TESTS; do + echo + TEST=$(basename $TEST) + echo "## RUN '$TEST'" + valgrind $VALGRIND_OPTS $TEST_DIR/$TEST + rc=$? + if [ $rc -ne 0 ]; then + if [ $rc -eq $VALGRIND_ERR_RC ];then + echo + echo "### Found memory leak" + exit $rc + fi + exit $rc + fi + echo "## PASS '$TEST'" +done + +echo +echo "# All PASS" diff --git a/libopeniscsiusr/tests/test_context.c b/libopeniscsiusr/tests/test_context.c new file mode 100644 index 0000000..d1b8fc8 --- /dev/null +++ b/libopeniscsiusr/tests/test_context.c @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ + +#include +#include +#include +#include +#include + +#include + +int main(void) +{ + struct iscsi_context *ctx = NULL; + int rc = EXIT_SUCCESS; + int i = 0; + + ctx = iscsi_context_new(); + assert(ctx != NULL); + iscsi_context_log_priority_set(ctx, LIBISCSI_LOG_PRIORITY_DEBUG); + assert(iscsi_context_log_priority_get(ctx) == + LIBISCSI_LOG_PRIORITY_DEBUG); + iscsi_context_log_func_set(ctx, NULL); + iscsi_context_userdata_set(ctx, (void *) &i); + assert(* (int *) iscsi_context_userdata_get(ctx) == 0); + + iscsi_context_free(ctx); + exit(rc); +} diff --git a/libopeniscsiusr/tests/test_iface.c b/libopeniscsiusr/tests/test_iface.c new file mode 100644 index 0000000..f3ad8c6 --- /dev/null +++ b/libopeniscsiusr/tests/test_iface.c @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define _assert_print_prop_str_can_empty(struct_name, obj, prop_name) \ + do { \ + assert(struct_name##_##prop_name##_get(obj) != NULL); \ + printf("\t" # prop_name ": '%s'\n", \ + struct_name##_##prop_name##_get(obj)); \ + } while(0) + +#define _assert_print_prop_str_not_empty(struct_name, obj, prop_name) \ + do { \ + assert(struct_name##_##prop_name##_get(obj) != NULL); \ + assert(strlen(struct_name##_##prop_name##_get(obj)) != 0); \ + printf("\t" # prop_name ": '%s'\n", \ + struct_name##_##prop_name##_get(obj)); \ + } while(0) + +static void test_iface(struct iscsi_context *ctx, struct iscsi_iface *iface) +{ + struct iscsi_iface *tmp_iface = NULL; + const char *conf = NULL; + + assert(iface != NULL); + printf("\t#### Interface info ####\n"); + _assert_print_prop_str_not_empty(iscsi_iface, iface, name); + if (! iscsi_is_default_iface(iface)) { + assert(iscsi_iface_get(ctx, iscsi_iface_name_get(iface), + &tmp_iface) == LIBISCSI_OK); + _assert_print_prop_str_can_empty(iscsi_iface, tmp_iface, + ipaddress); + _assert_print_prop_str_can_empty(iscsi_iface, tmp_iface, + transport_name); + _assert_print_prop_str_can_empty(iscsi_iface, tmp_iface, iname); + _assert_print_prop_str_can_empty(iscsi_iface, tmp_iface, + hwaddress); + _assert_print_prop_str_can_empty(iscsi_iface, tmp_iface, + netdev); + _assert_print_prop_str_can_empty(iscsi_iface, tmp_iface, + port_state); + _assert_print_prop_str_can_empty(iscsi_iface, tmp_iface, + port_speed); + iscsi_iface_free(tmp_iface); + } + printf("\t########################\n"); + + conf = iscsi_iface_dump_config(iface); + assert(conf != NULL); + free((char *) conf); + iscsi_iface_print_config(iface); +} + +int main() +{ + struct iscsi_context *ctx = NULL; + struct iscsi_iface **ifaces = NULL; + uint32_t iface_count = 0; + uint32_t i = 0; + int rc = EXIT_SUCCESS; + + ctx = iscsi_context_new(); + iscsi_context_log_priority_set(ctx, LIBISCSI_LOG_PRIORITY_DEBUG); + + if (iscsi_default_iface_setup(ctx) != LIBISCSI_OK) { + printf("FAILED\n"); + rc = EXIT_FAILURE; + } + + if (iscsi_ifaces_get(ctx, &ifaces, &iface_count) != LIBISCSI_OK) { + printf("FAILED\n"); + rc = EXIT_FAILURE; + } else { + assert(iface_count >= 2); + /* we will have at least the default ifaces: + * iser and iscsi_tcp + */ + printf("\nGot %" PRIu32 " iSCSI ifaces\n", iface_count); + for (i = 0; i < iface_count; ++i) { + test_iface(ctx, ifaces[i]); + } + iscsi_ifaces_free(ifaces, iface_count); + } + iscsi_context_free(ctx); + exit(rc); +} diff --git a/libopeniscsiusr/tests/test_node.c b/libopeniscsiusr/tests/test_node.c new file mode 100644 index 0000000..7ff7e9b --- /dev/null +++ b/libopeniscsiusr/tests/test_node.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ +#include +#include +#include +#include + +#include + +int main() +{ + struct iscsi_context *ctx = NULL; + struct iscsi_node **nodes = NULL; + uint32_t node_count = 0; + uint32_t i = 0; + const char *dump = NULL; + int rc = EXIT_SUCCESS; + + ctx = iscsi_context_new(); + iscsi_context_log_priority_set(ctx, LIBISCSI_LOG_PRIORITY_DEBUG); + + if (iscsi_nodes_get(ctx, &nodes, &node_count) != LIBISCSI_OK) { + printf("FAILED\n"); + rc = EXIT_FAILURE; + } else { + printf("\nGot %" PRIu32 " iSCSI nodes\n", node_count); + for (i = 0; i < node_count; ++i) { + dump = iscsi_node_dump_config(nodes[i], true); + assert(dump != NULL); + free((void *) dump); + iscsi_node_print_config(nodes[i], true); + } + iscsi_nodes_free(nodes, node_count); + } + iscsi_context_free(ctx); + exit(rc); +} diff --git a/libopeniscsiusr/tests/test_session.c b/libopeniscsiusr/tests/test_session.c new file mode 100644 index 0000000..0f23f2e --- /dev/null +++ b/libopeniscsiusr/tests/test_session.c @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define _assert_print_prop_str_can_empty(struct_name, obj, prop_name) \ + do { \ + assert(struct_name##_##prop_name##_get(obj) != NULL); \ + printf("\t" # prop_name ": '%s'\n", \ + struct_name##_##prop_name##_get(obj)); \ + } while(0) + +#define _assert_print_prop_str_not_empty(struct_name, obj, prop_name) \ + do { \ + assert(struct_name##_##prop_name##_get(obj) != NULL); \ + assert(strlen(struct_name##_##prop_name##_get(obj)) != 0); \ + printf("\t" # prop_name ": '%s'\n", \ + struct_name##_##prop_name##_get(obj)); \ + } while(0) + +#define _assert_print_prop_u32_not_zero(struct_name, obj, prop_name) \ + do { \ + assert(struct_name##_##prop_name##_get(obj) != 0); \ + printf("\t" # prop_name ": %" PRIu32 "\n", \ + struct_name##_##prop_name##_get(obj)); \ + } while(0) + +#define _assert_print_prop_i32_not_zero(struct_name, obj, prop_name) \ + do { \ + assert(struct_name##_##prop_name##_get(obj) != 0); \ + printf("\t" # prop_name ": %" PRIi32 "\n", \ + struct_name##_##prop_name##_get(obj)); \ + } while(0) + +static void test_session(struct iscsi_session *se); +static void test_iface(struct iscsi_iface *iface); + +static void test_iface(struct iscsi_iface *iface) +{ + assert(iface != NULL); + printf("\t#### Interface info ####\n"); + _assert_print_prop_str_not_empty(iscsi_iface, iface, name); + _assert_print_prop_str_not_empty(iscsi_iface, iface, ipaddress); + _assert_print_prop_str_not_empty(iscsi_iface, iface, transport_name); + _assert_print_prop_str_not_empty(iscsi_iface, iface, iname); + _assert_print_prop_str_can_empty(iscsi_iface, iface, hwaddress); + _assert_print_prop_str_can_empty(iscsi_iface, iface, netdev); + _assert_print_prop_str_can_empty(iscsi_iface, iface, port_state); + _assert_print_prop_str_can_empty(iscsi_iface, iface, port_speed); + printf("\t########################\n"); +} + +static void test_session(struct iscsi_session *se) +{ + assert(se != NULL); + printf("Session %" PRIu32 ":\n", iscsi_session_sid_get(se)); + + _assert_print_prop_u32_not_zero(iscsi_session, se, sid); + _assert_print_prop_str_not_empty(iscsi_session, se, persistent_address); + _assert_print_prop_i32_not_zero(iscsi_session, se, persistent_port); + _assert_print_prop_str_not_empty(iscsi_session, se, target_name); + _assert_print_prop_str_can_empty(iscsi_session, se, username); + _assert_print_prop_str_can_empty(iscsi_session, se, password); + _assert_print_prop_str_can_empty(iscsi_session, se, username_in); + _assert_print_prop_str_can_empty(iscsi_session, se, password_in); + _assert_print_prop_u32_not_zero(iscsi_session, se, recovery_tmo); + _assert_print_prop_u32_not_zero(iscsi_session, se, lu_reset_tmo); + _assert_print_prop_u32_not_zero(iscsi_session, se, tgt_reset_tmo); + _assert_print_prop_u32_not_zero(iscsi_session, se, abort_tmo); + _assert_print_prop_u32_not_zero(iscsi_session, se, tpgt); + _assert_print_prop_str_not_empty(iscsi_session, se, address); + _assert_print_prop_i32_not_zero(iscsi_session, se, port); +} + +int main() +{ + struct iscsi_context *ctx = NULL; + struct iscsi_session **ses = NULL; + uint32_t se_count = 0; + uint32_t i = 0; + int rc = EXIT_SUCCESS; + + ctx = iscsi_context_new(); + iscsi_context_log_priority_set(ctx, LIBISCSI_LOG_PRIORITY_DEBUG); + + if (iscsi_sessions_get(ctx, &ses, &se_count) != LIBISCSI_OK) { + printf("FAILED\n"); + rc = EXIT_FAILURE; + } else { + printf("\nGot %" PRIu32 " iSCSI sessions\n", se_count); + for (i = 0; i < se_count; ++i) { + test_session(ses[i]); + test_iface(iscsi_session_iface_get(ses[i])); + } + iscsi_sessions_free(ses, se_count); + } + iscsi_context_free(ctx); + exit(rc); +} diff --git a/libopeniscsiusr/version.h b/libopeniscsiusr/version.h new file mode 100644 index 0000000..a55f248 --- /dev/null +++ b/libopeniscsiusr/version.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017-2018 Red Hat, Inc. + * + * 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 3 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, see . + * + * Author: Gris Ge + */ + +#ifndef __ISCSI_OPEN_USR_VERSION_H__ +#define __ISCSI_OPEN_USR_VERSION_H__ + +/* + * iSCSI tools version. + * This may not be the same value as the kernel versions because + * some other maintainer could merge a patch without going through us + */ +#define ISCSI_VERSION_STR "2.0-878" + +#endif /* End of __ISCSI_OPEN_USR_VERSION_H__ */ diff --git a/sysfs-documentation b/sysfs-documentation new file mode 100644 index 0000000..3f3a5dd --- /dev/null +++ b/sysfs-documentation @@ -0,0 +1,514 @@ +Description of iface attributes and their valid values +====================================================== + +== IPv4 attributes == + +ipaddress +--------- +IP address in format XXX.XXX.XXX.XXX + +gateway +------- +IP address of the network router or gateway device in format XXX.XXX.XXX.XXX + +subnet +------ +Broadcast address in format XXX.XXX.XXX.XXX + +bootproto +--------- +The protocol type used to initialize interface + +Valid values: "dhcp" or "static" + +dhcp_dns_address_en +------------------- +Request DNS Server IP Addresses and Domain Name + +If bootproto is set to dhcp and dhcp_dns_address_en is enable, +requests DNS addresses (option 6) and domain name (option 15) in its +DHCP parameter request list. + +Valid values: "enable" or "disable" + +dhcp_slp_da_info_en +------------------- +Request SLP DA Information and SLP Scope +If bootproto is set to dhcp and dhcp_slp_da_info_en is enable, +requests SLP DA information (option 78) and SLP scope (option 79) +in its DHCP parameter request list. + +Valid values: "enable" or "disable" + +tos_en +------ +Enable IPv4 type of service (ToS) + +When tos_en is set to enable, use value set in tos when transmitting IPv4 TCP +packets on iSCSI connections. + +Valid values: "enable" or "disable" + +tos +--- +IPv4 Type of service (ToS) + +When tos_en is set to enable, use value set in tos when transmitting IPv4 TCP +packets on iSCSI connections. + +Valid range: 8-bit value. [0-255] + +grat_arp_en +----------- +Enable Gratuitous ARP Requests + +Valid values: "enable" or "disable" + +dhcp_alt_client_id_en +--------------------- +DHCP Use Alternate Client ID + +When dhcp_alt_client_id_en is set to enable, use the Client ID configured in +dhcp_alt_client_id as its Client ID (DHCP option 61) in outgoing DHCP messages. + +Valid values: "enable" or "disable" + +dhcp_alt_client_id +------------------ +DHCP Alternate Client ID + +When dhcp_alt_client_id_en is set to enable, use value set in dhcp_alt_client_id +for Client ID in DHCP messages. + +Valid values: 11-byte Client ID + +dhcp_req_vendor_id_en +--------------------- +DHCP Require Vendor ID + +When dhcp_req_vendor_id_en is set to enable, use value set in dhcp_vendor_id as +its vendor ID (DHCP option 60) in outgoing DHCP messages. + +Valid values: "enable" or "disable" + +dhcp_use_vendor_id_en +--------------------- +DHCP Use Vendor ID + +When dhcp_use_vendor_id_en is set to enable, use value set in dhcp_vendor_id as +its vendor ID (DHCP option 60) in outgoing DHCP messages. + +Valid values: "enable" or "disable" + +dhcp_vendor_id +-------------- +DHCP Vendor ID + +When dhcp_req_vendor_id_en or dhcp_use_vendor_id_en is set to enable, +use value set in dhcp_vendor_id for Vendor ID in DHCP messages. + +Valid values: 11-byte Client ID + +dhcp_learn_iqn_en +----------------- +DHCP Learn IQN + +When dhcp_learn_iqn_en is set to enable, iSCSI initiator attempts to use DHCP +to learn its (IQN) iSCSI name. + +Valid values: "enable" or "disable" + +fragment_disable +---------------- +Fragmentation Disable. + +When fragment_disable is set to disable, iSCSI initiator cannot fragment IP +datagrams. + +Valid values: "enable" or "disable" + +incoming_forwarding_en +---------------------- +When incoming_forwarding_en is set to enable, iSCSI initiator forwards all +incoming network traffic to the network driver, except for iSCSI TCP packets +destined to the iSCSI initiator. + +Valid values: "enable" or "disable" + +ttl +--- +IPv4 Time to Live (TTL) + +This attribute contain TTL value sent in IPv4 TCP packets transmitted on +iSCSI connections. + +Valid range: 8-bit value. [0-255] + +== IPv6 attributes == + +ipaddress +--------- +IP address in IPv6 format. + +link_local_addr +--------------- +Link local address in IPv6 format. + +router_addr +----------- +Router address in IPv6 format. + +ipaddr_autocfg +-------------- +Autoconfigure IPv6 Address. + +Valid values: nd, dhcpv6 or disable +qla4xxx don't support dhcpv6. + +link_local_autocfg +------------------ +Autoconfigure IPv6 Link Local Address. + +IPv6 neighbor discovery protocol to discover Link Local Address. + +Valid values: auto or disable + + +router_autocfg +-------------- +Autoconfigure IPv6 Router address. + +IPv6 neighbor discovery protocol to discover a default router address. + +Valid values: auto or disable + +link_local_state +---------------- +This Read-only attribute show Link Local IP address state in sysfs. + +Valid values: Unconfigured, Acquiring, Tentative, Valid, Disabling, Invalid, + Deprecated. + + +router_state +------------ +This Read-only attribute shows router state. + +Valid values: Unknown, Advertised, Manual, Stale. + + +grat_neighbor_adv_en +-------------------- +Enable Gratuitous Neighbor Advertisement + +Valid values: "enable" or "disable" + +mld_en +------ +Enable IPv6 Multicast Listener Discovery + +Valid values: "enable" or "disable" + +flow_label +---------- +This attribute specifies the default value of the Flow Label field in the +IPv6 header of TCP packets transmitted on iSCSI connections + +Valid range: 20-bit value. [0-1048575] +Value zero indicates that the traffic is not assigned to a labelled flow. + +traffic_class +------------- +This attribute specifies the IPv6 traffic class value to be used in IPv6 +TCP packets transmitted from the firmware on iSCSI connections. + +Valid range: 8-bit value. [0-255] + +hop_limit +--------- +This attribute specifies the IPv6 hop limit value to be used in IPv6 TCP +packets transmitted from the firmware on iSCSI connections + +Valid range: 8-bit value. [0-255] + +nd_reachable_tmo +---------------- +This attribute specifies the time (in milliseconds) that a node assumes +that the neighbor is reachable after confirmation. + +Valid range: 4-byte value. [0-4294967295] + +nd_rexmit_time +-------------- +This attribute specifies the time (in milliseconds) between retransmitted +neighbor solicitation messages. + +Valid range: 4-byte value. [0-4294967295] + +nd_stale_tmo +------------ +This attribute specifies the time (in milliseconds) after which a stale +neighbor or destination cache entry is discarded. + +Valid range: 4-byte value. [0-4294967295] + +dup_addr_detect_cnt +------------------- +This attribute specifies the IPv6 duplicate address detection count + +Valid range: 8-bit value. [0-255] + 0 - Disable + 1 - TryOnce + 2 - TryTwice, and so on + +router_adv_link_mtu +------------------- +IPv6 Router Advertised Link MTU Size. + +Valid range: 1280 bytes to 1500 bytes + +== Common == +enabled +------- +This attribute is used to enable or disable IPv4 or IPv6 protocol. + +Valid values: "enable" or "disable" + +vlan_id +------- +This attribute specifies 12-bit VLAN identifier (VID) + +Valid range: 12-bit value. [1-4094] + +vlan_priority +------------- +This attribute specifies Priority to outbound packets containing the +specified VLAN-ID (VID) + +Valid range: 3-bit value. [0-7] + +vlan_enabled +------------ +VLAN Tagging Enable. + +When this attribute is set to enable, use value set in vlan_id and +vlan_priority to transmit IP packets, and discards IP packets that were +received without a matching VLAN ID + +Valid values: "enable" or "disable" + +mtu +--- +Ethernet MTU Size. + +This field specifies the maximum payload length in byte of an +Ethernet frame supported by iSCSI initiator. + +Valid values: 576 bytes to 9000 bytes + +port +---- +This attribute shows the initiator iSCSI port number. + +ipaddress_state +--------------- +This Read-only attribute show IP address state. + +Valid values: Unconfigured, Acquiring, Tentative, Valid, Disabling, Invalid, + Deprecated. + +delayed_ack_en +-------------- +When this attribute is set to enable, TCP delayed ACK is enabled. + +Valid values: "enable" or "disable" + +tcp_nagle_disable +----------------- +When this attribute is set to disable, TCP Nagle algorithm is disabled. + +Valid values: "enable" or "disable" + +tcp_wsf_disable +--------------- +When this attribute is set to disable, TCP window scale is disabled. + +Valid values: "enable" or "disable" + +tcp_wsf +------- +This attribute specifies the TCP window scale factor to be negotiated +on TCP connections. + +Valid range: 8-bit value. [0-255] + +tcp_timer_scale +--------------- +The TCP Timer Scale is scale factor that adjusts the time interval between +timer ticks on a TCP connection. The scale factor allows for faster time-outs +for connections running on a very small network, versus connections running +on a very large network. + +Valid range: 3-bit value. [0-7] + +tcp_timestamp_en +---------------- +When this attribute is set to enable, iSCSI initiator negotiates to use time +stamps in TCP headers + +Valid values: "enable" or "disable" + +cache_id +-------- +This Read-only attribute is used to find the valid cache entries for the +interface. + +For IPv4, ARP cache entry +For IPv6, Neighbor cache entry + +redirect_en +----------- +For IPv4: +When this attribute is set to enable, an ARP redirect can modify the address +resolution protocol (ARP) table and any active connections. + +For IPv6: +When this attribute is set to enable and neighbor advertisements are received, +the connection table is examined and updated if any active connections match +the IP address on the neighbor advertisement. This action is required for +failover and redirect. + +Valid values: "enable" or "disable" + +def_taskmgmt_tmo +---------------- +This attribute specifies timeout interval in seconds that iSCSI uses for +timing out task-management commands. + +Valid range: 16-bit value [0-65535]. + +header_digest +------------- +When this attribute is set to enable iSCSI initiator negotiates for +HeaderDigest=CRC32 and when set to disable negotiates HeaderDigest=none. + +Valid values: "enable" or "disable" + +data_digest +----------- +When this attribute is set to enable iSCSI initiator negotiates for +DataDigest=CRC32 and when set to disable negotiates DataDigest=none. + +Valid values: "enable" or "disable" + +immediate_data +-------------- +When this attribute is set to enable iSCSI initiator negotiates for +ImmediateData=yes and When set to disable negotiates ImmediateData=none + +Valid values: "enable" or "disable" + +initial_r2t +----------- +When this attribute is set to enable iSCSI initiator negotiates for +InitialR2T=yes. When set to disable negotiates InitialR2T=no. + +Valid values: "enable" or "disable" + +data_seq_in_order +----------------- +When this attribute is set to enable iSCSI initiator set data sequences +in order + +Valid values: "enable" or "disable" +qla4xxx does not support out-of-order data sequences + +data_pdu_in_order +----------------- +When this attribute is set to enable iSCSI initiator set Data PDU +in order + +Valid values: "enable" or "disable" +qla4xxx does not support out-of-order Data PDUs. + +erl +--- +Error Recovery Level + +This attribute specifies error recovery level (ERL) supported by the +connection. + +Valid values: 2-bit value [0-2] + +max_recv_dlength +---------------- +iSCSI Maximum Receive Data Segment Length. + +This attribute specifies Maximum data segment length in bytes, that receive +in an iSCSI PDU. + +first_burst_len +--------------- +iSCSI First Burst Length + +This attribute Specifies the maximum amount of unsolicited data an iSCSI +initiator can send to the target during the execution of a single SCSI command, +in bytes. + +max_outstanding_r2t +------------------- +iSCSI Maximum Outstanding R2T + +This attribute Specifies how many R2T PDUs per command can be outstanding +during an iSCSI session. + +max_burst_len +------------- +This attribute Specifies the maximum length for unsolicited or immediate data +iSCSI session can send or receive. + +chap_auth +--------- +When this attribute is set to enable iSCSI session performs authentication +during the security state of login phase. + +Valid values: "enable" or "disable" + +bidi_chap +--------- +When this attribute is set to enable iSCSI session generates a CHAP challenge +to any target that has issued a CHAP challenge to the iSCSI session. +iSCSI session issues the challenge to the target after responding to the +targets challenge. This attribute is ignored if chap_auth is set to disable. + +Valid values: "enable" or "disable" + +discovery_auth_optional +----------------------- +When this attribute is set to enable and the chap_auth is set to enable, +iSCSI session does not require authentication on discovery sessions unless +requested by the peer. When this attribute is set to disable iSCSI session +requires CHAP authentication for a discovery session. + +Valid values: "enable" or "disable" + +discovery_logout +---------------- +When this attribute is set to enable, iSCSI initiator initiates an iSCSI logout +on a discovery session when discovery is complete (before closing the connection). +When this attribute is set to disable, iSCSI initiator closes the connection when +discovery is complete. + +Valid values: "enable" or "disable" + +strict_login_comp_en +-------------------- +When this attribute is set to enable, iSCSI initiator enforces the iSCSI login +negotiation rules. When this attribute is set to disable, iSCSI initiator does +not enforce iSCSI login negotiation. + +Valid values: "enable" or "disable" + +initiator_name +-------------- +This Read-only attribute contains the iSCSI Name string used by the firmware. diff --git a/test/README b/test/README new file mode 100644 index 0000000..295b96f --- /dev/null +++ b/test/README @@ -0,0 +1,15 @@ +This directory contains regression suite. + +I would appreciate if developer will run it at least once after +modifications done before commit or mailing list submit. + +./regression.sh script expects next binaries and data files exists +in current directory: + + - regression.dat + - iscsiadm + - bonnie++ (source: http://www.open-iscsi.org/bits/bonnie++.tar.gz) + - disktest (source: http://www.open-iscsi.org/bits/disktest.tar.gz) + +Thanks! +Dmitry diff --git a/test/regression.dat b/test/regression.dat new file mode 100644 index 0000000..c96cd9d --- /dev/null +++ b/test/regression.dat @@ -0,0 +1,108 @@ +#imdata inir2t hdrdgst datdgst frstbst mxbrst mxrecv mxr2t +# 0 - 16 +No Yes None None 4096 4096 4096 1 +No Yes None None 8192 4096 4096 1 +No Yes None None 16384 4096 4096 1 +No Yes None None 32768 4096 4096 1 +No Yes None None 65536 4096 4096 1 +No Yes None None 131972 4096 4096 1 +No Yes None None 4096 8192 4096 1 +No Yes None None 4096 16384 4096 1 +No Yes None None 4096 32768 4096 1 +No Yes None None 4096 65536 4096 1 +No Yes None None 4096 131072 4096 1 +No Yes None None 4096 4096 8192 1 +No Yes None None 4096 4096 16384 1 +No Yes None None 4096 4096 32768 1 +No Yes None None 4096 4096 65536 1 +No Yes None None 4096 4096 131072 1 + +# 15 - 31 +No No None None 4096 4096 4096 1 +No No None None 8192 4096 4096 1 +No No None None 16384 4096 4096 1 +No No None None 32768 4096 4096 1 +No No None None 65536 4096 4096 1 +No No None None 131972 4096 4096 1 +No No None None 4096 8192 4096 1 +No No None None 4096 16384 4096 1 +No No None None 4096 32768 4096 1 +No No None None 4096 65536 4096 1 +No No None None 4096 131072 4096 1 +No No None None 4096 4096 8192 1 +No No None None 4096 4096 16384 1 +No No None None 4096 4096 32768 1 +No No None None 4096 4096 65536 1 +No No None None 4096 4096 131072 1 + +# 32 - 47 +Yes No None None 4096 4096 4096 1 +Yes No None None 8192 4096 4096 1 +Yes No None None 16384 4096 4096 1 +Yes No None None 32768 4096 4096 1 +Yes No None None 65536 4096 4096 1 +Yes No None None 131972 4096 4096 1 +Yes No None None 4096 8192 4096 1 +Yes No None None 4096 16384 4096 1 +Yes No None None 4096 32768 4096 1 +Yes No None None 4096 65536 4096 1 +Yes No None None 4096 131072 4096 1 +Yes No None None 4096 4096 8192 1 +Yes No None None 4096 4096 16384 1 +Yes No None None 4096 4096 32768 1 +Yes No None None 4096 4096 65536 1 +Yes No None None 4096 4096 131072 1 + +# 48 - 63 +Yes Yes None None 4096 4096 4096 1 +Yes Yes None None 8192 4096 4096 1 +Yes Yes None None 16384 4096 4096 1 +Yes Yes None None 32768 4096 4096 1 +Yes Yes None None 65536 4096 4096 1 +Yes Yes None None 131972 4096 4096 1 +Yes Yes None None 4096 8192 4096 1 +Yes Yes None None 4096 16384 4096 1 +Yes Yes None None 4096 32768 4096 1 +Yes Yes None None 4096 65536 4096 1 +Yes Yes None None 4096 131072 4096 1 +Yes Yes None None 4096 4096 8192 1 +Yes Yes None None 4096 4096 16384 1 +Yes Yes None None 4096 4096 32768 1 +Yes Yes None None 4096 4096 65536 1 +Yes Yes None None 4096 4096 131072 1 + +# 64 - 79 +No Yes CRC32C None 4096 4096 4096 1 +No Yes CRC32C None 8192 4096 4096 1 +No Yes CRC32C None 16384 4096 4096 1 +No Yes CRC32C None 32768 4096 4096 1 +No Yes CRC32C None 65536 4096 4096 1 +No Yes CRC32C None 131972 4096 4096 1 +No Yes CRC32C None 4096 8192 4096 1 +No Yes CRC32C None 4096 16384 4096 1 +No Yes CRC32C None 4096 32768 4096 1 +No Yes CRC32C None 4096 65536 4096 1 +No Yes CRC32C None 4096 131072 4096 1 +No Yes CRC32C None 4096 4096 8192 1 +No Yes CRC32C None 4096 4096 16384 1 +No Yes CRC32C None 4096 4096 32768 1 +No Yes CRC32C None 4096 4096 65536 1 +No Yes CRC32C None 4096 4096 131072 1 + +# 80 - 95 +No Yes CRC32C CRC32C 4096 4096 4096 1 +No Yes CRC32C CRC32C 8192 4096 4096 1 +No Yes CRC32C CRC32C 16384 4096 4096 1 +No Yes CRC32C CRC32C 32768 4096 4096 1 +No Yes CRC32C CRC32C 65536 4096 4096 1 +No Yes CRC32C CRC32C 131972 4096 4096 1 +No Yes CRC32C CRC32C 4096 8192 4096 1 +No Yes CRC32C CRC32C 4096 16384 4096 1 +No Yes CRC32C CRC32C 4096 32768 4096 1 +No Yes CRC32C CRC32C 4096 65536 4096 1 +No Yes CRC32C CRC32C 4096 131072 4096 1 +No Yes CRC32C CRC32C 4096 4096 8192 1 +No Yes CRC32C CRC32C 4096 4096 16384 1 +No Yes CRC32C CRC32C 4096 4096 32768 1 +No Yes CRC32C CRC32C 4096 4096 65536 1 +No Yes CRC32C CRC32C 4096 4096 131072 1 diff --git a/test/regression.sh b/test/regression.sh new file mode 100755 index 0000000..25d4a28 --- /dev/null +++ b/test/regression.sh @@ -0,0 +1,263 @@ +#!/bin/bash +# +# Open-iSCSI Regression Test Utility +# Copyright (C) 2004 Dmitry Yusupov +# maintained by open-iscsi@googlegroups.com +# +# 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. +# +# See the file COPYING included with this distribution for more details. +# + +PATH=".:${PATH}" +FSTYPE="${FSTYPE:-ext3}" +DEFAULTMOUNTOPTS='-o _netdev' +[ -z "${MOUNTOPTS}" ] && MOUNTOPTS="${DEFAULTMOUNTOPTS}" +# to avoid mount looking for fstype +MOUNTOPTIONS="${MOUNTOPTIONS} -t ${FSTYPE}" +MKFSCMD="${MKFSCMD:-mkfs.${FSTYPE}} ${MKFSOPTS}" +PARTITIONSUFFIX="1" +BONNIEPARAMS="${BONNIEPARAMS:--r0 -n10:0:0 -s16 -uroot -f -q}" + +trap regress_signal INT QUIT TERM +regress_signal() { + printf "\nterminating, restore defaults: " + # use the other function to clean up + imm_data_en="Yes" + initial_r2t_en="No" + hdrdgst_en="None,CRC32C" + datdgst_en="None,CRC32C" + c="${iscsiadm} -m node -T $target -p $ipnr -o update" + first_burst="$((256*1024))" + max_burst="$((16*1024*1024-1024))" + max_recv_dlength="$((128*1024))" + max_r2t="1" + update_cfg + ${iscsiadm} -m node -T $target -p $ipnr --logout 2>/dev/null >/dev/null + printf "done\n" + exit 0 +} + +function update_cfg() { + c="${iscsiadm} -m node -T $target -p $ipnr -o update" + $c -n node.session.iscsi.ImmediateData -v $imm_data_en + $c -n node.session.iscsi.InitialR2T -v $initial_r2t_en + $c -n node.conn[0].iscsi.HeaderDigest -v $hdrdgst_en + $c -n node.conn[0].iscsi.DataDigest -v $datdgst_en + $c -n node.session.iscsi.FirstBurstLength -v $first_burst + $c -n node.session.iscsi.MaxBurstLength -v $max_burst + $c -n node.conn[0].iscsi.MaxRecvDataSegmentLength -v $max_recv_dlength + $c -n node.session.iscsi.MaxOutstandingR2T -v $max_r2t +} + +function disktest_run() { + bsizes="512 1024 2048 4096 8192 16384 32768 65536 131072 1000000" + test "x$bsize" != x && bsizes=$bsize + test "x$bsize" = xbonnie && return 0; + for bs in $bsizes; do + echo -n "disktest -T2 -K8 -B$bs -r -ID $device: " + if ! ${disktest} -T2 -K8 -B$bs -r -ID $device >/dev/null; then + echo "FAILED" + return 1; + fi + echo "PASSED" + echo -n "disktest -T2 -K8 -B$bs -E16 -w -ID $device: " + if ! ${disktest} -T2 -K8 -B$bs -E16 -w -ID $device >/dev/null;then + echo "FAILED" + return 1; + fi + echo "PASSED" + done + return 0; +} + +function fdisk_run() { + echo -n "sfdisk -Lqf $device: " + sfdisk -Lqf $device >/dev/null 2>/dev/null <<-EOF + 0, + ; + ; + ; + EOF + rc=$? + if [ $rc -ne 0 ]; then + echo "FAILED" + return 1; + fi + echo "PASSED" + return 0; +} + +function mkfs_run() { + echo -n "${MKFSCMD} $device_partition: " + if ! ${MKFSCMD} $device_partition 2>/dev/null >/dev/null; then + echo "FAILED" + return 1; + fi + echo "PASSED" + return 0; +} + +function bonnie_run() { + dir="/tmp/iscsi.bonnie.regression.$record.$RANDOM" + umount $dir 2>/dev/null >/dev/null + rm -rf $dir; mkdir $dir + echo -n "mount $dir: " + if ! mount ${MOUNTOPTIONS} $device_partition $dir; then + echo "FAILED" + return 1; + fi + echo "PASSED" + echo -n "bonnie++ ${BONNIEPARAMS}: " + pushd $dir >/dev/null + ${bonnie} ${BONNIEPARAMS} 2>/dev/null >/dev/null + rc=$? + popd >/dev/null + umount $dir 2>/dev/null >/dev/null + rmdir ${dir} + if [ $rc -ne 0 ]; then + echo "FAILED" + return 1; + fi + echo "PASSED" + return 0; +} + +function fatal() { + echo "regression.sh: $1" + echo "Usage: regression.sh [-f | ] [test#[:#]] [bsize]" + exit 1 +} + +############################ main ################################### + +disktest=`which disktest` +iscsiadm=`which iscsiadm` +bonnie=`which bonnie++` +datfile=`dirname $0`"/regression.dat" +test ! -e ${datfile} && fatal "can not find regression.dat" +test ! -e ${disktest} && fatal "can not find disktest" +test ! -e ${iscsiadm} && fatal "can not find iscsiadm" +test ! -e ${bonnie} && fatal "can not find bonnie++" + +if test x$1 = "x-f" -o x$1 = "x--format"; then + test x$2 = x && fatal "SCSI device parameter error" + device=$2 +else + test x$1 = x && fatal "target name parameter error" + test x$2 = x && fatal "ipnumber parameter error" + test x$3 = x && fatal "SCSI device parameter error" + + target="$1" + ipnr="$2" + device=$3 +fi + +device_dir="$(dirname ${device})" +device_partition='' +case "${device_dir}" in + # /dev/sdaX + /dev) device_partition="${device}1" ;; + # /dev/disk/by-id/scsi-${ID_SERIAL}-part1 + # where ID_SERIAL is SCSI disk SERIAL from scsi_id + /dev/disk/by-id|/dev/disk/by-path) device_partition="${device}-part1" ;; + # upcoming stuff + /dev/iscsi/*) device_partition="${device}-part1" ;; +esac + +if test x$1 = "x-f" -o x$1 = "x--format"; then + mkfs_run + exit +fi + +if [ -z "${device_partition}" ]; then + echo 'Unable to find device name for first partition.' >&2 + exit 1 +fi + +test "x$4" != x && begin="$4" +test "x$5" != x && bsize="$5" + +if test "x$begin" != "x"; then + end="${begin/*:}" + begin="${begin/:*}" +fi + +# don't say we didn't warn you +if [ -z "${SKIP_WARNING}" ]; then + cat <<-EOF + BIG FAT WARNING! + + Open-iSCSI Regression Test Suite is about to start. It is going + to use "$device" for its testing. iSCSI session could be re-opened + during the tests several times and as the result device name could + not match provided device name if some other SCSI activity happened + during the test. + + Are you sure you want to continue? [y/n]: + EOF + read line + if test x$line = xn -o x$line = xN -o x$line = xno -o x$line = xNO; then + echo "aborting..." + exit + fi +fi + +i=0 +cat ${datfile} | while read line; do + if echo $line | grep "^#" >/dev/null; then continue; fi + if echo $line | grep "^$" >/dev/null; then continue; fi + if test x$begin != x; then + if test x$begin != x$i -a x$end = x; then + let i=i+1 + continue + elif test x$begin != x -a x$end != x; then + if test $i -lt $begin -o $i -gt $end; then + let i=i+1 + continue + fi + fi + fi + imm_data_en=`echo $line | awk '/^[YesNo]+/ {print $1}'` + if test x$imm_data_en = x; then continue; fi + initial_r2t_en=`echo $line | awk '{print $2}'` + hdrdgst_en=`echo $line | awk '{print $3}'` + datdgst_en=`echo $line | awk '{print $4}'` + first_burst=`echo $line | awk '{print $5}'` + max_burst=`echo $line | awk '{print $6}'` + max_recv_dlength=`echo $line | awk '{print $7}'` + max_r2t=`echo $line | awk '{print $8}'` + # ensure we are logged out + ${iscsiadm} -m node -T $target -p $ipnr --logout 2>/dev/null >/dev/null + # set parameters for next run + update_cfg + echo "================== TEST #$i BEGIN ====================" + echo "ImmediateData = $imm_data_en" + echo "InitialR2T = $initial_r2t_en" + echo "HeaderDigest = $hdrdgst_en" + echo "DataDigest = $datdgst_en" + echo "FirstBurstLength = $first_burst" + echo "MaxBurstLength = $max_burst" + echo "MaxRecvDataSegmentLength = $max_recv_dlength" + echo "MaxOutstandingR2T = $max_r2t" + # login for new test + # catch errors on this + if ! ${iscsiadm} -m node -T $target -p $ipnr --login; then break; fi + while [ ! -e $device ] ; do sleep 1 ; done + if ! disktest_run; then break; fi + if ! fdisk_run; then break; fi + if ! mkfs_run; then break; fi + if ! bonnie_run; then break; fi + let i=i+1 +done +regress_signal +echo +echo "===================== THE END ========================" diff --git a/usr/.gitignore b/usr/.gitignore new file mode 100644 index 0000000..32000e2 --- /dev/null +++ b/usr/.gitignore @@ -0,0 +1,3 @@ +iscsiadm +iscsid +iscsistart diff --git a/usr/Makefile b/usr/Makefile new file mode 100644 index 0000000..3bb0cb4 --- /dev/null +++ b/usr/Makefile @@ -0,0 +1,84 @@ +# This Makefile will work only with GNU make. + +ifeq ($(TOPDIR),) + TOPDIR = .. +endif + +OSNAME=$(shell uname -s) + +# allow users to override these +# eg to compile for a kernel that you aren't currently running +KERNELRELEASE ?= $(shell uname -r) +KSRC ?= /lib/modules/$(KERNELRELEASE)/build + +KSUBLEVEL=$(shell cat $(KSRC)/Makefile | awk -F= '/^SUBLEVEL =/ {print $$2}' | \ + sed 's/^[ \t]*//;s/[ \t]*$$//') + +ifeq ($(OSNAME),Linux) + ifeq ($(KSUBLEVEL),11) + IPC_CFLAGS=-DNETLINK_ISCSI=12 -D_GNU_SOURCE + else + ifeq ($(KSUBLEVEL),12) + IPC_CFLAGS=-DNETLINK_ISCSI=12 -D_GNU_SOURCE + else + IPC_CFLAGS=-DNETLINK_ISCSI=8 -D_GNU_SOURCE + endif + endif +IPC_OBJ=netlink.o +else +ifeq ($(OSNAME),FreeBSD) +IPC_CFLAGS= +IPC_OBJ=ioctl.o +endif +endif + +PKG_CONFIG = /usr/bin/pkg-config + +CFLAGS ?= -O2 -g +WARNFLAGS ?= -Wall -Wstrict-prototypes +CFLAGS += $(WARNFLAGS) -I../include -I. -D_GNU_SOURCE \ + -I$(TOPDIR)/libopeniscsiusr +CFLAGS += $(shell $(PKG_CONFIG) --cflags libkmod) +ISCSI_LIB = -L$(TOPDIR)/libopeniscsiusr -lopeniscsiusr +LDFLAGS += $(shell $(PKG_CONFIG) --libs libkmod) +ifeq ($(NO_SYSTEMD),) +LDFLAGS += $(shell $(PKG_CONFIG) --libs libsystemd) +endif +PROGRAMS = iscsid iscsiadm iscsistart + +# libc compat files +SYSDEPS_SRCS = $(sort $(wildcard ../utils/sysdeps/*.o)) +# sources shared between iscsid, iscsiadm and iscsistart +ISCSI_LIB_SRCS = iscsi_util.o io.o auth.o iscsi_timer.o login.o log.o \ + iface.o idbm.o sysfs.o host.o session_info.o iscsi_sysfs.o \ + iscsi_net_util.o iscsid_req.o transport.o iser.o cxgbi.o be2iscsi.o \ + initiator_common.o iscsi_err.o flashnode.o uip_mgmt_ipc.o \ + netlink.o $(SYSDEPS_SRCS) +# core initiator files +INITIATOR_SRCS = initiator.o scsi.o actor.o event_poll.o mgmt_ipc.o kern_err_table.o + +# fw boot files +FW_BOOT_SRCS = $(sort $(wildcard ../utils/fwparam_ibft/*.o)) + +# core discovery files +DISCOVERY_SRCS = $(FW_BOOT_SRCS) local_strings.o discovery.o + +all: $(PROGRAMS) + +iscsid: $(ISCSI_LIB_SRCS) $(INITIATOR_SRCS) $(DISCOVERY_SRCS) \ + iscsid.o session_mgmt.o discoveryd.o mntcheck.o + $(CC) $(CFLAGS) $^ -o $@ -lisns -lcrypto -lrt -lmount $(LDFLAGS) $(ISCSI_LIB) + +iscsiadm: $(ISCSI_LIB_SRCS) $(DISCOVERY_SRCS) iscsiadm.o session_mgmt.o mntcheck.o + $(CC) $(CFLAGS) $^ -o $@ -lisns -lcrypto -lmount $(LDFLAGS) $(ISCSI_LIB) + +iscsistart: $(ISCSI_LIB_SRCS) $(INITIATOR_SRCS) $(FW_BOOT_SRCS) \ + iscsistart.o statics.o + $(CC) $(CFLAGS) $^ -o $@ -lcrypto -lrt $(LDFLAGS) $(ISCSI_LIB) +clean: + rm -f *.o $(PROGRAMS) .depend $(LIBSYS) + +depend: + gcc $(CFLAGS) -M `ls *.c` > .depend + +-include .depend diff --git a/usr/actor.c b/usr/actor.c new file mode 100644 index 0000000..91a9506 --- /dev/null +++ b/usr/actor.c @@ -0,0 +1,285 @@ +/* + * iSCSI timeout & deferred work handling + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * Copyright (C) 2014 Red Hat Inc. + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#include +#include +#include +#include +#include +#include "actor.h" +#include "log.h" +#include "list.h" + +static LIST_HEAD(pend_list); +static LIST_HEAD(ready_list); +static volatile int poll_in_progress; + +static uint64_t +actor_time_left(actor_t *thread, uint64_t current_time) +{ + if (current_time > thread->ttschedule) + return 0; + else + return (thread->ttschedule - current_time); +} + +#define time_after(a,b) \ + ((int64_t)(b) - (int64_t)(a) < 0) + +void +actor_init(actor_t *thread, void (*callback)(void *), void *data) +{ + INIT_LIST_HEAD(&thread->list); + thread->state = ACTOR_NOTSCHEDULED; + thread->callback = callback; + thread->data = data; +} + +void +actor_delete(actor_t *thread) +{ + log_debug(7, "thread %08lx delete: state %d", (long)thread, + thread->state); + switch(thread->state) { + case ACTOR_WAITING: + /* TODO: remove/reset alarm if we were 1st entry in pend_list */ + /* priority: low */ + /* fallthrough */ + case ACTOR_SCHEDULED: + log_debug(1, "deleting a scheduled/waiting thread!"); + list_del_init(&thread->list); + if (list_empty(&pend_list)) { + log_debug(7, "nothing left on pend_list, deactivating alarm"); + alarm(0); + } + + break; + default: + break; + } + thread->state = ACTOR_NOTSCHEDULED; +} + +/* + * Inserts actor on pend list and sets alarm if new item is + * sooner than previous entries. + */ +static void +actor_insert_on_pend_list(actor_t *thread, uint32_t delay_secs) +{ + struct actor *orig_head; + struct actor *new_head; + struct actor *next_thread; + + orig_head = list_first_entry_or_null(&pend_list, + struct actor, list); + + /* insert new entry in sort order */ + list_for_each_entry(next_thread, &pend_list, list) { + if (time_after(next_thread->ttschedule, thread->ttschedule)) { + log_debug(7, "next thread %p due %lld", next_thread, + (long long)next_thread->ttschedule); + log_debug(7, "new thread %p is before (%lld), inserting", thread, + (long long)thread->ttschedule); + + /* insert new thread before the next thread */ + __list_add(&thread->list, next_thread->list.prev, &next_thread->list); + goto inserted; + } + } + + if (orig_head) { + log_debug(7, "last thread %p due %lld", next_thread, + (long long)next_thread->ttschedule); + log_debug(7, "new thread %p is after (%lld), inserting at tail", thread, + (long long)thread->ttschedule); + } + else + log_debug(7, "new thread %p due %lld is first item on pend_list", thread, + (long long)thread->ttschedule); + + /* Not before any existing entries */ + list_add_tail(&thread->list, &pend_list); + +inserted: + new_head = list_first_entry(&pend_list, struct actor, list); + if (orig_head != new_head) { + int result = alarm(delay_secs); + log_debug(7, "new alarm set for %d seconds, old alarm %d", + delay_secs, result); + } +} + +static void +actor_schedule_private(actor_t *thread, uint32_t delay_secs, int head) +{ + time_t current_time; + + struct timespec tv; + + if (clock_gettime(CLOCK_MONOTONIC_COARSE, &tv)) { + log_error("clock_getime failed, can't schedule!"); + return; + } + + current_time = tv.tv_sec; + + log_debug(7, "thread %p schedule: delay %u state %d", + thread, delay_secs, thread->state); + + switch(thread->state) { + case ACTOR_WAITING: + log_error("rescheduling a waiting thread!"); + list_del(&thread->list); + /* fall-through */ + case ACTOR_NOTSCHEDULED: + INIT_LIST_HEAD(&thread->list); + + if (delay_secs == 0) { + thread->state = ACTOR_SCHEDULED; + if (head) + list_add(&thread->list, &ready_list); + else + list_add_tail(&thread->list, &ready_list); + } else { + thread->state = ACTOR_WAITING; + thread->ttschedule = current_time + delay_secs; + + actor_insert_on_pend_list(thread, delay_secs); + } + break; + case ACTOR_SCHEDULED: + // don't do anything + break; + case ACTOR_INVALID: + log_error("BUG: Trying to schedule a thread that has not been " + "setup. Ignoring sched."); + break; + } + +} + +void +actor_schedule_head(actor_t *thread) +{ + actor_schedule_private(thread, 0, 1); +} + +void +actor_schedule(actor_t *thread) +{ + actor_schedule_private(thread, 0, 0); +} + +void +actor_timer(actor_t *thread, uint32_t timeout_secs, void (*callback)(void *), + void *data) +{ + actor_init(thread, callback, data); + actor_schedule_private(thread, timeout_secs, 0); +} + +void +actor_timer_mod(actor_t *thread, uint32_t new_timeout_secs, void *data) +{ + actor_delete(thread); + thread->data = data; + actor_schedule_private(thread, new_timeout_secs, 0); +} + +/* + * Execute all items that have expired. + * + * Set an alarm if items remain. Caller must catch SIGALRM and + * then re-invoke this function. + */ +void +actor_poll(void) +{ + struct actor *thread, *tmp; + uint64_t current_time; + struct timespec tv; + + if (poll_in_progress) { + log_error("recursive actor_poll() is not allowed"); + return; + } + + if (clock_gettime(CLOCK_MONOTONIC_COARSE, &tv)) { + log_error("clock_gettime failed, can't schedule!"); + return; + } + + current_time = tv.tv_sec; + + /* + * Move items that are ripe from pend_list to ready_list. + * Actors are in sorted order of ascending run time, so + * stop at the first unripe entry. + */ + log_debug(7, "current time %" PRIu64, current_time); + + list_for_each_entry_safe(thread, tmp, &pend_list, list) { + uint64_t time_left = actor_time_left(thread, current_time); + if (time_left) { + log_debug(7, "thread %08lx due %" PRIu64 ", wait %" PRIu64 " more", + (long)thread, thread->ttschedule, time_left); + + alarm(time_left); + break; + } + + /* This entry can be run now */ + list_del_init(&thread->list); + + log_debug(2, "thread %08lx was scheduled for " + "%" PRIu64 ", curtime %" PRIu64 " q_forw %p " + "&pend_list %p", + (long)thread, thread->ttschedule, + current_time, pend_list.next, &pend_list); + + list_add_tail(&thread->list, &ready_list); + assert(thread->state == ACTOR_WAITING); + thread->state = ACTOR_SCHEDULED; + log_debug(7, "thread %08lx now in ready_list", + (long)thread); + } + + /* Disable alarm if nothing else pending */ + if (list_empty(&pend_list)) { + log_debug(7, "nothing on pend_list, deactivating alarm"); + alarm(0); + } + + poll_in_progress = 1; + while (!list_empty(&ready_list)) { + thread = list_first_entry(&ready_list, struct actor, list); + list_del_init(&thread->list); + + if (thread->state != ACTOR_SCHEDULED) + log_error("ready_list: thread state corrupted! " + "Thread with state %d in actor list.", + thread->state); + thread->state = ACTOR_NOTSCHEDULED; + log_debug(7, "exec thread %08lx callback", (long)thread); + thread->callback(thread->data); + log_debug(7, "thread %08lx done", (long)thread); + } + poll_in_progress = 0; +} diff --git a/usr/actor.h b/usr/actor.h new file mode 100644 index 0000000..f572f2e --- /dev/null +++ b/usr/actor.h @@ -0,0 +1,50 @@ +/* + * iSCSI usermode single-threaded scheduler + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#ifndef ACTOR_H +#define ACTOR_H + +#include "types.h" +#include "list.h" + +typedef enum actor_state_e { + ACTOR_INVALID, + ACTOR_WAITING, + ACTOR_SCHEDULED, + ACTOR_NOTSCHEDULED, +} actor_state_e; + +typedef struct actor { + struct list_head list; + actor_state_e state; + void *data; + void (*callback)(void * ); + time_t ttschedule; +} actor_t; + +extern void actor_init(actor_t *thread, void (*callback)(void *), void * data); +extern void actor_delete(actor_t *thread); +extern void actor_schedule_head(actor_t *thread); +extern void actor_schedule(actor_t *thread); +extern void actor_timer(actor_t *thread, uint32_t delay_secs, + void (*callback)(void *), void *data); +extern void actor_timer_mod(actor_t *thread, uint32_t new_delay_secs, + void *data); +extern void actor_poll(void); + +#endif /* ACTOR_H */ diff --git a/usr/auth.c b/usr/auth.c new file mode 100644 index 0000000..5c819c2 --- /dev/null +++ b/usr/auth.c @@ -0,0 +1,2111 @@ +/* + * iSCSI Authorization Library + * + * maintained by open-iscsi@@googlegroups.com + * + * Originally based on: + * Copyright (C) 2001 Cisco Systems, Inc. + * + * 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. + * + * This file implements the iSCSI CHAP authentication method based on + * RFC 3720. The code in this file is meant to be common for both kernel and + * user level and makes use of only limited library functions, presently only + * string.h. Routines specific to kernel, user level are implemented in + * separate files under the appropriate directories. + * This code in this files assumes a single thread of execution + * for each iscsi_acl structure, and does no locking. + */ + +#include +#include +#include +#include +#include + +#include "sysdeps.h" +#include "auth.h" +#include "initiator.h" +#include "log.h" + +static const char acl_hexstring[] = "0123456789abcdefABCDEF"; +static const char acl_base64_string[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char acl_authmethod_set_chap_alg_list[] = "CHAP"; +static const char acl_reject_option_name[] = "Reject"; + +#include +static int auth_hash_init(EVP_MD_CTX **context, int chap_alg); +static void auth_hash_update(EVP_MD_CTX *context, unsigned char *md, unsigned int); +static unsigned int auth_hash_final(unsigned char *, EVP_MD_CTX *context); + +void get_random_bytes(unsigned char *data, unsigned int length); +size_t strlcpy(char *, const char *, size_t); +size_t strlcat(char *, const char *, size_t); + +enum auth_dbg_status +acl_chap_compute_rsp(struct iscsi_acl *client, int rmt_auth, unsigned int id, + unsigned char *challenge_data, + unsigned int challenge_length, + unsigned char *response_data) +{ + unsigned char id_data[1]; + EVP_MD_CTX *context = NULL; + unsigned char out_data[AUTH_STR_MAX_LEN]; + unsigned int out_length = AUTH_STR_MAX_LEN; + + if (!client->passwd_present) + return AUTH_DBG_STATUS_LOCAL_PASSWD_NOT_SET; + + if (auth_hash_init(&context, client->negotiated_chap_alg) != 0) + return AUTH_DBG_STATUS_AUTH_FAIL; + + /* id byte */ + id_data[0] = id; + auth_hash_update(context, id_data, 1); + + /* decrypt password */ + if (acl_data(out_data, &out_length, client->passwd_data, + client->passwd_length)) + return AUTH_DBG_STATUS_PASSWD_DECRYPT_FAILED; + + if (!rmt_auth && !client->ip_sec && out_length < 12) + return AUTH_DBG_STATUS_PASSWD_TOO_SHORT_WITH_NO_IPSEC; + + /* shared secret */ + auth_hash_update(context, out_data, out_length); + + /* clear decrypted password */ + memset(out_data, 0, AUTH_STR_MAX_LEN); + + /* challenge value */ + auth_hash_update(context, challenge_data, challenge_length); + + auth_hash_final(response_data, context); + + return AUTH_DBG_STATUS_NOT_SET; /* no error */ +} + +/* + * Authenticate a target's CHAP response. + */ +int +acl_chap_auth_request(struct iscsi_acl *client, char *username, unsigned int id, + unsigned char *challenge_data, + unsigned int challenge_length, + unsigned char *response_data, + unsigned int rsp_length) +{ + iscsi_session_t *session = client->session_handle; + EVP_MD_CTX *context = NULL; + unsigned char verify_data[client->chap_challenge_len]; + + /* the expected credentials are in the session */ + if (session->username_in == NULL) { + log_error("failing authentication, no incoming username " + "configured to authenticate target %s", + session->target_name); + return AUTH_STATUS_FAIL; + } + if (strcmp(username, session->username_in) != 0) { + log_error("failing authentication, received incorrect " + "username from target %s", session->target_name); + return AUTH_STATUS_FAIL; + } + + if ((session->password_in_length < 1) || + (session->password_in == NULL) || + (session->password_in[0] == '\0')) { + log_error("failing authentication, no incoming password " + "configured to authenticate target %s", + session->target_name); + return AUTH_STATUS_FAIL; + } + + /* challenge length is I->T, and shouldn't need to be checked */ + + if (rsp_length != sizeof(verify_data)) { + log_error("failing authentication, received incorrect " + "CHAP response length %u from target %s", + rsp_length, session->target_name); + return AUTH_STATUS_FAIL; + } + + if (auth_hash_init(&context, client->negotiated_chap_alg) != 0) + return AUTH_STATUS_FAIL; + + /* id byte */ + verify_data[0] = id; + auth_hash_update(context, verify_data, 1); + + /* shared secret */ + auth_hash_update(context, (unsigned char *)session->password_in, + session->password_in_length); + + /* challenge value */ + auth_hash_update(context, (unsigned char *)challenge_data, + challenge_length); + + auth_hash_final(verify_data, context); + + if (memcmp(response_data, verify_data, sizeof(verify_data)) == 0) { + log_debug(1, "initiator authenticated target %s", + session->target_name); + return AUTH_STATUS_PASS; + } + + log_error("failing authentication, received incorrect CHAP " + "response from target %s", session->target_name); + return AUTH_STATUS_FAIL; +} + +static int auth_hash_init(EVP_MD_CTX **context, int chap_alg) { + const EVP_MD *digest = NULL; + *context = EVP_MD_CTX_new(); + int rc; + + switch (chap_alg) { + case AUTH_CHAP_ALG_MD5: + digest = EVP_md5(); + break; + case AUTH_CHAP_ALG_SHA1: + digest = EVP_sha1(); + break; + case AUTH_CHAP_ALG_SHA256: + digest = EVP_sha256(); + break; + case AUTH_CHAP_ALG_SHA3_256: + digest = EVP_sha3_256(); + break; + } + + if (*context == NULL) + goto fail_context; + if (digest == NULL) + goto fail_digest; + rc = EVP_DigestInit_ex(*context, digest, NULL); + if (!rc) + goto fail_init; + + return 0; + +fail_init: +fail_digest: + EVP_MD_CTX_free(*context); + *context = NULL; +fail_context: + return -1; +} + +static void auth_hash_update(EVP_MD_CTX *context, unsigned char *data, unsigned int length) { + EVP_DigestUpdate(context, data, length); +} + +static unsigned int auth_hash_final(unsigned char *hash, EVP_MD_CTX *context) { + unsigned int md_len; + EVP_DigestFinal_ex(context, hash, &md_len); + EVP_MD_CTX_free(context); + context = NULL; + return md_len; +} + +void +get_random_bytes(unsigned char *data, unsigned int length) +{ + + long r; + unsigned n; + int fd, r_size = sizeof(r); + + fd = open("/dev/urandom", O_RDONLY); + while (length > 0) { + + if (fd == -1 || read(fd, &r, r_size) != r_size) + r = rand(); + r = r ^ (r >> 8); + r = r ^ (r >> 4); + n = r & 0x7; + + if (fd == -1 || read(fd, &r, r_size) != r_size) + r = rand(); + r = r ^ (r >> 8); + r = r ^ (r >> 5); + n = (n << 3) | (r & 0x7); + + if (fd == -1 || read(fd, &r, r_size) != r_size) + r = rand(); + r = r ^ (r >> 8); + r = r ^ (r >> 5); + n = (n << 2) | (r & 0x3); + + *data++ = n; + length--; + } + if (fd) + close(fd); +} + +static const char acl_none_option_name[] = "None"; + +static int +acl_text_to_number(const char *text, unsigned long *num) +{ + char *end; + unsigned long number; + + if (text[0] == '0' && (text[1] == 'x' || text[1] == 'X')) + number = strtoul(text + 2, &end, 16); + else + number = strtoul(text, &end, 10); + + if (*text != '\0' && *end == '\0') { + *num = number; + return 0; /* No error */ + } else + return 1; /* Error */ +} + +static int +acl_chk_string(const char *s, unsigned int max_len, unsigned int *out_len) +{ + unsigned int len; + + if (!s) + return 1; + + for (len = 0; len < max_len; len++) + if (*s++ == '\0') { + if (out_len) + *out_len = len; + return 0; + } + + return 1; +} + +static int +acl_str_index(const char *s, int c) +{ + char *str = strchr(s, c); + + if (str) + return (str - s); + else + return -1; +} + +static int +acl_chk_auth_mthd_optn(int val) +{ + if (val == AUTH_OPTION_NONE || val == AUTH_METHOD_CHAP) + return 0; + + return 1; +} + +static const char * +acl_authmethod_optn_to_text(int value) +{ + const char *s; + switch (value) { + case AUTH_OPTION_REJECT: + s = acl_reject_option_name; + break; + case AUTH_OPTION_NONE: + s = acl_none_option_name; + break; + case AUTH_METHOD_CHAP: + s = acl_authmethod_set_chap_alg_list; + break; + default: + s = NULL; + } + return s; +} + +static int +acl_chk_chap_alg_optn(int chap_algorithm) +{ + if (chap_algorithm == AUTH_OPTION_NONE || + chap_algorithm == AUTH_CHAP_ALG_SHA3_256 || + chap_algorithm == AUTH_CHAP_ALG_SHA256 || + chap_algorithm == AUTH_CHAP_ALG_SHA1 || + chap_algorithm == AUTH_CHAP_ALG_MD5) + return 0; + + return 1; +} + +static int +acl_data_to_text(unsigned char *data, unsigned int data_length, char *text, + unsigned int text_length) +{ + unsigned long n; + + if (!text || text_length == 0) + return 1; + + if (!data || data_length == 0) { + *text = '\0'; + return 1; + } + + if (text_length < 3) { + *text = '\0'; + return 1; + } + + *text++ = '0'; + *text++ = 'x'; + + text_length -= 2; + + while (data_length > 0) { + + if (text_length < 3) { + *text = '\0'; + return 1; + } + + n = *data++; + data_length--; + + *text++ = acl_hexstring[(n >> 4) & 0xf]; + *text++ = acl_hexstring[n & 0xf]; + + text_length -= 2; + } + + *text = '\0'; + + return 0; +} + +static int +acl_hex_to_data(const char *text, unsigned int text_length, unsigned char *data, + unsigned int *data_lenp) +{ + int i; + unsigned int n1; + unsigned int n2; + unsigned int data_length = *data_lenp; + + if ((text_length % 2) == 1) { + + i = acl_str_index(acl_hexstring, *text++); + if (i < 0) + return 1; /* error, bad character */ + + if (i > 15) + i -= 6; + n2 = i; + + if (data_length < 1) + return 1; /* error, too much data */ + + *data++ = n2; + data_length--; + } + + while (*text != '\0') { + i = acl_str_index(acl_hexstring, *text++); + if (i < 0) + return 1; /* error, bad character */ + + if (i > 15) + i -= 6; + n1 = i; + + if (*text == '\0') + return 1; /* error, odd string length */ + + i = acl_str_index(acl_hexstring, *text++); + if (i < 0) + return 1; /* error, bad character */ + + if (i > 15) + i -= 6; + n2 = i; + + if (data_length < 1) + return 1; /* error, too much data */ + + *data++ = (n1 << 4) | n2; + data_length--; + } + + if (data_length >= *data_lenp) + return 1; /* error, no data */ + + *data_lenp = *data_lenp - data_length; + + return 0; /* no error */ +} + +static int +acl_base64_to_data(const char *text, unsigned char *data, + unsigned int *data_lenp) +{ + int i; + unsigned int n; + unsigned int count; + unsigned int data_length = *data_lenp; + + n = 0; + count = 0; + + while (*text != '\0' && *text != '=') { + + i = acl_str_index(acl_base64_string, *text++); + if (i < 0) + return 1; /* error, bad character */ + + n = (n << 6 | (unsigned int)i); + count++; + + if (count >= 4) { + if (data_length < 3) + return 1; /* error, too much data */ + *data++ = n >> 16; + *data++ = n >> 8; + *data++ = n; + data_length -= 3; + n = 0; + count = 0; + } + } + + while (*text != '\0') + if (*text++ != '=') + return 1; /* error, bad pad */ + + if (count == 0) { + /* do nothing */ + } else if (count == 2) { + if (data_length < 1) + return 1; /* error, too much data */ + n = n >> 4; + *data++ = n; + data_length--; + } else if (count == 3) { + if (data_length < 2) + return 1; /* error, too much data */ + n = n >> 2; + *data++ = n >> 8; + *data++ = n; + data_length -= 2; + } else + return 1; /* bad encoding */ + + if (data_length >= *data_lenp) + return 1; /* error, no data */ + + *data_lenp = *data_lenp - data_length; + + return 0; /* no error */ +} + +static int +acl_text_to_data(const char *text, unsigned char *data, + unsigned int *data_length) +{ + int status; + unsigned int text_length; + + status = acl_chk_string(text, 2 + 2 * AUTH_LARGE_BINARY_MAX_LEN + 1, + &text_length); + if (status) + return status; + + if (text[0] == '0' && (text[1] == 'x' || text[1] == 'X')) { + /* skip prefix */ + text += 2; + text_length -= 2; + status = acl_hex_to_data(text, text_length, data, data_length); + } else if (text[0] == '0' && (text[1] == 'b' || text[1] == 'B')) { + /* skip prefix */ + text += 2; + text_length -= 2; + status = acl_base64_to_data(text, data, data_length); + } else + status = 1; /* prefix not recognized. */ + + return status; +} + +static void +acl_init_key_blk(struct auth_key_block *key_blk) +{ + char *str_block = key_blk->str_block; + + memset(key_blk, 0, sizeof(*key_blk)); + key_blk->str_block = str_block; +} + +static void +acl_set_key_value(struct auth_key_block *key_blk, int key_type, + const char *key_val) +{ + unsigned int length; + char *string; + + if (key_blk->key[key_type].value_set) { + key_blk->dup_set = 1; + return; + } + + key_blk->key[key_type].value_set = 1; + + if (!key_val) + return; + + if (acl_chk_string(key_val, AUTH_STR_MAX_LEN, &length)) { + key_blk->str_too_long = 1; + return; + } + + length += 1; + + if ((key_blk->blk_length + length) > AUTH_STR_BLOCK_MAX_LEN) { + key_blk->too_much_data = 1; + return; + } + + string = &key_blk->str_block[key_blk->blk_length]; + + if (strlcpy(string, key_val, length) >= length) { + key_blk->too_much_data = 1; + return; + } + key_blk->blk_length += length; + + key_blk->key[key_type].string = string; + key_blk->key[key_type].present = 1; +} + +static const char * +acl_get_key_val(struct auth_key_block *key_blk, int key_type) +{ + key_blk->key[key_type].processed = 1; + + if (!key_blk->key[key_type].present) + return NULL; + + return key_blk->key[key_type].string; +} + +static void +acl_chk_key(struct iscsi_acl *client, int key_type, int *negotiated_option, + unsigned int option_count, int *option_list, + const char *(*value_to_text) (int)) +{ + const char *key_val; + int length; + unsigned int i; + + key_val = acl_get_key_val(&client->recv_key_block, key_type); + if (!key_val) { + *negotiated_option = AUTH_OPTION_NOT_PRESENT; + return; + } + + while (*key_val != '\0') { + + length = 0; + + while (*key_val != '\0' && *key_val != ',') + client->scratch_key_value[length++] = *key_val++; + + if (*key_val == ',') + key_val++; + client->scratch_key_value[length++] = '\0'; + + for (i = 0; i < option_count; i++) { + const char *s = (*value_to_text)(option_list[i]); + + if (!s) + continue; + + if (strcmp(client->scratch_key_value, s) == 0) { + *negotiated_option = option_list[i]; + return; + } + } + } + + *negotiated_option = AUTH_OPTION_REJECT; +} + +static void +acl_set_key(struct iscsi_acl *client, int key_type, unsigned int option_count, + int *option_list, const char *(*value_to_text)(int)) +{ + unsigned int i; + + if (option_count == 0) { + /* + * No valid options to send, but we always want to + * send something. + */ + acl_set_key_value(&client->send_key_block, key_type, + acl_none_option_name); + return; + } + + if (option_count == 1 && option_list[0] == AUTH_OPTION_NOT_PRESENT) { + acl_set_key_value(&client->send_key_block, key_type, NULL); + return; + } + + for (i = 0; i < option_count; i++) { + const char *s = (*value_to_text)(option_list[i]); + + if (!s) + continue; + + if (i == 0) + strlcpy(client->scratch_key_value, s, + AUTH_STR_MAX_LEN); + else { + strlcat(client->scratch_key_value, ",", + AUTH_STR_MAX_LEN); + strlcat(client->scratch_key_value, s, + AUTH_STR_MAX_LEN); + } + } + + acl_set_key_value(&client->send_key_block, key_type, + client->scratch_key_value); +} + +static void +acl_chk_auth_method_key(struct iscsi_acl *client) +{ + acl_chk_key(client, AUTH_KEY_TYPE_AUTH_METHOD, + &client->negotiated_auth_method, + client->auth_method_valid_count, + client->auth_method_valid_list, + acl_authmethod_optn_to_text); +} + +static void +acl_set_auth_method_key(struct iscsi_acl *client, + unsigned int auth_method_count, int *auth_method_list) +{ + acl_set_key(client, AUTH_KEY_TYPE_AUTH_METHOD, auth_method_count, + auth_method_list, acl_authmethod_optn_to_text); +} + +static void +acl_chk_chap_alg_key(struct iscsi_acl *client) +{ + const char *key_val; + int length; + unsigned long number; + unsigned int i; + + key_val = acl_get_key_val(&client->recv_key_block, + AUTH_KEY_TYPE_CHAP_ALG); + if (!key_val) { + client->negotiated_chap_alg = AUTH_OPTION_NOT_PRESENT; + return; + } + + while (*key_val != '\0') { + + length = 0; + + while (*key_val != '\0' && *key_val != ',') + client->scratch_key_value[length++] = *key_val++; + + if (*key_val == ',') + key_val++; + client->scratch_key_value[length++] = '\0'; + + if (acl_text_to_number(client->scratch_key_value, &number)) + continue; + + + for (i = 0; i < client->chap_alg_count; i++) + if (number == (unsigned long)client->chap_alg_list[i]) + { + client->negotiated_chap_alg = number; + switch (number) { + case AUTH_CHAP_ALG_MD5: + client->chap_challenge_len = AUTH_CHAP_MD5_RSP_LEN; + break; + case AUTH_CHAP_ALG_SHA1: + client->chap_challenge_len = AUTH_CHAP_SHA1_RSP_LEN; + break; + case AUTH_CHAP_ALG_SHA256: + client->chap_challenge_len = AUTH_CHAP_SHA256_RSP_LEN; + break; + case AUTH_CHAP_ALG_SHA3_256: + client->chap_challenge_len = AUTH_CHAP_SHA3_256_RSP_LEN; + break; + } + return; + } + } + + client->negotiated_chap_alg = AUTH_OPTION_REJECT; +} + +static void +acl_set_chap_alg_key(struct iscsi_acl *client, unsigned int chap_alg_count, + int *chap_alg_list) +{ + unsigned int i; + + if (chap_alg_count == 0) { + acl_set_key_value(&client->send_key_block, + AUTH_KEY_TYPE_CHAP_ALG, NULL); + return; + } + + if (chap_alg_count == 1 && + chap_alg_list[0] == AUTH_OPTION_NOT_PRESENT) { + acl_set_key_value(&client->send_key_block, + AUTH_KEY_TYPE_CHAP_ALG, NULL); + return; + } + + if (chap_alg_count == 1 && chap_alg_list[0] == AUTH_OPTION_REJECT) { + acl_set_key_value(&client->send_key_block, + AUTH_KEY_TYPE_CHAP_ALG, + acl_reject_option_name); + return; + } + + for (i = 0; i < chap_alg_count; i++) { + char s[20]; + + snprintf(s, sizeof(s), "%lu",(unsigned long)chap_alg_list[i]); + + if (i == 0) + strlcpy(client->scratch_key_value, s, + AUTH_STR_MAX_LEN); + else { + strlcat(client->scratch_key_value, ",", + AUTH_STR_MAX_LEN); + strlcat(client->scratch_key_value, s, + AUTH_STR_MAX_LEN); + } + } + + acl_set_key_value(&client->send_key_block, AUTH_KEY_TYPE_CHAP_ALG, + client->scratch_key_value); +} + +static void +acl_next_phase(struct iscsi_acl *client) +{ + switch (client->phase) { + case AUTH_PHASE_CONFIGURE: + client->phase = AUTH_PHASE_NEGOTIATE; + break; + case AUTH_PHASE_NEGOTIATE: + client->phase = AUTH_PHASE_AUTHENTICATE; + + if (client->negotiated_auth_method == AUTH_OPTION_REJECT || + client->negotiated_auth_method == AUTH_OPTION_NOT_PRESENT || + client->negotiated_auth_method == AUTH_OPTION_NONE) { + + client->local_state = AUTH_LOCAL_STATE_DONE; + client->rmt_state = AUTH_RMT_STATE_DONE; + + if (client->auth_rmt) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + } else + client->rmt_auth_status = AUTH_STATUS_PASS; + + switch (client->negotiated_auth_method) { + case AUTH_OPTION_REJECT: + client->dbg_status = + AUTH_DBG_STATUS_AUTH_METHOD_REJECT; + break; + case AUTH_OPTION_NOT_PRESENT: + client->dbg_status = + AUTH_DBG_STATUS_AUTH_METHOD_NOT_PRESENT; + break; + case AUTH_OPTION_NONE: + client->dbg_status = + AUTH_DBG_STATUS_AUTH_METHOD_NONE; + } + + } else if (client->negotiated_auth_method == AUTH_METHOD_CHAP) { + client->local_state = AUTH_LOCAL_STATE_SEND_ALG; + client->rmt_state = AUTH_RMT_STATE_SEND_ALG; + } else { + + client->local_state = AUTH_LOCAL_STATE_DONE; + client->rmt_state = AUTH_RMT_STATE_DONE; + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->dbg_status = AUTH_DBG_STATUS_AUTH_METHOD_BAD; + } + break; + case AUTH_PHASE_AUTHENTICATE: + client->phase = AUTH_PHASE_DONE; + break; + case AUTH_PHASE_DONE: + case AUTH_PHASE_ERROR: + default: + client->phase = AUTH_PHASE_ERROR; + } +} + +static void +acl_local_auth(struct iscsi_acl *client) +{ + unsigned int chap_identifier; + unsigned char response_data[AUTH_CHAP_RSP_MAX]; + unsigned long number; + int status; + enum auth_dbg_status dbg_status; + const char *chap_identifier_key_val; + const char *chap_challenge_key_val; + + switch (client->local_state) { + case AUTH_LOCAL_STATE_SEND_ALG: + if (client->node_type == TYPE_INITIATOR) { + acl_set_chap_alg_key(client, client->chap_alg_count, + client->chap_alg_list); + client->local_state = AUTH_LOCAL_STATE_RECV_ALG; + break; + } + /* Fall through */ + case AUTH_LOCAL_STATE_RECV_ALG: + acl_chk_chap_alg_key(client); + + if (client->node_type == TYPE_TARGET) + acl_set_chap_alg_key(client, 1, + &client->negotiated_chap_alg); + + /* Make sure only supported CHAP algorithm is used. */ + if (client->negotiated_chap_alg == AUTH_OPTION_NOT_PRESENT) { + client->local_state = AUTH_LOCAL_STATE_ERROR; + client->dbg_status = AUTH_DBG_STATUS_CHAP_ALG_EXPECTED; + break; + } else if (client->negotiated_chap_alg == AUTH_OPTION_REJECT) { + client->local_state = AUTH_LOCAL_STATE_ERROR; + client->dbg_status = AUTH_DBG_STATUS_CHAP_ALG_REJECT; + break; + } else if ((client->negotiated_chap_alg != AUTH_CHAP_ALG_SHA3_256) && + (client->negotiated_chap_alg != AUTH_CHAP_ALG_SHA256) && + (client->negotiated_chap_alg != AUTH_CHAP_ALG_SHA1) && + (client->negotiated_chap_alg != AUTH_CHAP_ALG_MD5)) { + client->local_state = AUTH_LOCAL_STATE_ERROR; + client->dbg_status = AUTH_DBG_STATUS_CHAP_ALG_BAD; + break; + } + if (client->node_type == TYPE_TARGET) { + client->local_state = AUTH_LOCAL_STATE_RECV_CHALLENGE; + break; + } + /* Fall through */ + case AUTH_LOCAL_STATE_RECV_CHALLENGE: + chap_identifier_key_val = acl_get_key_val(&client->recv_key_block, + AUTH_KEY_TYPE_CHAP_IDENTIFIER); + chap_challenge_key_val = acl_get_key_val(&client->recv_key_block, + AUTH_KEY_TYPE_CHAP_CHALLENGE); + if (client->node_type == TYPE_TARGET) { + if (!chap_identifier_key_val && + !chap_challenge_key_val) { + client->local_state = AUTH_LOCAL_STATE_DONE; + break; + } + } + + if (!chap_identifier_key_val) { + client->local_state = AUTH_LOCAL_STATE_ERROR; + client->dbg_status = + AUTH_DBG_STATUS_CHAP_IDENTIFIER_EXPECTED; + break; + } + + if (!chap_challenge_key_val) { + client->local_state = AUTH_LOCAL_STATE_ERROR; + client->dbg_status = + AUTH_DBG_STATUS_CHAP_CHALLENGE_EXPECTED; + break; + } + + status = acl_text_to_number(chap_identifier_key_val, &number); + if (status || (255 < number)) { + client->local_state = AUTH_LOCAL_STATE_ERROR; + client->dbg_status = AUTH_DBG_STATUS_CHAP_IDENTIFIER_BAD; + break; + } + chap_identifier = number; + + if (client->recv_chap_challenge_status) { + client->local_state = AUTH_LOCAL_STATE_ERROR; + client->dbg_status = AUTH_DBG_STATUS_CHALLENGE_BAD; + break; + } + + if (client->node_type == TYPE_TARGET && + client->recv_chap_challenge.length == + client->send_chap_challenge.length && + memcmp(client->recv_chap_challenge.large_binary, + client->send_chap_challenge.large_binary, + client->send_chap_challenge.length) == 0) { + client->local_state = AUTH_LOCAL_STATE_ERROR; + client->dbg_status = + AUTH_DBG_STATUS_CHAP_CHALLENGE_REFLECTED; + break; + } + + dbg_status = acl_chap_compute_rsp(client, 0, + chap_identifier, + client->recv_chap_challenge.large_binary, + client->recv_chap_challenge.length, + response_data); + + if (dbg_status != AUTH_DBG_STATUS_NOT_SET) { + client->local_state = AUTH_LOCAL_STATE_ERROR; + client->dbg_status = dbg_status; + break; + } + + acl_data_to_text(response_data, client->chap_challenge_len, + client->scratch_key_value, + AUTH_STR_MAX_LEN); + acl_set_key_value(&client->send_key_block, + AUTH_KEY_TYPE_CHAP_RSP, + client->scratch_key_value); + acl_set_key_value(&client->send_key_block, + AUTH_KEY_TYPE_CHAP_USERNAME, + client->username); + + client->local_state = AUTH_LOCAL_STATE_DONE; + break; + case AUTH_LOCAL_STATE_DONE: + break; + case AUTH_LOCAL_STATE_ERROR: + default: + client->phase = AUTH_PHASE_ERROR; + } +} + +static void +acl_rmt_auth(struct iscsi_acl *client) +{ + unsigned char id_data[1]; + unsigned char response_data[AUTH_STR_MAX_LEN]; + unsigned int rsp_len = AUTH_STR_MAX_LEN; + unsigned char my_rsp_data[AUTH_CHAP_RSP_MAX]; + int status; + enum auth_dbg_status dbg_status; + const char *chap_rsp_key_val; + const char *chap_username_key_val; + + switch (client->rmt_state) { + case AUTH_RMT_STATE_SEND_ALG: + if (client->node_type == TYPE_INITIATOR) { + client->rmt_state = AUTH_RMT_STATE_SEND_CHALLENGE; + break; + } + /* Fall through */ + case AUTH_RMT_STATE_SEND_CHALLENGE: + if (!client->auth_rmt) { + client->rmt_auth_status = AUTH_STATUS_PASS; + client->dbg_status = AUTH_DBG_STATUS_AUTH_RMT_FALSE; + client->rmt_state = AUTH_RMT_STATE_DONE; + break; + } + get_random_bytes(id_data, 1); + client->send_chap_identifier = id_data[0]; + snprintf(client->scratch_key_value, AUTH_STR_MAX_LEN, "%lu", + (unsigned long)client->send_chap_identifier); + acl_set_key_value(&client->send_key_block, + AUTH_KEY_TYPE_CHAP_IDENTIFIER, + client->scratch_key_value); + + client->send_chap_challenge.length = client->chap_challenge_len; + get_random_bytes(client->send_chap_challenge.large_binary, + client->send_chap_challenge.length); + acl_set_key_value(&client->send_key_block, + AUTH_KEY_TYPE_CHAP_CHALLENGE, ""); + + client->rmt_state = AUTH_RMT_STATE_RECV_RSP; + break; + case AUTH_RMT_STATE_RECV_RSP: + chap_rsp_key_val = acl_get_key_val(&client->recv_key_block, + AUTH_KEY_TYPE_CHAP_RSP); + chap_username_key_val = acl_get_key_val(&client->recv_key_block, + AUTH_KEY_TYPE_CHAP_USERNAME); + + if (!chap_rsp_key_val) { + client->rmt_state = AUTH_RMT_STATE_ERROR; + client->dbg_status = AUTH_DBG_STATUS_CHAP_RSP_EXPECTED; + break; + } + + if (!chap_username_key_val) { + client->rmt_state = AUTH_RMT_STATE_ERROR; + client->dbg_status = AUTH_DBG_STATUS_CHAP_USERNAME_EXPECTED; + break; + } + + status = acl_text_to_data(chap_rsp_key_val, response_data, + &rsp_len); + + if (status) { + client->rmt_state = AUTH_RMT_STATE_ERROR; + client->dbg_status = AUTH_DBG_STATUS_CHAP_RSP_BAD; + break; + } + + if (rsp_len == client->chap_challenge_len) { + dbg_status = acl_chap_compute_rsp(client, 1, + client->send_chap_identifier, + client->send_chap_challenge.large_binary, + client->send_chap_challenge.length, + my_rsp_data); + + if (dbg_status == AUTH_DBG_STATUS_NOT_SET && + memcmp(my_rsp_data, response_data, + client->chap_challenge_len) == 0) { + client->rmt_state = AUTH_RMT_STATE_ERROR; + client->dbg_status = AUTH_DBG_STATUS_PASSWD_IDENTICAL; + break; + } + } + + strlcpy(client->chap_username, chap_username_key_val, + AUTH_STR_MAX_LEN); + + status = acl_chap_auth_request(client, client->chap_username, + client->send_chap_identifier, + client->send_chap_challenge. + large_binary, + client->send_chap_challenge. + length, response_data, + rsp_len); + + client->rmt_auth_status = (enum auth_status) status; + client->auth_rsp_flag = 1; + + if (client->auth_server_error_flag) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->dbg_status = AUTH_DBG_STATUS_AUTH_SERVER_ERROR; + } else if (client->rmt_auth_status == AUTH_STATUS_PASS) + client->dbg_status = AUTH_DBG_STATUS_AUTH_PASS; + else if (client->rmt_auth_status == AUTH_STATUS_FAIL) + client->dbg_status = AUTH_DBG_STATUS_AUTH_FAIL; + else { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->dbg_status = AUTH_DBG_STATUS_AUTH_STATUS_BAD; + } + client->rmt_state = AUTH_RMT_STATE_DONE; + + /* Fall through */ + case AUTH_RMT_STATE_DONE: + break; + case AUTH_RMT_STATE_ERROR: + default: + client->phase = AUTH_PHASE_ERROR; + } +} + +static void +acl_hand_shake(struct iscsi_acl *client) +{ + if (client->phase == AUTH_PHASE_DONE) + + /* + * Should only happen if authentication + * protocol error occurred. + */ + return; + + if (client->node_type == TYPE_INITIATOR) + + /* + * Target should only have set T bit on response if + * initiator set it on previous message. + */ + if (client->recv_key_block.transit_bit && + !client->transit_bit_sent_flag) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = + AUTH_DBG_STATUS_T_BIT_SET_ILLEGAL; + return; + } + + if (client->phase == AUTH_PHASE_NEGOTIATE) { + /* + * Should only happen if waiting for peer + * to send AuthMethod key or set Transit Bit. + */ + if (client->node_type == TYPE_INITIATOR) + client->send_key_block.transit_bit = 1; + return; + } + + if (client->rmt_state == AUTH_RMT_STATE_RECV_RSP || + client->rmt_state == AUTH_RMT_STATE_DONE) { + if (client->node_type == TYPE_INITIATOR) { + if (client->recv_key_block.transit_bit) { + if (client->rmt_state != + AUTH_RMT_STATE_DONE) + goto recv_transit_bit_err; + acl_next_phase(client); + } else + client->send_key_block.transit_bit = 1; + } else { + if (client->rmt_state == AUTH_RMT_STATE_DONE && + client->rmt_auth_status != AUTH_STATUS_PASS) + /* + * Authentication failed, don't do T bit + * handshake. + */ + acl_next_phase(client); + else { + /* + * Target can only set T bit on response if + * initiator set it on current message. + */ + if (client->recv_key_block.transit_bit) { + client->send_key_block.transit_bit = 1; + acl_next_phase(client); + } + } + } + } else + if (client->node_type == TYPE_INITIATOR) + if (client->recv_key_block.transit_bit) + goto recv_transit_bit_err; + return; + + recv_transit_bit_err: + /* + * Target set T bit on response but + * initiator was not done with authentication. + */ + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = AUTH_DBG_STATUS_T_BIT_SET_PREMATURE; +} + +static int +acl_rcv_end_status(struct iscsi_acl *client) +{ + int auth_status; + int key_type; + + if (client->phase == AUTH_PHASE_ERROR) + return AUTH_STATUS_ERROR; + + if (client->phase == AUTH_PHASE_DONE) { + + /* Perform sanity check against configured parameters. */ + if (client->auth_rmt && !client->auth_rsp_flag && + client->rmt_auth_status == AUTH_STATUS_PASS) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->dbg_status = AUTH_DBG_STATUS_AUTHPASS_NOT_VALID; + } + + auth_status = client->rmt_auth_status; + + } else + auth_status = AUTH_STATUS_CONTINUE; + + if (auth_status == AUTH_STATUS_CONTINUE || + auth_status == AUTH_STATUS_PASS) { + if (client->send_key_block.dup_set) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = + AUTH_DBG_STATUS_SEND_DUP_SET_KEY_VALUE; + auth_status = AUTH_STATUS_FAIL; + } else if (client->send_key_block.str_too_long) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = + AUTH_DBG_STATUS_SEND_STR_TOO_LONG; + auth_status = AUTH_STATUS_FAIL; + } else if (client->send_key_block.too_much_data) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = + AUTH_DBG_STATUS_SEND_TOO_MUCH_DATA; + auth_status = AUTH_STATUS_FAIL; + } else { + /* Check that all incoming keys have been processed. */ + + for (key_type = AUTH_KEY_TYPE_FIRST; + key_type < AUTH_KEY_TYPE_MAX_COUNT; key_type++) + if (client->recv_key_block.key[key_type].present && + !client->recv_key_block.key[key_type]. + processed) + break; + + if (key_type < AUTH_KEY_TYPE_MAX_COUNT) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = + AUTH_DBG_STATUS_UNEXPECTED_KEY_PRESENT; + auth_status = AUTH_STATUS_FAIL; + } + } + } + + if (auth_status != AUTH_STATUS_PASS && + auth_status != AUTH_STATUS_CONTINUE) { + int auth_method_key_present = 0; + int chap_alg_key_present = 0; + + /* + * Suppress send keys on error, + * except for AuthMethod and CHAP_A. + */ + if (client->node_type == TYPE_TARGET) { + if (acl_get_key_val(&client->send_key_block, + AUTH_KEY_TYPE_AUTH_METHOD)) + auth_method_key_present = 1; + else if (acl_get_key_val(&client->send_key_block, + AUTH_KEY_TYPE_CHAP_ALG)) + chap_alg_key_present = 1; + } + + acl_init_key_blk(&client->send_key_block); + + if (client->node_type == TYPE_TARGET) { + if (auth_method_key_present && + client->negotiated_auth_method == + AUTH_OPTION_REJECT) + acl_set_key_value(&client->send_key_block, + AUTH_KEY_TYPE_AUTH_METHOD, + acl_reject_option_name); + else if (chap_alg_key_present && + client->negotiated_chap_alg == + AUTH_OPTION_REJECT) + acl_set_key_value(&client->send_key_block, + AUTH_KEY_TYPE_CHAP_ALG, + acl_reject_option_name); + } + } + client->recv_in_progress_flag = 0; + + return auth_status; +} + +int +acl_recv_begin(struct iscsi_acl *client) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase == AUTH_PHASE_ERROR) + return AUTH_STATUS_ERROR; + + if (client->phase == AUTH_PHASE_DONE) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + if (client->recv_in_progress_flag) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + client->recv_in_progress_flag = 1; + + if (client->phase == AUTH_PHASE_CONFIGURE) + acl_next_phase(client); + + client->transit_bit_sent_flag = client->send_key_block.transit_bit; + + acl_init_key_blk(&client->recv_key_block); + acl_init_key_blk(&client->send_key_block); + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_recv_end(struct iscsi_acl *client, iscsi_session_t *session_handle) +{ + int next_phase_flag = 0; + + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase == AUTH_PHASE_ERROR) + return AUTH_STATUS_ERROR; + + if (!client->recv_in_progress_flag) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + if (client->recv_end_count > AUTH_RECV_END_MAX_COUNT) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = AUTH_DBG_STATUS_RECV_MSG_COUNT_LIMIT; + } else if (client->recv_key_block.dup_set) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = AUTH_DBG_STATUS_RECV_DUP_SET_KEY_VALUE; + } else if (client->recv_key_block.str_too_long) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = AUTH_DBG_STATUS_RECV_STR_TOO_LONG; + } else if (client->recv_key_block.too_much_data) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = AUTH_DBG_STATUS_RECV_TOO_MUCH_DATA; + } + + client->recv_end_count++; + client->session_handle = session_handle; + + switch (client->phase) { + case AUTH_PHASE_NEGOTIATE: + acl_chk_auth_method_key(client); + if (client->auth_method_valid_neg_role == + AUTH_NEG_ROLE_RESPONDER) { + if (client->negotiated_auth_method == + AUTH_OPTION_NOT_PRESENT) { + if (client->auth_rmt || + !client->recv_key_block.transit_bit) { + /* + * No AuthMethod key from peer on + * first message, try moving the + * process along by sending the + * AuthMethod key. + */ + + client->auth_method_valid_neg_role = + AUTH_NEG_ROLE_ORIGINATOR; + acl_set_auth_method_key(client, + client->auth_method_valid_count, + client->auth_method_valid_list); + break; + } + + /* + * Special case if peer sent no AuthMethod key, + * but did set Transit Bit, allowing this side + * to do a null authentication, and compelete + * the iSCSI security phase without either side + * sending the AuthMethod key. + */ + } else + /* Send response to AuthMethod key. */ + acl_set_auth_method_key(client, 1, + &client->negotiated_auth_method); + + if (client->node_type == TYPE_INITIATOR) + acl_next_phase(client); + else + next_phase_flag = 1; + } else { + + if (client->negotiated_auth_method == + AUTH_OPTION_NOT_PRESENT) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = + AUTH_DBG_STATUS_AUTH_METHOD_EXPECTED; + break; + } + + acl_next_phase(client); + } + break; + case AUTH_PHASE_AUTHENTICATE: + case AUTH_PHASE_DONE: + break; + default: + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + switch (client->phase) { + case AUTH_PHASE_NEGOTIATE: + if (next_phase_flag) + acl_next_phase(client); + break; + case AUTH_PHASE_AUTHENTICATE: + /* + * Must call acl_local_auth() + * before acl_rmt_auth() + * to insure processing of the CHAP algorithm key, + * and to avoid leaving an in progress request to the + * authentication service. + */ + acl_local_auth(client); + + if (client->local_state != AUTH_LOCAL_STATE_ERROR) + acl_rmt_auth(client); + + if (client->local_state == AUTH_LOCAL_STATE_ERROR || + client->rmt_state == AUTH_RMT_STATE_ERROR) { + + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + /* client->dbg_status should already be set. */ + } + break; + case AUTH_PHASE_DONE: + break; + default: + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + acl_hand_shake(client); + + return acl_rcv_end_status(client); +} + +const char * +acl_get_key_name(int key_type) +{ + /* + * Note: The ordering of this table must match the order + * defined by enum auth_key_type in iscsi-auth-client.h. + */ + static char *const key_names[AUTH_KEY_TYPE_MAX_COUNT] = { + "AuthMethod", + "CHAP_A", + "CHAP_N", + "CHAP_R", + "CHAP_I", + "CHAP_C" + }; + + if (key_type < AUTH_KEY_TYPE_FIRST || key_type > AUTH_KEY_TYPE_LAST) + return NULL; + + return key_names[key_type]; +} + +int +acl_get_next_key_type(int *key_type) +{ + if (*key_type >= AUTH_KEY_TYPE_LAST) + return AUTH_STATUS_ERROR; + + if (*key_type < AUTH_KEY_TYPE_FIRST) + *key_type = AUTH_KEY_TYPE_FIRST; + else + (*key_type)++; + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_recv_key_value(struct iscsi_acl *client, int key_type, + const char *user_key_val) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_NEGOTIATE && + client->phase != AUTH_PHASE_AUTHENTICATE) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + if (key_type < AUTH_KEY_TYPE_FIRST || key_type > AUTH_KEY_TYPE_LAST) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + if (key_type == AUTH_KEY_TYPE_CHAP_CHALLENGE) { + client->recv_chap_challenge.length = + AUTH_LARGE_BINARY_MAX_LEN; + client->recv_chap_challenge_status = + acl_text_to_data(user_key_val, + client->recv_chap_challenge.large_binary, + &client->recv_chap_challenge.length); + user_key_val = ""; + } + + acl_set_key_value(&client->recv_key_block, key_type, user_key_val); + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_send_key_val(struct iscsi_acl *client, int key_type, int *key_present, + char *user_key_val, unsigned int max_length) +{ + const char *key_val; + + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_CONFIGURE && + client->phase != AUTH_PHASE_NEGOTIATE && + client->phase != AUTH_PHASE_AUTHENTICATE && + client->phase != AUTH_PHASE_DONE) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + if (key_type < AUTH_KEY_TYPE_FIRST || key_type > AUTH_KEY_TYPE_LAST) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + key_val = acl_get_key_val(&client->send_key_block, key_type); + if (key_val) { + if (key_type == AUTH_KEY_TYPE_CHAP_CHALLENGE) { + if (acl_data_to_text(client->send_chap_challenge.large_binary, + client->send_chap_challenge.length, user_key_val, + max_length)) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + } else if (strlcpy(user_key_val, key_val, max_length) >= + max_length) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + *key_present = 1; + } else + *key_present = 0; + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_recv_transit_bit(struct iscsi_acl *client, int value) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_NEGOTIATE && + client->phase != AUTH_PHASE_AUTHENTICATE) { + + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + if (value) + client->recv_key_block.transit_bit = 1; + else + client->recv_key_block.transit_bit = 0; + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_send_transit_bit(struct iscsi_acl *client, int *value) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_CONFIGURE && + client->phase != AUTH_PHASE_NEGOTIATE && + client->phase != AUTH_PHASE_AUTHENTICATE && + client->phase != AUTH_PHASE_DONE) { + + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + *value = client->send_key_block.transit_bit; + + return AUTH_STATUS_NO_ERROR; +} + +static int +acl_set_option_list(struct iscsi_acl *client, unsigned int opt_count, + const int *opt_list, unsigned int *clnt_optn_count, + int *clnt_optn_list, unsigned int optn_max_count, + int (*chk_option)(int), + int (*chk_list)(unsigned int opt_count, const int *opt_list)) +{ + unsigned int i, j; + + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_CONFIGURE || + opt_count > optn_max_count) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + for (i = 0; i < opt_count; i++) + if (chk_option(opt_list[i])) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + /* Check for duplicate entries. */ + for (i = 0; i < opt_count; i++) + for (j = 0; j < opt_count; j++) { + if (j == i) + continue; + if (opt_list[i] == opt_list[j]) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + } + + /* Check for key specific constraints. */ + if (chk_list) + if (chk_list(opt_count, opt_list)) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + for (i = 0; i < opt_count; i++) + clnt_optn_list[i] = opt_list[i]; + + *clnt_optn_count = opt_count; + + return AUTH_STATUS_NO_ERROR; +} + +static int +acl_chk_auth_method_list(unsigned int option_count, const int *option_list) +{ + unsigned int i; + + if (!option_list || option_count < 2) + return 1; + + if (option_list[option_count - 1] != AUTH_OPTION_NONE) + return 1; + + for (i = 0; i < (option_count - 1); i++) + if (option_list[i] != AUTH_OPTION_NONE) + return 0; + + return 0; +} + +static void +acl_set_auth_method_valid(struct iscsi_acl *client) +{ + unsigned int i, j = 0; + int option = 0; + + /* + * Following checks may need to be revised if + * authentication options other than CHAP and none + * are supported. + */ + if (client->node_type == TYPE_INITIATOR) { + if (client->auth_rmt) + /* + * If initiator doing authentication, + * don't offer authentication option none. + */ + option = 1; + else if (!client->passwd_present) + /* + * If initiator password not set, + * only offer authentication option none. + */ + option = 2; + } + + if (client->node_type == TYPE_TARGET) { + if (client->auth_rmt) + /* + * If target doing authentication, + * don't accept authentication option none. + */ + option = 1; + else + /* + * If target not doing authentication, + * only accept authentication option none. + */ + option = 2; + } + + for (i = 0; i < client->auth_method_count; i++) { + if (option == 1) { + if (client->auth_method_list[i] == AUTH_OPTION_NONE) + continue; + } else if (option == 2) + if (client->auth_method_list[i] != AUTH_OPTION_NONE) + continue; + client->auth_method_valid_list[j++] = client->auth_method_list[i]; + } + + client->auth_method_valid_count = j; + + acl_init_key_blk(&client->send_key_block); + + if (client->node_type == TYPE_INITIATOR) { + if (client->auth_rmt) { + /* + * Initiator wants to authenticate target, + * always send AuthMethod key. + */ + client->send_key_block.transit_bit = 0; + client->auth_method_valid_neg_role = + AUTH_NEG_ROLE_ORIGINATOR; + } else { + client->send_key_block.transit_bit = 1; + client->auth_method_valid_neg_role = + client->auth_method_neg_role; + } + } else { + client->send_key_block.transit_bit = 0; + client->auth_method_valid_neg_role = AUTH_NEG_ROLE_RESPONDER; + } + + if (client->auth_method_valid_neg_role == AUTH_NEG_ROLE_ORIGINATOR) + acl_set_auth_method_key(client, client->auth_method_valid_count, + client->auth_method_valid_list); + else { + int value = AUTH_OPTION_NOT_PRESENT; + acl_set_auth_method_key(client, 1, &value); + } +} + +static int +acl_set_auth_method_list(struct iscsi_acl *client, unsigned int option_count, + const int *option_list) +{ + int status; + + status = acl_set_option_list(client, option_count, option_list, + &client->auth_method_count, + client->auth_method_list, + AUTH_METHOD_MAX_COUNT, + acl_chk_auth_mthd_optn, + acl_chk_auth_method_list); + + if (status != AUTH_STATUS_NO_ERROR) + return status; + + /* Setting authMethod affects auth_method_valid. */ + acl_set_auth_method_valid(client); + + return AUTH_STATUS_NO_ERROR; +} + +static int +acl_chk_chap_alg_list(unsigned int option_count, const int *option_list) +{ + if (!option_list || option_count < 1) + return 1; + + return 0; +} + +static int +acl_set_chap_alg_list(struct iscsi_acl *client, unsigned int option_count, + const int *option_list) +{ + return acl_set_option_list(client, option_count, option_list, + &client->chap_alg_count, + client->chap_alg_list, + AUTH_CHAP_ALG_MAX_COUNT, + acl_chk_chap_alg_optn, + acl_chk_chap_alg_list); +} + +int +acl_init_chap_digests(int *value_list) { + EVP_MD_CTX *context = EVP_MD_CTX_new(); + int i = 0; + + if (EVP_DigestInit_ex(context, EVP_sha3_256(), NULL)) { + value_list[i++] = AUTH_CHAP_ALG_SHA3_256; + } + if (EVP_DigestInit_ex(context, EVP_sha256(), NULL)) { + value_list[i++] = AUTH_CHAP_ALG_SHA256; + } + if (EVP_DigestInit_ex(context, EVP_sha1(), NULL)) { + value_list[i++] = AUTH_CHAP_ALG_SHA1; + } + if (EVP_DigestInit_ex(context, EVP_md5(), NULL)) { + value_list[i++] = AUTH_CHAP_ALG_MD5; + } + return i; +} + +#define MAX(a,b) ((a) > (b) ? (a) : (b)) + +int +acl_init(int node_type, int buf_desc_count, struct auth_buffer_desc *buff_desc) +{ + struct iscsi_acl *client; + struct auth_str_block *recv_str_blk; + struct auth_str_block *send_str_blk; + struct auth_large_binary *recv_chap_challenge; + struct auth_large_binary *send_chap_challenge; + int value_list[MAX(AUTH_METHOD_MAX_COUNT, AUTH_CHAP_ALG_MAX_COUNT)]; + + if (buf_desc_count != 5 || !buff_desc) + return AUTH_STATUS_ERROR; + + if (!buff_desc[0].address || + buff_desc[0].length != sizeof(*client)) + return AUTH_STATUS_ERROR; + client = (struct iscsi_acl *)buff_desc[0].address; + + if (!buff_desc[1].address || + buff_desc[1].length != sizeof(*recv_str_blk)) + return AUTH_STATUS_ERROR; + recv_str_blk = (struct auth_str_block *)buff_desc[1].address; + + if (!buff_desc[2].address || + buff_desc[2].length != sizeof(*send_str_blk)) + return AUTH_STATUS_ERROR; + + send_str_blk = (struct auth_str_block *)buff_desc[2].address; + + if (!buff_desc[3].address || + buff_desc[3].length != sizeof(*recv_chap_challenge)) + return AUTH_STATUS_ERROR; + + recv_chap_challenge = (struct auth_large_binary *) + buff_desc[3].address; + + if (!buff_desc[4].address || + buff_desc[4].length != sizeof(*send_chap_challenge)) + return AUTH_STATUS_ERROR; + send_chap_challenge = (struct auth_large_binary *) + buff_desc[4].address; + memset(client, 0, sizeof(*client)); + memset(recv_str_blk, 0, sizeof(*recv_str_blk)); + memset(send_str_blk, 0, sizeof(*send_str_blk)); + memset(recv_chap_challenge, 0, sizeof(*recv_chap_challenge)); + memset(send_chap_challenge, 0, sizeof(*send_chap_challenge)); + + client->recv_key_block.str_block = recv_str_blk->str_block; + client->send_key_block.str_block = send_str_blk->str_block; + client->recv_chap_challenge.large_binary = recv_chap_challenge->large_binary; + client->send_chap_challenge.large_binary = send_chap_challenge->large_binary; + + if (node_type != TYPE_INITIATOR && node_type != TYPE_TARGET) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + client->signature = ACL_SIGNATURE; + client->node_type = (enum auth_node_type) node_type; + client->auth_rmt = 1; + client->passwd_present = 0; + client->ip_sec = 0; + + client->phase = AUTH_PHASE_CONFIGURE; + client->negotiated_auth_method = AUTH_OPTION_NOT_PRESENT; + client->negotiated_chap_alg = AUTH_OPTION_NOT_PRESENT; + + if (client->node_type == TYPE_INITIATOR) + client->auth_method_neg_role = AUTH_NEG_ROLE_ORIGINATOR; + else + /* Initial value ignored for Target. */ + client->auth_method_neg_role = AUTH_NEG_ROLE_RESPONDER; + + value_list[0] = AUTH_METHOD_CHAP; + value_list[1] = AUTH_OPTION_NONE; + + /* + * Must call after setting auth_rmt, password, + * and auth_method_neg_role + */ + if (acl_set_auth_method_list(client, 2, value_list) != + AUTH_STATUS_NO_ERROR) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + if (acl_set_chap_alg_list(client, acl_init_chap_digests(value_list), + value_list) != AUTH_STATUS_NO_ERROR) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_finish(struct iscsi_acl *client) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + memset(client, 0, sizeof(*client)); + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_set_user_name(struct iscsi_acl *client, const char *username) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_CONFIGURE || + acl_chk_string(username, AUTH_STR_MAX_LEN, NULL)) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + if (!username) + client->username[0] = '\0'; + else if (strlcpy(client->username, username, AUTH_STR_MAX_LEN) >= + AUTH_STR_MAX_LEN) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_set_passwd(struct iscsi_acl *client, const unsigned char *passwd_data, + unsigned int passwd_length) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_CONFIGURE || + passwd_length > AUTH_STR_MAX_LEN) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + memcpy(client->passwd_data, passwd_data, passwd_length); + client->passwd_length = passwd_length; + client->passwd_present = 1; + + /* Setting password may affect auth_method_valid. */ + acl_set_auth_method_valid(client); + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_set_auth_rmt(struct iscsi_acl *client, int auth_rmt) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_CONFIGURE) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + client->auth_rmt = auth_rmt; + + /* Setting auth_rmt may affect auth_method_valid. */ + acl_set_auth_method_valid(client); + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_set_ip_sec(struct iscsi_acl *client, int ip_sec) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_CONFIGURE) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + client->ip_sec = ip_sec; + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_get_dbg_status(struct iscsi_acl *client, int *value) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_DONE) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + *value = client->dbg_status; + + return AUTH_STATUS_NO_ERROR; +} + +const char * +acl_dbg_status_to_text(int dbg_status) +{ + /* + * Note: The ordering of this table must match the order + * defined by enum auth_dbg_status in iscsi-auth-client.h. + */ + static char *const dbg_text[AUTH_DBG_STATUS_MAX_COUNT] = { + "Debug status not set", + "Authentication request passed", + "Authentication not enabled", + "Authentication request failed", + "AuthMethod bad", + "CHAP algorithm bad", + "Decrypt password failed", + "Local password too short with no IPSec", + "Unexpected error from authentication server", + "Authentication request status bad", + "Authentication pass status not valid", + "Same key set more than once on send", + "Key value too long on send", + "Too much data on send", + "AuthMethod key expected", + "CHAP algorithm key expected", + "CHAP identifier expected", + "CHAP challenge expected", + "CHAP response expected", + "CHAP username expected", + "AuthMethod key not present", + "AuthMethod negotiation failed", + "AuthMethod negotiated to none", + "CHAP algorithm negotiation failed", + "CHAP challenge reflected", + "Local password same as remote", + "Local password not set", + "CHAP identifier bad", + "CHAP challenge bad", + "CHAP response bad", + "Unexpected key present", + "T bit set on response, but not on previous message", + "T bit set on response, but authenticaton not complete", + "Message count limit reached on receive", + "Same key set more than once on receive", + "Key value too long on receive", + "Too much data on receive" + }; + + if (dbg_status < 0 || dbg_status >= AUTH_DBG_STATUS_MAX_COUNT) + return "Unknown error"; + + return dbg_text[dbg_status]; +} + +int +acl_data(unsigned char *out_data, unsigned int *out_length, + unsigned char *in_data, unsigned int in_length) +{ + if (*out_length < in_length) + return 1; /* error */ + + memcpy(out_data, in_data, in_length); + *out_length = in_length; + + return 0; /* no error */ +} + diff --git a/usr/auth.h b/usr/auth.h new file mode 100644 index 0000000..f6dbbe4 --- /dev/null +++ b/usr/auth.h @@ -0,0 +1,292 @@ +/* + * iSCSI Authorization Library + * + * maintained by open-iscsi@@googlegroups.com + * + * Originally based on: + * Copyright (C) 2001 Cisco Systems, Inc. + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#ifndef AUTH_CLIENT_H +#define AUTH_CLIENT_H + +struct iscsi_session; + +enum { + AUTH_STR_MAX_LEN = 256, + AUTH_STR_BLOCK_MAX_LEN = 1024, + AUTH_LARGE_BINARY_MAX_LEN = 1024, + AUTH_RECV_END_MAX_COUNT = 10, + ACL_SIGNATURE = 0x5984B2E3, + AUTH_CHAP_MD5_RSP_LEN = 16, + AUTH_CHAP_SHA1_RSP_LEN = 20, + AUTH_CHAP_SHA256_RSP_LEN = 32, + AUTH_CHAP_SHA3_256_RSP_LEN = 32, + AUTH_CHAP_RSP_MAX = 32, +}; + +/* + * Note: The ordering of these values are chosen to match + * the ordering of the keys as shown in the iSCSI spec. + * The order of table key_names in acl_get_key_name() + * must match the order defined by enum auth_key_type. + */ +enum auth_key_type { + AUTH_KEY_TYPE_NONE = -1, + AUTH_KEY_TYPE_FIRST = 0, + AUTH_KEY_TYPE_AUTH_METHOD = AUTH_KEY_TYPE_FIRST, + AUTH_KEY_TYPE_CHAP_ALG, + AUTH_KEY_TYPE_CHAP_USERNAME, + AUTH_KEY_TYPE_CHAP_RSP, + AUTH_KEY_TYPE_CHAP_IDENTIFIER, + AUTH_KEY_TYPE_CHAP_CHALLENGE, + AUTH_KEY_TYPE_MAX_COUNT, + AUTH_KEY_TYPE_LAST = AUTH_KEY_TYPE_MAX_COUNT - 1 +}; + +enum { + /* Common options for all keys. */ + AUTH_OPTION_REJECT = -2, + AUTH_OPTION_NOT_PRESENT = -1, + AUTH_OPTION_NONE = 1, + + AUTH_METHOD_CHAP = 2, + AUTH_METHOD_MAX_COUNT = 2, + + AUTH_CHAP_ALG_MD5 = 5, + AUTH_CHAP_ALG_SHA1 = 6, + AUTH_CHAP_ALG_SHA256 = 7, + AUTH_CHAP_ALG_SHA3_256 = 8, + AUTH_CHAP_ALG_MAX_COUNT = 5 +}; + +enum auth_neg_role { + AUTH_NEG_ROLE_ORIGINATOR = 1, + AUTH_NEG_ROLE_RESPONDER = 2 +}; + +enum auth_status { + AUTH_STATUS_NO_ERROR = 0, + AUTH_STATUS_ERROR, + AUTH_STATUS_PASS, + AUTH_STATUS_FAIL, + AUTH_STATUS_CONTINUE, +}; + +/* + * Note: The order of table dbg_text in acl_dbg_status_to_text() + * must match the ordered defined by enum auth_dbg_status. + */ +enum auth_dbg_status { + AUTH_DBG_STATUS_NOT_SET = 0, + + AUTH_DBG_STATUS_AUTH_PASS, + AUTH_DBG_STATUS_AUTH_RMT_FALSE, + + AUTH_DBG_STATUS_AUTH_FAIL, + + AUTH_DBG_STATUS_AUTH_METHOD_BAD, + AUTH_DBG_STATUS_CHAP_ALG_BAD, + AUTH_DBG_STATUS_PASSWD_DECRYPT_FAILED, + AUTH_DBG_STATUS_PASSWD_TOO_SHORT_WITH_NO_IPSEC, + AUTH_DBG_STATUS_AUTH_SERVER_ERROR, + AUTH_DBG_STATUS_AUTH_STATUS_BAD, + AUTH_DBG_STATUS_AUTHPASS_NOT_VALID, + AUTH_DBG_STATUS_SEND_DUP_SET_KEY_VALUE, + AUTH_DBG_STATUS_SEND_STR_TOO_LONG, + AUTH_DBG_STATUS_SEND_TOO_MUCH_DATA, + + AUTH_DBG_STATUS_AUTH_METHOD_EXPECTED, + AUTH_DBG_STATUS_CHAP_ALG_EXPECTED, + AUTH_DBG_STATUS_CHAP_IDENTIFIER_EXPECTED, + AUTH_DBG_STATUS_CHAP_CHALLENGE_EXPECTED, + AUTH_DBG_STATUS_CHAP_RSP_EXPECTED, + AUTH_DBG_STATUS_CHAP_USERNAME_EXPECTED, + + AUTH_DBG_STATUS_AUTH_METHOD_NOT_PRESENT, + AUTH_DBG_STATUS_AUTH_METHOD_REJECT, + AUTH_DBG_STATUS_AUTH_METHOD_NONE, + AUTH_DBG_STATUS_CHAP_ALG_REJECT, + AUTH_DBG_STATUS_CHAP_CHALLENGE_REFLECTED, + AUTH_DBG_STATUS_PASSWD_IDENTICAL, + + AUTH_DBG_STATUS_LOCAL_PASSWD_NOT_SET, + + AUTH_DBG_STATUS_CHAP_IDENTIFIER_BAD, + AUTH_DBG_STATUS_CHALLENGE_BAD, + AUTH_DBG_STATUS_CHAP_RSP_BAD, + AUTH_DBG_STATUS_UNEXPECTED_KEY_PRESENT, + AUTH_DBG_STATUS_T_BIT_SET_ILLEGAL, + AUTH_DBG_STATUS_T_BIT_SET_PREMATURE, + + AUTH_DBG_STATUS_RECV_MSG_COUNT_LIMIT, + AUTH_DBG_STATUS_RECV_DUP_SET_KEY_VALUE, + AUTH_DBG_STATUS_RECV_STR_TOO_LONG, + AUTH_DBG_STATUS_RECV_TOO_MUCH_DATA, + AUTH_DBG_STATUS_MAX_COUNT +}; + +enum auth_node_type { + TYPE_INITIATOR = 1, + TYPE_TARGET = 2 +}; + +enum auth_phase { + AUTH_PHASE_CONFIGURE = 1, + AUTH_PHASE_NEGOTIATE, + AUTH_PHASE_AUTHENTICATE, + AUTH_PHASE_DONE, + AUTH_PHASE_ERROR +}; + +enum auth_local_state { + AUTH_LOCAL_STATE_SEND_ALG = 1, + AUTH_LOCAL_STATE_RECV_ALG, + AUTH_LOCAL_STATE_RECV_CHALLENGE, + AUTH_LOCAL_STATE_DONE, + AUTH_LOCAL_STATE_ERROR +}; + +enum auth_rmt_state { + AUTH_RMT_STATE_SEND_ALG = 1, + AUTH_RMT_STATE_SEND_CHALLENGE, + AUTH_RMT_STATE_RECV_RSP, + AUTH_RMT_STATE_DONE, + AUTH_RMT_STATE_ERROR +}; + +struct auth_buffer_desc { + unsigned int length; + void *address; +}; + +struct auth_key { + unsigned int present:1; + unsigned int processed:1; + unsigned int value_set:1; + char *string; +}; + +struct auth_large_binary_key { + unsigned int length; + unsigned char *large_binary; +}; + +struct auth_key_block { + unsigned int transit_bit:1; + unsigned int dup_set:1; + unsigned int str_too_long:1; + unsigned int too_much_data:1; + unsigned int blk_length:16; + char *str_block; + struct auth_key key[AUTH_KEY_TYPE_MAX_COUNT]; +}; + +struct auth_str_block { + char str_block[AUTH_STR_BLOCK_MAX_LEN]; +}; + +struct auth_large_binary { + unsigned char large_binary[AUTH_LARGE_BINARY_MAX_LEN]; +}; + +struct iscsi_acl { + unsigned long signature; + + enum auth_node_type node_type; + unsigned int auth_method_count; + int auth_method_list[AUTH_METHOD_MAX_COUNT]; + enum auth_neg_role auth_method_neg_role; + unsigned int chap_alg_count; + int chap_alg_list[AUTH_CHAP_ALG_MAX_COUNT]; + int auth_rmt; + char username[AUTH_STR_MAX_LEN]; + int passwd_present; + unsigned int passwd_length; + unsigned char passwd_data[AUTH_STR_MAX_LEN]; + unsigned int chap_challenge_len; + int ip_sec; + + unsigned int auth_method_valid_count; + int auth_method_valid_list[AUTH_METHOD_MAX_COUNT]; + int auth_method_valid_neg_role; + + int recv_in_progress_flag; + int recv_end_count; + struct iscsi_session *session_handle; /* + * session_handle can only be + * used by acl_chap_auth_request + */ + enum auth_phase phase; + enum auth_local_state local_state; + enum auth_rmt_state rmt_state; + enum auth_status rmt_auth_status; + enum auth_dbg_status dbg_status; + int negotiated_auth_method; + int negotiated_chap_alg; + int auth_rsp_flag; + int auth_server_error_flag; + int transit_bit_sent_flag; + + unsigned int send_chap_identifier; + struct auth_large_binary_key send_chap_challenge; + char chap_username[AUTH_STR_MAX_LEN]; + + int recv_chap_challenge_status; + struct auth_large_binary_key recv_chap_challenge; + + char scratch_key_value[AUTH_STR_MAX_LEN]; + + struct auth_key_block recv_key_block; + struct auth_key_block send_key_block; +}; + +extern int acl_init(int node_type, int buf_desc_count, + struct auth_buffer_desc *buff_desc); +extern int acl_finish(struct iscsi_acl *client); + +extern int acl_recv_begin(struct iscsi_acl *client); +extern int acl_recv_end(struct iscsi_acl *client, + struct iscsi_session *session_handle); +extern const char *acl_get_key_name(int key_type); +extern int acl_get_next_key_type(int *key_type); +extern int acl_recv_key_value(struct iscsi_acl *client, int key_type, + const char *user_key_val); +extern int acl_send_key_val(struct iscsi_acl *client, int key_type, + int *key_present, char *user_key_val, + unsigned int max_length); +extern int acl_recv_transit_bit(struct iscsi_acl *client, int value); +extern int acl_send_transit_bit(struct iscsi_acl *client, int *value); +extern int acl_set_user_name(struct iscsi_acl *client, const char *username); +extern int acl_set_passwd(struct iscsi_acl *client, + const unsigned char *pw_data, unsigned int pw_len); +extern int acl_set_auth_rmt(struct iscsi_acl *client, int auth_rmt); +extern int acl_set_ip_sec(struct iscsi_acl *client, int ip_sec); +extern int acl_get_dbg_status(struct iscsi_acl *client, int *value); +extern const char *acl_dbg_status_to_text(int dbg_status); +extern enum auth_dbg_status acl_chap_compute_rsp(struct iscsi_acl *client, + int rmt_auth, + unsigned int id, + unsigned char *challenge_data, + unsigned int challenge_len, + unsigned char *response_data); +extern int acl_chap_auth_request(struct iscsi_acl *client, char *username, + unsigned int id, + unsigned char *challenge_data, + unsigned int challenge_length, + unsigned char *response_data, + unsigned int rsp_length); +extern int acl_data(unsigned char *out_data, unsigned int *out_length, + unsigned char *in_data, unsigned int in_length); +#endif /* #ifndef ISCSIAUTHCLIENT_H */ diff --git a/usr/be2iscsi.c b/usr/be2iscsi.c new file mode 100644 index 0000000..8a346a5 --- /dev/null +++ b/usr/be2iscsi.c @@ -0,0 +1,37 @@ +/* + * be2iscsi helpers + * + * Copyright (C) 2006 Mike Christie + * Copyright (C) 2006 Red Hat, Inc. 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 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. + */ +#include "initiator.h" + +void be2iscsi_create_conn(struct iscsi_conn *conn) +{ + struct iscsi_session *session = conn->session; + + if (conn->max_recv_dlength > 65536) + conn->max_recv_dlength = 65536; + + if (session->first_burst > 8192) + session->first_burst = 8192; + + if (session->max_burst > 262144) + session->max_burst = 262144; + + if (conn->max_xmit_dlength > 65536) + conn->max_xmit_dlength = 65536; + + session->erl = 0; + session->initial_r2t_en = 1; +} diff --git a/usr/be2iscsi.h b/usr/be2iscsi.h new file mode 100644 index 0000000..9e5c727 --- /dev/null +++ b/usr/be2iscsi.h @@ -0,0 +1,8 @@ +#ifndef BE2ISCSI_TRANSPORT +#define BE2ISCSI_TRANSPORT + +struct iscsi_conn; + +extern void be2iscsi_create_conn(struct iscsi_conn *conn); + +#endif diff --git a/usr/config.h b/usr/config.h new file mode 100644 index 0000000..250879d --- /dev/null +++ b/usr/config.h @@ -0,0 +1,328 @@ +/* + * iSCSI Configuration + * + * Copyright (C) 2002 Cisco Systems, Inc. + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#ifndef CONFIG_H +#define CONFIG_H + +#include +#include + +#include "types.h" +#include "auth.h" /* for the username and password sizes */ +#include "list.h" +#include "iscsi_proto.h" +#include "iscsi_net_util.h" + +/* ISIDs now have a typed naming authority in them. We use an OUI */ +#define DRIVER_ISID_0 0x00 +#define DRIVER_ISID_1 0x02 +#define DRIVER_ISID_2 0x3D + +/* number of possible connections per session */ +#define ISCSI_CONN_MAX 1 +/* max len of interface */ +#define ISCSI_MAX_IFACE_LEN 65 + +/* the following structures store the options set in the config file. + * a structure is defined for each logically-related group of options. + * if you are adding a new option, first check if it should belong + * to one of the existing groups. If it does, add it. If not, define + * a new structure. + */ + +/* all authentication-related options should be added to this structure. + * this structure is per-session, and can be configured + * by TargetName but not Subnet. + */ +struct iscsi_auth_config { + unsigned int authmethod; + char username[AUTH_STR_MAX_LEN]; + unsigned char password[AUTH_STR_MAX_LEN]; + unsigned int password_length; + char username_in[AUTH_STR_MAX_LEN]; + unsigned char password_in[AUTH_STR_MAX_LEN]; + unsigned int password_in_length; +}; + +/* all per-connection timeouts go in this structure. + * this structure is per-portal, and can be configured + * both by TargetName and Subnet. + */ +struct iscsi_connection_timeout_config { + int login_timeout; + int logout_timeout; + int auth_timeout; + int active_timeout; + int noop_out_interval; + int noop_out_timeout; +}; + +/* all per-session timeouts go in this structure. + * this structure is per-session, and can be configured + * by TargetName but not by Subnet. + */ +struct iscsi_session_timeout_config { + int replacement_timeout; +}; + +/* all error handling timeouts go in this structure. + * this structure is per-portal, and can be configured + * both by TargetName and Subnet. + */ +struct iscsi_error_timeout_config { + int abort_timeout; + int host_reset_timeout; + int lu_reset_timeout; + int tgt_reset_timeout; +}; + +/* all TCP options go in this structure. + * this structure is per-portal, and can be configured + * both by TargetName and Subnet. + */ +struct iscsi_tcp_config { + int window_size; + int type_of_service; /* try to set IP TOS bits */ +}; + +struct iscsi_conn_operational_config { + int MaxRecvDataSegmentLength; + int MaxXmitDataSegmentLength; + int HeaderDigest; + int DataDigest; + int IFMarker; + int OFMarker; +}; + +/* all iSCSI operational params go in this structure. + * this structure is per-portal, and can be configured + * both by TargetName and Subnet. + */ +struct iscsi_session_operational_config { + int DataPDUInOrder; + int DataSequenceInOrder; + int protocol; + int InitialR2T; + int ImmediateData; + int FirstBurstLength; + int MaxBurstLength; + int DefaultTime2Wait; + int DefaultTime2Retain; + int MaxConnections; + int MaxOutstandingR2T; + int ERL; + int FastAbort; +}; + +#define CONFIG_DIGEST_NEVER 0 +#define CONFIG_DIGEST_ALWAYS 1 +#define CONFIG_DIGEST_PREFER_ON 2 +#define CONFIG_DIGEST_PREFER_OFF 3 + +struct iscsi_sendtargets_config { + int reopen_max; + int use_discoveryd; + int discoveryd_poll_inval; + struct iscsi_auth_config auth; + struct iscsi_connection_timeout_config conn_timeo; + struct iscsi_conn_operational_config conn_conf; + struct iscsi_session_operational_config session_conf; +}; + +struct iscsi_isns_config { + int use_discoveryd; + int discoveryd_poll_inval; +}; + +struct iscsi_slp_config { + char *scopes; + char *interfaces; /* for multicast, list of interfaces names, + * "all", or "none" */ + int poll_interval; + struct iscsi_auth_config auth; +}; + +typedef enum iscsi_startup { + ISCSI_STARTUP_MANUAL, + ISCSI_STARTUP_AUTOMATIC, + ISCSI_STARTUP_ONBOOT, +} iscsi_startup_e; + +typedef enum discovery_type { + DISCOVERY_TYPE_SENDTARGETS, + DISCOVERY_TYPE_ISNS, + DISCOVERY_TYPE_OFFLOAD_SENDTARGETS, + DISCOVERY_TYPE_SLP, + DISCOVERY_TYPE_STATIC, + DISCOVERY_TYPE_FW, +} discovery_type_e; + +typedef struct conn_rec { + iscsi_startup_e startup; + char address[NI_MAXHOST]; + int port; + struct iscsi_tcp_config tcp; + struct iscsi_connection_timeout_config timeo; + struct iscsi_conn_operational_config iscsi; +} conn_rec_t; + +typedef struct session_rec { + int initial_cmdsn; + int reopen_max; + int xmit_thread_priority; + int cmds_max; + int queue_depth; + int initial_login_retry_max; + int nr_sessions; + int scan; + struct iscsi_auth_config auth; + struct iscsi_session_timeout_config timeo; + struct iscsi_error_timeout_config err_timeo; + struct iscsi_session_operational_config iscsi; + struct session_info *info; + unsigned sid; + /* + * This is a flag passed to iscsid. If set, multiple sessions are + * allowed to be initiated on this record + */ + unsigned char multiple; + char boot_root[BOOT_NAME_MAXLEN]; + char boot_nic[BOOT_NAME_MAXLEN]; + char boot_target[BOOT_NAME_MAXLEN]; +} session_rec_t; + +#define ISCSI_TRANSPORT_NAME_MAXLEN 16 +#define ISCSI_MAX_STR_LEN 80 + +typedef struct iface_rec { + struct list_head list; + /* iscsi iface record name */ + char name[ISCSI_MAX_IFACE_LEN]; + uint32_t iface_num; + /* network layer iface name (eth0) */ + char netdev[IFNAMSIZ]; + char ipaddress[NI_MAXHOST]; + char subnet_mask[NI_MAXHOST]; + char gateway[NI_MAXHOST]; + char bootproto[ISCSI_MAX_STR_LEN]; + char ipv6_linklocal[NI_MAXHOST]; + char ipv6_router[NI_MAXHOST]; + char ipv6_autocfg[NI_MAXHOST]; + char linklocal_autocfg[NI_MAXHOST]; + char router_autocfg[NI_MAXHOST]; + uint8_t prefix_len; + uint16_t vlan_id; + uint8_t vlan_priority; + char vlan_state[ISCSI_MAX_STR_LEN]; + char state[ISCSI_MAX_STR_LEN]; /* 0 = disable, + * 1 = enable */ + uint16_t mtu; + uint16_t port; + char delayed_ack[ISCSI_MAX_STR_LEN]; + char nagle[ISCSI_MAX_STR_LEN]; + char tcp_wsf_state[ISCSI_MAX_STR_LEN]; + uint8_t tcp_wsf; + uint8_t tcp_timer_scale; + char tcp_timestamp[ISCSI_MAX_STR_LEN]; + char dhcp_dns[ISCSI_MAX_STR_LEN]; + char dhcp_slp_da[ISCSI_MAX_STR_LEN]; + char tos_state[ISCSI_MAX_STR_LEN]; + uint8_t tos; + char gratuitous_arp[ISCSI_MAX_STR_LEN]; + char dhcp_alt_client_id_state[ISCSI_MAX_STR_LEN]; + char dhcp_alt_client_id[ISCSI_MAX_STR_LEN]; + char dhcp_req_vendor_id_state[ISCSI_MAX_STR_LEN]; + char dhcp_vendor_id_state[ISCSI_MAX_STR_LEN]; + char dhcp_vendor_id[ISCSI_MAX_STR_LEN]; + char dhcp_learn_iqn[ISCSI_MAX_STR_LEN]; + char fragmentation[ISCSI_MAX_STR_LEN]; + char incoming_forwarding[ISCSI_MAX_STR_LEN]; + uint8_t ttl; + char gratuitous_neighbor_adv[ISCSI_MAX_STR_LEN]; + char redirect[ISCSI_MAX_STR_LEN]; + char mld[ISCSI_MAX_STR_LEN]; + uint32_t flow_label; + uint32_t traffic_class; + uint8_t hop_limit; + uint32_t nd_reachable_tmo; + uint32_t nd_rexmit_time; + uint32_t nd_stale_tmo; + uint8_t dup_addr_detect_cnt; + uint32_t router_adv_link_mtu; + uint16_t def_task_mgmt_tmo; + char header_digest[ISCSI_MAX_STR_LEN]; + char data_digest[ISCSI_MAX_STR_LEN]; + char immediate_data[ISCSI_MAX_STR_LEN]; + char initial_r2t[ISCSI_MAX_STR_LEN]; + char data_seq_inorder[ISCSI_MAX_STR_LEN]; + char data_pdu_inorder[ISCSI_MAX_STR_LEN]; + uint8_t erl; + uint32_t max_recv_dlength; + uint32_t first_burst_len; + uint16_t max_out_r2t; + uint32_t max_burst_len; + char chap_auth[ISCSI_MAX_STR_LEN]; + char bidi_chap[ISCSI_MAX_STR_LEN]; + char strict_login_comp[ISCSI_MAX_STR_LEN]; + char discovery_auth[ISCSI_MAX_STR_LEN]; + char discovery_logout[ISCSI_MAX_STR_LEN]; + char port_state[ISCSI_MAX_STR_LEN]; + char port_speed[ISCSI_MAX_STR_LEN]; + /* + * TODO: we may have to make this bigger and interconnect + * specific for infiniband + */ + char hwaddress[ISCSI_HWADDRESS_BUF_SIZE]; + char transport_name[ISCSI_TRANSPORT_NAME_MAXLEN]; + /* + * This is only used for boot now, but the iser guys + * can use this for their virtualization idea. + */ + char alias[TARGET_NAME_MAXLEN + 1]; + char iname[TARGET_NAME_MAXLEN + 1]; +} iface_rec_t; + +typedef struct node_rec { + struct list_head list; + char name[TARGET_NAME_MAXLEN]; + int tpgt; + iscsi_startup_e startup; + int leading_login; + session_rec_t session; + conn_rec_t conn[ISCSI_CONN_MAX]; + iface_rec_t iface; + discovery_type_e disc_type; + char disc_address[NI_MAXHOST]; + int disc_port; +} node_rec_t; + +typedef struct discovery_rec { + iscsi_startup_e startup; + discovery_type_e type; + char address[NI_MAXHOST]; + int port; + int iscsid_req_tmo; + union { + struct iscsi_sendtargets_config sendtargets; + struct iscsi_slp_config slp; + struct iscsi_isns_config isns; + } u; +} discovery_rec_t; + +#endif /* CONFIG_H */ diff --git a/usr/cxgbi.c b/usr/cxgbi.c new file mode 100644 index 0000000..4f3d1db --- /dev/null +++ b/usr/cxgbi.c @@ -0,0 +1,24 @@ +/* + * cxgb3i/cxgb4i helpers + * + * Copyright (C) 2006 Mike Christie + * Copyright (C) 2006 Red Hat, Inc. 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 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. + */ +#include "initiator.h" + +void cxgbi_create_conn(struct iscsi_conn *conn) +{ + /* card can handle up to 15360 bytes */ + if (conn->max_recv_dlength > 8192) + conn->max_recv_dlength = 8192; +} diff --git a/usr/cxgbi.h b/usr/cxgbi.h new file mode 100644 index 0000000..bd46603 --- /dev/null +++ b/usr/cxgbi.h @@ -0,0 +1,8 @@ +#ifndef CXGBI_TRANSPORT +#define CXGBI_TRANSPORT + +struct iscsi_conn; + +extern void cxgbi_create_conn(struct iscsi_conn *conn); + +#endif diff --git a/usr/discovery.c b/usr/discovery.c new file mode 100644 index 0000000..199c160 --- /dev/null +++ b/usr/discovery.c @@ -0,0 +1,1785 @@ +/* + * iSCSI Discovery + * + * Copyright (C) 2002 Cisco Systems, Inc. + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "local_strings.h" +#include "types.h" +#include "iscsi_proto.h" +#include "initiator.h" +#include "log.h" +#include "idbm.h" +#include "iscsi_settings.h" +#include "sysdeps.h" +#include "fw_context.h" +#include "iscsid_req.h" +#include "iscsi_util.h" +#include "transport.h" +#include "iscsi_sysfs.h" +#include "iscsi_ipc.h" +#include "iface.h" +#include "iscsi_timer.h" +#include "iscsi_err.h" +/* libisns includes */ +#include +#include +#include + +#ifdef SLP_ENABLE +#include "iscsi-slp-discovery.h" +#endif + +#define DISCOVERY_NEED_RECONNECT 0xdead0001 + +static char initiator_name[TARGET_NAME_MAXLEN + 1]; +static char initiator_alias[TARGET_NAME_MAXLEN + 1]; +static struct iscsi_ev_context ipc_ev_context; + +static int request_initiator_name(int tmo) +{ + int rc; + iscsiadm_req_t req; + iscsiadm_rsp_t rsp; + + memset(initiator_name, 0, sizeof(initiator_name)); + initiator_name[0] = '\0'; + memset(initiator_alias, 0, sizeof(initiator_alias)); + initiator_alias[0] = '\0'; + + memset(&req, 0, sizeof(req)); + req.command = MGMT_IPC_CONFIG_INAME; + + rc = iscsid_exec_req(&req, &rsp, 1, tmo); + if (rc) + return rc; + + if (rsp.u.config.var[0] != '\0') + strcpy(initiator_name, rsp.u.config.var); + + memset(&req, 0, sizeof(req)); + req.command = MGMT_IPC_CONFIG_IALIAS; + + rc = iscsid_exec_req(&req, &rsp, 0, tmo); + if (rc) + /* alias is optional so return ok */ + return 0; + + if (rsp.u.config.var[0] != '\0') + strcpy(initiator_alias, rsp.u.config.var); + return 0; +} + +void discovery_isns_free_servername(void) +{ + if (isns_config.ic_server_name) + free(isns_config.ic_server_name); + isns_config.ic_server_name = NULL; +} + +int discovery_isns_set_servername(char *address, int port) +{ + char *server; + int len; + + if (port > USHRT_MAX) { + log_error("Invalid port %d", port); + return ISCSI_ERR_INVAL; + } + + /* 5 for port and 1 for colon and 1 for null */ + len = strlen(address) + 7; + server = calloc(1, len); + if (!server) + return ISCSI_ERR_NOMEM; + + snprintf(server, len, "%s:%d", address, port); + isns_assign_string(&isns_config.ic_server_name, server); + free(server); + return 0; +} + +int discovery_isns_query(struct discovery_rec *drec, const char *iname, + const char *targetname, struct list_head *rec_list) +{ + isns_attr_list_t key_attrs = ISNS_ATTR_LIST_INIT; + isns_object_list_t objects = ISNS_OBJECT_LIST_INIT; + isns_source_t *source; + isns_simple_t *qry; + isns_client_t *clnt; + uint32_t status; + int rc, i; + + isns_config.ic_security = 0; + source = isns_source_create_iscsi(iname); + if (!source) + return ISCSI_ERR_NOMEM; + + clnt = isns_create_client(NULL, iname); + if (!clnt) { + rc = ISCSI_ERR_NOMEM; + goto free_src; + } + + /* do not retry forever */ + isns_socket_set_disconnect_fatal(clnt->ic_socket); + + if (targetname) + isns_attr_list_append_string(&key_attrs, ISNS_TAG_ISCSI_NAME, + targetname); + else + /* Query for all visible targets */ + isns_attr_list_append_uint32(&key_attrs, + ISNS_TAG_ISCSI_NODE_TYPE, + ISNS_ISCSI_TARGET_MASK); + + qry = isns_create_query2(clnt, &key_attrs, source); + if (!qry) { + rc = ISCSI_ERR_NOMEM; + goto free_clnt; + } + + isns_query_request_attr_tag(qry, ISNS_TAG_ISCSI_NAME); + isns_query_request_attr_tag(qry, ISNS_TAG_ISCSI_NODE_TYPE); + isns_query_request_attr_tag(qry, ISNS_TAG_PORTAL_IP_ADDRESS); + isns_query_request_attr_tag(qry, ISNS_TAG_PORTAL_TCP_UDP_PORT); + isns_query_request_attr_tag(qry, ISNS_TAG_PG_ISCSI_NAME); + isns_query_request_attr_tag(qry, ISNS_TAG_PG_PORTAL_IP_ADDR); + isns_query_request_attr_tag(qry, ISNS_TAG_PG_PORTAL_TCP_UDP_PORT); + isns_query_request_attr_tag(qry, ISNS_TAG_PG_TAG); + + status = isns_client_call(clnt, &qry); + switch (status) { + case ISNS_SUCCESS: + break; + case ISNS_SOURCE_UNKNOWN: + /* server requires that we are registered but we are not */ + rc = ISCSI_ERR_ISNS_REG_FAILED; + goto free_query; + default: + log_error("iSNS discovery failed: %s", isns_strerror(status)); + rc = ISCSI_ERR_ISNS_QUERY; + goto free_query; + } + + status = isns_query_response_get_objects(qry, &objects); + if (status) { + log_error("Unable to extract object list from query " + "response: %s", isns_strerror(status)); + rc = ISCSI_ERR; + goto free_query; + } + + for (i = 0; i < objects.iol_count; ++i) { + isns_object_t *obj = objects.iol_data[i]; + const char *pg_tgt = NULL; + struct in6_addr in_addr; + uint32_t pg_port = ISCSI_LISTEN_PORT; + uint32_t pg_tag = PORTAL_GROUP_TAG_UNKNOWN; + char pg_addr[INET6_ADDRSTRLEN + 1]; + struct node_rec *rec; + + if (!isns_object_is_pg(obj)) + continue; + + if (!isns_object_get_string(obj, ISNS_TAG_PG_ISCSI_NAME, + &pg_tgt)) { + log_debug(1, "Missing target name"); + continue; + } + + if (!isns_object_get_ipaddr(obj, ISNS_TAG_PG_PORTAL_IP_ADDR, + &in_addr)) { + log_debug(1, "Missing addr"); + continue; + } + if (IN6_IS_ADDR_V4MAPPED(&in_addr) || + IN6_IS_ADDR_V4COMPAT(&in_addr)) { + struct in_addr ipv4; + + ipv4.s_addr = in_addr.s6_addr32[3]; + inet_ntop(AF_INET, &ipv4, pg_addr, sizeof(pg_addr)); + } else + inet_ntop(AF_INET6, &in_addr, pg_addr, sizeof(pg_addr)); + + if (!isns_object_get_uint32(obj, + ISNS_TAG_PG_PORTAL_TCP_UDP_PORT, + &pg_port)) { + log_debug(1, "Missing port"); + continue; + } + + if (!isns_object_get_uint32(obj, ISNS_TAG_PG_TAG, &pg_tag)) { + log_debug(1, "Missing tag"); + continue; + } + + rec = calloc(1, sizeof(*rec)); + if (!rec) { + rc = ISCSI_ERR_NOMEM; + goto destroy_list; + } + + idbm_node_setup_from_conf(rec); + if (drec) { + rec->disc_type = drec->type; + rec->disc_port = drec->port; + strcpy(rec->disc_address, drec->address); + } + + strlcpy(rec->name, pg_tgt, TARGET_NAME_MAXLEN); + rec->tpgt = pg_tag; + rec->conn[0].port = pg_port; + strlcpy(rec->conn[0].address, pg_addr, NI_MAXHOST); + list_add_tail(&rec->list, rec_list); + } + rc = 0; + + isns_flush_events(); +destroy_list: + isns_object_list_destroy(&objects); +free_query: + isns_simple_free(qry); +free_clnt: + isns_client_destroy(clnt); +free_src: + isns_source_release(source); + return rc; +} + +/* + * discovery_isns_reg_node - register/deregister node + * @iname: initiator name + * @reg: bool indicating if we are supposed to register or deregister node. + * + * We do a very simple registration just so we can query. + */ +static int discovery_isns_reg_node(const char *iname, int op_reg) +{ + isns_simple_t *reg; + isns_client_t *clnt; + isns_source_t *source; + int rc = 0, status; + + isns_config.ic_security = 0; + + log_debug(1, "trying to %s %s with iSNS server.", + op_reg ? "register" : "deregister", iname); + + source = isns_source_create_iscsi(iname); + if (!source) + return ISCSI_ERR_NOMEM; + + clnt = isns_create_client(NULL, iname); + if (!clnt) { + rc = ISCSI_ERR_NOMEM; + goto free_src; + } + + reg = isns_simple_create(op_reg ? ISNS_DEVICE_ATTRIBUTE_REGISTER : + ISNS_DEVICE_DEREGISTER, + source, NULL); + if (!reg) { + rc = ISCSI_ERR_NOMEM; + goto free_clnt; + } + + isns_attr_list_append_string(®->is_operating_attrs, + ISNS_TAG_ISCSI_NAME, iname); + if (op_reg) + isns_attr_list_append_uint32(®->is_operating_attrs, + ISNS_TAG_ISCSI_NODE_TYPE, + ISNS_ISCSI_INITIATOR_MASK); + status = isns_client_call(clnt, ®); + if (status != ISNS_SUCCESS) { + log_error("Could not %s %s with iSNS server: %s.", + reg ? "register" : "deregister", iname, + isns_strerror(status)); + rc = ISCSI_ERR_ISNS_REG_FAILED; + } else + log_debug(1, "%s %s with iSNS server successful.", + op_reg ? "register" : "deregister", iname); +free_clnt: + isns_client_destroy(clnt); +free_src: + isns_source_release(source); + return rc; +} + +int discovery_isns(void *data, struct iface_rec *iface, + struct list_head *rec_list) +{ + struct discovery_rec *drec = data; + char *iname; + int rc, registered = 0; + + if (iface && strlen(iface->iname)) + iname = iface->iname; + else { + rc = request_initiator_name(drec->iscsid_req_tmo); + if (rc) { + log_error("Cannot perform discovery. Initiatorname " + "required."); + return rc; + } else if (initiator_name[0] == '\0') { + log_error("Cannot perform discovery. Invalid " + "Initiatorname."); + return ISCSI_ERR_INVAL; + } + + iname = initiator_name; + } + + rc = discovery_isns_set_servername(drec->address, drec->port); + if (rc) + return rc; +retry: + rc = discovery_isns_query(drec, iname, NULL, rec_list); + if (!registered && rc == ISCSI_ERR_ISNS_REG_FAILED) { + rc = discovery_isns_reg_node(iname, 1); + if (!rc) { + registered = 1; + goto retry; + } + } + + if (registered) + discovery_isns_reg_node(iname, 0); + + discovery_isns_free_servername(); + return rc; +} + +int discovery_fw(void *data, struct iface_rec *iface, + struct list_head *rec_list) +{ + struct discovery_rec *drec = data; + struct boot_context *bcontext; + struct list_head targets; + struct node_rec *rec; + int rc; + + INIT_LIST_HEAD(&targets); + rc = fw_get_targets(&targets); + if (rc) { + log_error("Could not get list of targets from firmware. " + "(err %d)", rc); + return rc; + } + if (list_empty(&targets)) + return 0; + /* + * TODO: Do we want to match the iface MAC/netdev with what is in + * the firmware or could the user want to bind based on what is + * in passed in or in the default ifaces? + */ + + list_for_each_entry(bcontext, &targets, list) { + rec = idbm_create_rec_from_boot_context(bcontext); + if (!rec) { + log_error("Could not convert firmware info to " + "node record."); + rc = ISCSI_ERR_NOMEM; + goto free_targets; + } + rec->disc_type = drec->type; + + list_add_tail(&rec->list, rec_list); + } + +free_targets: + fw_free_targets(&targets); + return rc; +} + +int discovery_offload_sendtargets(int host_no, int do_login, + discovery_rec_t *drec) +{ + struct sockaddr_storage ss; + char default_port[NI_MAXSERV]; + iscsiadm_req_t req; + iscsiadm_rsp_t rsp; + int rc; + + log_debug(4, "offload st though host %d to %s", host_no, + drec->address); + + memset(&req, 0, sizeof(req)); + req.command = MGMT_IPC_SEND_TARGETS; + req.u.st.host_no = host_no; + req.u.st.do_login = do_login; + + /* resolve the DiscoveryAddress to an IP address */ + sprintf(default_port, "%d", drec->port); + rc = resolve_address(drec->address, default_port, &ss); + if (rc) + return rc; + + req.u.st.ss = ss; + + /* + * We only know how ask qla4xxx to do discovery and login + * to what it finds. We are not able to get what it finds or + * is able to log into so we just send the command and proceed. + * + * There is a way to just use the hw to send a sendtargets command + * and get back the results. We should do this since it would + * allows us to then process the results like software iscsi. + */ + rc = iscsid_exec_req(&req, &rsp, 1, drec->iscsid_req_tmo); + if (rc) { + log_error("Could not offload sendtargets to %s.", + drec->address); + iscsi_err_print_msg(rc); + return rc; + } + + return 0; +} + +static int +iscsi_make_text_pdu(iscsi_session_t *session, struct iscsi_hdr *hdr, + char *data, int max_data_length) +{ + struct iscsi_text *text_pdu = (struct iscsi_text *)hdr; + + /* initialize the PDU header */ + memset(text_pdu, 0, sizeof (*text_pdu)); + + text_pdu->opcode = ISCSI_OP_TEXT; + text_pdu->itt = htonl(session->itt); + text_pdu->ttt = ISCSI_RESERVED_TAG; + text_pdu->cmdsn = htonl(session->cmdsn++); + text_pdu->exp_statsn = htonl(session->conn[0].exp_statsn); + + return 1; +} + +static int +request_targets(iscsi_session_t *session) +{ + char data[64]; + struct iscsi_text text; + struct iscsi_hdr *hdr = (struct iscsi_hdr *) &text; + + memset(&text, 0, sizeof (text)); + memset(data, 0, sizeof (data)); + + /* make a text PDU with SendTargets=All */ + if (!iscsi_make_text_pdu(session, hdr, data, sizeof (data))) { + log_error("failed to make a SendTargets PDU"); + return 0; + } + + if (!iscsi_add_text(hdr, data, sizeof (data), "SendTargets", "All")) { + log_error("failed to add SendTargets text key"); + return 0; + } + + text.ttt = ISCSI_RESERVED_TAG; + text.flags = ISCSI_FLAG_CMD_FINAL; + + if (!iscsi_io_send_pdu(&session->conn[0], hdr, ISCSI_DIGEST_NONE, data, + ISCSI_DIGEST_NONE, session->conn[0].active_timeout)) { + log_error("failed to send SendTargets PDU"); + return 0; + } + + return 1; +} + +static int +iterate_targets(iscsi_session_t *session, uint32_t ttt) +{ + char data[64]; + struct iscsi_text text; + struct iscsi_hdr *pdu = (struct iscsi_hdr *) &text; + + memset(&text, 0, sizeof (text)); + memset(data, 0, sizeof (data)); + + /* make an empty text PDU */ + if (!iscsi_make_text_pdu(session, pdu, data, sizeof (data))) { + log_error("failed to make an empty text PDU"); + return 0; + } + + text.ttt = ttt; + text.flags = ISCSI_FLAG_CMD_FINAL; + + if (!iscsi_io_send_pdu(&session->conn[0], pdu, ISCSI_DIGEST_NONE, data, + ISCSI_DIGEST_NONE, session->conn[0].active_timeout)) { + log_error("failed to send empty text PDU"); + return 0; + } + + return 1; +} + +static int add_portal(struct list_head *rec_list, discovery_rec_t *drec, + char *targetname, char *address, char *port, char *tag) +{ + struct sockaddr_storage ss; + struct node_rec *rec; + + if (resolve_address(address, port, &ss)) { + log_error("cannot resolve %s", address); + return 0; + } + + rec = calloc(1, sizeof(*rec)); + if (!rec) + return 0; + + idbm_node_setup_from_conf(rec); + rec->disc_type = drec->type; + rec->disc_port = drec->port; + strcpy(rec->disc_address, drec->address); + + strlcpy(rec->name, targetname, TARGET_NAME_MAXLEN); + if (tag && *tag) + rec->tpgt = atoi(tag); + else + rec->tpgt = PORTAL_GROUP_TAG_UNKNOWN; + if (port && *port) + rec->conn[0].port = atoi(port); + else + rec->conn[0].port = ISCSI_LISTEN_PORT; + strlcpy(rec->conn[0].address, address, NI_MAXHOST); + + list_add_tail(&rec->list, rec_list); + return 1; +} + +static int +add_target_record(char *name, char *end, discovery_rec_t *drec, + struct list_head *rec_list) +{ + char *text = NULL; + char *nul = name; + size_t length; + + /* address = IPv4 + * address = [IPv6] + * address = DNSname + * address = IPv4:port + * address = [IPv6]:port + * address = DNSname:port + * address = IPv4,tag + * address = [IPv6],tag + * address = DNSname,tag + * address = IPv4:port,tag + * address = [IPv6]:port,tag + * address = DNSname:port,tag + */ + + log_debug(7, "adding target record %p, end %p", name, end); + + /* find the end of the name */ + while ((nul < end) && (*nul != '\0')) + nul++; + + length = nul - name; + if (length > TARGET_NAME_MAXLEN) { + log_error("TargetName %s too long, ignoring", name); + return 0; + } + text = name + length; + + /* skip NULs after the name */ + while ((text < end) && (*text == '\0')) + text++; + + /* if no address is provided, use the default */ + if (text >= end) { + if (drec->address == NULL) { + log_error("no default address known for target %s", + name); + return 0; + } else { + char default_port[NI_MAXSERV]; + + sprintf(default_port, "%d", drec->port); + if (!add_portal(rec_list, drec, name, drec->address, + default_port, NULL)) { + log_error("failed to add default portal, " + "ignoring target %s", name); + return 0; + } + } + /* finished adding the default */ + return 1; + } + + /* process TargetAddresses */ + while (text < end) { + char *next = text + strlen(text) + 1; + + log_debug(7, "text %p, next %p, end %p, %s", text, next, end, + text); + + if (strncmp(text, "TargetAddress=", 14) == 0) { + char *port = NULL; + char *tag = NULL; + char *address = text + 14; + char *temp; + + if ((tag = strrchr(text, ','))) { + *tag = '\0'; + tag++; + } + if ((port = strrchr(text, ':'))) { + *port = '\0'; + port++; + } + + if (*address == '[') { + address++; + if ((temp = strrchr(text, ']'))) + *temp = '\0'; + } + + if (!add_portal(rec_list, drec, name, address, port, + tag)) { + log_error("failed to add default portal, " + "ignoring target %s", name); + return 0; + } + } else + log_error("unexpected SendTargets data: %s", + text); + text = next; + } + + return 1; +} + +static int +process_sendtargets_response(struct str_buffer *sendtargets, + int final, discovery_rec_t *drec, + struct list_head *rec_list) +{ + char *start = str_buffer_data(sendtargets); + char *text = start; + char *end = text + str_data_length(sendtargets); + char *nul = end - 1; + char *record = NULL; + int num_targets = 0; + + if (start == end) { + /* no SendTargets data */ + goto done; + } + + /* scan backwards to find the last NUL in the data, to ensure we + * don't walk off the end. Since key=value pairs can span PDU + * boundaries, we're not guaranteed that the end of the data has a + * NUL. + */ + while ((nul > start) && *nul) + nul--; + + if (nul == start) { + /* couldn't find anything we can process now, + * it's one big partial string + */ + goto done; + } + + /* find the boundaries between target records (TargetName or final PDU) + */ + for (;;) { + /* skip NULs */ + while ((text < nul) && (*text == '\0')) + text++; + + if (text == nul) + break; + + log_debug(7, + "processing sendtargets record %p, text %p, line %s", + record, text, text); + + /* look for the start of a new target record */ + if (strncmp(text, "TargetName=", 11) == 0) { + if (record) { + /* send the last record, which we just found + * the end of. don't bother passing the + * "TargetName=" prefix. + */ + if (!add_target_record(record + 11, text, + drec, rec_list)) { + log_error( + "failed to add target record"); + str_truncate_buffer(sendtargets, 0); + goto done; + } + num_targets++; + } + record = text; + } + + /* everything up til the next NUL must be part of the + * current target record + */ + while ((text < nul) && (*text != '\0')) + text++; + } + + if (record) { + if (final) { + /* if this is the last PDU of the text sequence, + * it also ends a target record + */ + log_debug(7, + "processing final sendtargets record %p, " + "line %s", + record, record); + if (add_target_record (record + 11, text, + drec, rec_list)) { + num_targets++; + record = NULL; + str_truncate_buffer(sendtargets, 0); + } else { + log_error("failed to add target record"); + str_truncate_buffer(sendtargets, 0); + goto done; + } + } else { + /* remove the parts of the sendtargets buffer we've + * processed, and move the parts we haven't to the + * beginning of the buffer. + */ + log_debug(7, + "processed %d bytes of sendtargets data, " + "%d remaining", + (int)(record - str_buffer_data(sendtargets)), + (int)(str_buffer_data(sendtargets) + + str_data_length(sendtargets) - record)); + str_remove_initial(sendtargets, + record - str_buffer_data(sendtargets)); + } + } + + done: + + return 1; +} + +static void iscsi_free_session(struct iscsi_session *session) +{ + list_del_init(&session->list); + free(session); +} + +static iscsi_session_t * +iscsi_alloc_session(struct iscsi_sendtargets_config *config, + struct iface_rec *iface, int *rc, int tmo) +{ + iscsi_session_t *session; + + *rc = 0; + + session = calloc(1, sizeof (*session)); + if (session == NULL) { + *rc = ISCSI_ERR_NOMEM; + return NULL; + } + + session->t = iscsi_sysfs_get_transport_by_name(iface->transport_name); + if (!session->t) { + log_error("iSCSI driver %s is not loaded. Load the module " + "then retry the command.", iface->transport_name); + *rc = ISCSI_ERR_TRANS_NOT_FOUND; + goto fail; + } + + INIT_LIST_HEAD(&session->list); + /* initialize the session's leading connection */ + session->conn[0].id = 0; + session->conn[0].socket_fd = -1; + session->conn[0].session = session; + session->conn[0].login_timeout = config->conn_timeo.login_timeout; + session->conn[0].auth_timeout = config->conn_timeo.auth_timeout; + session->conn[0].active_timeout = config->conn_timeo.active_timeout; + session->conn[0].noop_out_timeout = 0; + session->conn[0].noop_out_interval = 0; + session->reopen_cnt = config->reopen_max + 1; + iscsi_copy_operational_params(&session->conn[0], &config->session_conf, + &config->conn_conf); + + /* OUI and uniqifying number */ + session->isid[0] = DRIVER_ISID_0; + session->isid[1] = DRIVER_ISID_1; + session->isid[2] = DRIVER_ISID_2; + session->isid[3] = 0; + session->isid[4] = 0; + session->isid[5] = 0; + + if (strlen(iface->iname)) { + strcpy(initiator_name, iface->iname); + /* MNC TODO add iface alias */ + } else { + *rc = request_initiator_name(tmo); + if (*rc) { + log_error("Cannot perform discovery. Initiatorname " + "required."); + goto fail; + } else if (initiator_name[0] == '\0') { + log_error("Cannot perform discovery. Invalid " + "Initiatorname."); + *rc = ISCSI_ERR_INVAL; + goto fail; + } + } + + iface_copy(&session->nrec.iface, iface); + session->initiator_name = initiator_name; + session->initiator_alias = initiator_alias; + session->portal_group_tag = PORTAL_GROUP_TAG_UNKNOWN; + session->type = ISCSI_SESSION_TYPE_DISCOVERY; + session->id = -1; + + /* setup authentication variables for the session*/ + *rc = iscsi_setup_authentication(session, &config->auth); + if (*rc) + goto fail; + + list_add_tail(&session->list, &session->t->sessions); + return session; + +fail: + free(session); + return NULL; +} + +static int +process_recvd_pdu(struct iscsi_hdr *pdu, + discovery_rec_t *drec, + struct list_head *rec_list, + iscsi_session_t *session, + struct str_buffer *sendtargets, + int *active, + int *valid_text, + char *data) +{ + int rc=0; + + switch (pdu->opcode) { + case ISCSI_OP_TEXT_RSP:{ + struct iscsi_text_rsp *text_response = + (struct iscsi_text_rsp *) pdu; + int dlength = ntoh24(pdu->dlength); + int final = + (text_response->flags & ISCSI_FLAG_CMD_FINAL) || + (text_response-> ttt == ISCSI_RESERVED_TAG); + size_t curr_data_length; + + log_debug(4, "discovery session to %s:%d received text" + " response, %d data bytes, ttt 0x%x, " + "final 0x%x", + drec->address, + drec->port, + dlength, + ntohl(text_response->ttt), + text_response->flags & ISCSI_FLAG_CMD_FINAL); + + /* mark how much more data in the sendtargets + * buffer is now valid + */ + curr_data_length = str_data_length(sendtargets); + if (str_enlarge_data(sendtargets, dlength)) { + log_error("Could not allocate memory to " + "process SendTargets response."); + rc = 0; + goto done; + } + + memcpy(str_buffer_data(sendtargets) + curr_data_length, + data, dlength); + + *valid_text = 1; + /* process as much as we can right now */ + process_sendtargets_response(sendtargets, + final, + drec, + rec_list); + + if (final) { + /* SendTargets exchange is now complete + */ + *active = 0; + /* from now on, after any reconnect, + * assume LUNs may have changed + */ + } else { + /* ask for more targets */ + if (!iterate_targets(session, + text_response->ttt)) { + rc = DISCOVERY_NEED_RECONNECT; + goto done; + } + } + break; + } + default: + log_warning( + "discovery session to %s:%d received " + "unexpected opcode 0x%x", + drec->address, drec->port, pdu->opcode); + rc = DISCOVERY_NEED_RECONNECT; + goto done; + } + done: + return(rc); +} + +#if 0 /* Unused */ +/* + * Make a best effort to logout the session. + */ +static void iscsi_logout(iscsi_session_t * session) +{ + struct iscsi_logout logout_req; + struct iscsi_logout_rsp logout_resp; + int rc; + + /* + * Build logout request header + */ + memset(&logout_req, 0, sizeof (logout_req)); + logout_req.opcode = ISCSI_OP_LOGOUT | ISCSI_OP_IMMEDIATE; + logout_req.flags = ISCSI_FLAG_CMD_FINAL | + (ISCSI_LOGOUT_REASON_CLOSE_SESSION & + ISCSI_FLAG_LOGOUT_REASON_MASK); + logout_req.itt = htonl(session->itt); + if (++session->itt == ISCSI_RESERVED_TAG) + session->itt = 1; + logout_req.cmdsn = htonl(session->cmdsn); + logout_req.exp_statsn = htonl(++session->conn[0].exp_statsn); + + /* + * Send the logout request + */ + rc = iscsi_io_send_pdu(&session->conn[0],(struct iscsi_hdr *)&logout_req, + ISCSI_DIGEST_NONE, NULL, ISCSI_DIGEST_NONE, 3); + if (!rc) { + log_error( + "iscsid: iscsi_logout - failed to send logout PDU."); + return; + } + + /* + * Read the logout response + */ + memset(&logout_resp, 0, sizeof(logout_resp)); + rc = iscsi_io_recv_pdu(&session->conn[0], + (struct iscsi_hdr *)&logout_resp, ISCSI_DIGEST_NONE, NULL, + 0, ISCSI_DIGEST_NONE, 1); + if (rc < 0) { + log_error("iscsid: logout - failed to receive logout resp"); + return; + } + if (logout_resp.response != ISCSI_LOGOUT_SUCCESS) { + log_error("iscsid: logout failed - response = 0x%x", + logout_resp.response); + } +} +#endif /* Unused */ + +static void iscsi_destroy_session(struct iscsi_session *session) +{ + struct iscsi_transport *t = session->t; + struct iscsi_conn *conn = &session->conn[0]; + int rc; + + if (session->id == -1) + return; + + if (!(t->caps & CAP_TEXT_NEGO)) { + iscsi_io_disconnect(&session->conn[0]); + goto done; + } + + log_debug(2, "%s ep disconnect", __FUNCTION__); + t->template->ep_disconnect(conn); + + log_debug(2, "stop conn"); + rc = ipc->stop_conn(session->t->handle, session->id, + conn->id, STOP_CONN_TERM); + if (rc) { + log_error("Could not stop conn %d:%d cleanly (err %d)", + session->id, conn->id, rc); + goto done; + } + + log_debug(2, "%s destroy conn", __FUNCTION__); + rc = ipc->destroy_conn(session->t->handle, session->id, conn->id); + if (rc) { + log_error("Could not safely destroy conn %d:%d (err %d)", + session->id, conn->id, rc); + goto done; + } + + log_debug(2, "%s destroy session", __FUNCTION__); + rc = ipc->destroy_session(session->t->handle, session->id); + if (rc) + log_error("Could not safely destroy session %d (err %d)", + session->id, rc); +done: + if (session->target_alias) { + free(session->target_alias); + session->target_alias = NULL; + } + + if (conn->socket_fd >= 0) { + ipc->ctldev_close(); + conn->socket_fd = -1; + } + session->id = -1; +} + +static int iscsi_create_leading_conn(struct iscsi_session *session) +{ + struct iface_rec *iface = &session->nrec.iface; + struct iscsi_transport *t = session->t; + struct iscsi_conn *conn = &session->conn[0]; + uint32_t host_no; + int rc, sleep_count = 0; + + if (!(t->caps & CAP_TEXT_NEGO)) { + /* + * If the LLD does not support TEXT PDUs then we do + * discovery in userspace. + */ + session->use_ipc = 0; + + if (!iscsi_io_connect(conn)) + return ISCSI_ERR_TRANS; + + session->id = 1; + return 0; + } + session->use_ipc = 1; + + /* + * for software this is the tcp socket fd set in iscsi_io_connect + * and for offload this is the iscsi netlink socket fd + */ + conn->socket_fd = ipc->ctldev_open(); + if (conn->socket_fd < 0) { + log_error("Could not open netlink interface (err %d)", + errno); + return ISCSI_ERR_INTERNAL; + } + + host_no = iscsi_sysfs_get_host_no_from_hwinfo(iface, &rc); + if (!rc) { + /* + * if the netdev or mac was set, then we are going to want + * to want to bind the all the conns/eps to a specific host + * if offload is used. + */ + session->conn[0].bind_ep = 1; + session->hostno = host_no; + } + + rc = iscsi_host_set_net_params(iface, session); + if (rc) { + log_error("Could not set host net params (err %d)", + rc); + if (rc != ISCSI_ERR_AGAIN) + rc = ISCSI_ERR_INTERNAL; + goto close_ipc; + } + + /* create interconnect endpoint */ + log_debug(2, "%s discovery ep connect", __FUNCTION__); + rc = t->template->ep_connect(conn, 1); + if (rc < 0) { + rc = ISCSI_ERR_TRANS; + goto close_ipc; + } + + do { + rc = t->template->ep_poll(conn, 1); + if (rc < 0) { + rc = ISCSI_ERR_TRANS; + goto disconnect; + } else if (rc == 0) { + if (sleep_count == conn->login_timeout) { + rc = ISCSI_ERR_TRANS_TIMEOUT; + goto disconnect; + } + sleep_count++; + sleep(1); + } else + break; + } while (1); + + log_debug(2, "%s discovery create session", __FUNCTION__); + /* create kernel structs */ + rc = ipc->create_session(session->t->handle, + conn->transport_ep_handle, 1, 32, 1, + &session->id, &host_no); + if (rc) { + log_error("Could not create kernel session (err %d).", rc); + rc = ISCSI_ERR_INTERNAL; + goto disconnect; + } + log_debug(2, "%s discovery created session %u", __FUNCTION__, + session->id); + session->isid[3] = (session->id >> 16) & 0xff; + session->isid[4] = (session->id >> 8) & 0xff; + session->isid[5] = session->id & 0xff; + + log_debug(2, "%s discovery create conn", __FUNCTION__); + rc = ipc->create_conn(t->handle, session->id, conn->id, &conn->id); + if (rc) { + log_error("Could not create connection (err %d)", rc); + rc = ISCSI_ERR_INTERNAL; + goto disconnect; + } + + log_debug(2, "%s discovery bind conn", __FUNCTION__); + if (ipc->bind_conn(t->handle, session->id, conn->id, + conn->transport_ep_handle, (conn->id == 0), &rc) || + rc) { + log_error("Could not bind conn %d:%d to session %d, " + "(err %d)", session->id, conn->id, + session->id, rc); + rc = ISCSI_ERR_INTERNAL; + goto disconnect; + } + + /* all set */ + return 0; + +disconnect: + t->template->ep_disconnect(conn); + + if (session->id != -1 && + iscsi_sysfs_session_has_leadconn(session->id)) { + if (ipc->destroy_conn(session->t->handle, session->id, + conn->id)) + log_error("Could not safely destroy connection %d:%d", + session->id, conn->id); + } + + if (session->id != -1) { + if (ipc->destroy_session(session->t->handle, session->id)) + log_error("Could not safely destroy session %d", + session->id); + session->id = -1; + } + +close_ipc: + if (conn->socket_fd >= 0) { + ipc->ctldev_close(); + conn->socket_fd = -1; + } + + log_error("Connection to discovery portal %s failed: %s", + conn->host, iscsi_err_to_str(rc)); + return rc; +} + +static struct iscsi_ev_context * +iscsi_ev_context_get(struct iscsi_conn *conn, int ev_size) +{ + log_debug(2, "%s: ev_size %d", __FUNCTION__, ev_size); + + ipc_ev_context.data = calloc(1, ev_size); + if (!ipc_ev_context.data) + return NULL; + + return &ipc_ev_context; +} + +static void iscsi_ev_context_put(struct iscsi_ev_context *ev_context) +{ + if (ev_context->data) + free(ev_context->data); + ev_context->data = NULL; +} + +static int iscsi_sched_ev_context(struct iscsi_ev_context *ev_context, + struct iscsi_conn *conn, unsigned long tmo, + int event) +{ + if (event == EV_CONN_RECV_PDU || event == EV_CONN_LOGIN) { + conn->recv_context = ev_context; + return 0; + } + + return -EIO; +} + +static struct iscsi_ipc_ev_clbk ipc_clbk = { + .get_ev_context = iscsi_ev_context_get, + .put_ev_context = iscsi_ev_context_put, + .sched_ev_context = iscsi_sched_ev_context, +}; + +static int iscsi_wait_for_login(struct iscsi_conn *conn) +{ + struct iscsi_session *session = conn->session; + struct iscsi_transport *t = session->t; + struct pollfd pfd; + struct timeval connection_timer; + int timeout, rc; + uint32_t conn_state; + int status = 0; + + if (!(t->caps & CAP_LOGIN_OFFLOAD)) + return 0; + + iscsi_timer_set(&connection_timer, conn->active_timeout); + + /* prepare to poll */ + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = conn->socket_fd; + pfd.events = POLLIN | POLLPRI; + + timeout = iscsi_timer_msecs_until(&connection_timer); + +login_repoll: + log_debug(4, "discovery login process polling fd %d, " + "timeout in %f seconds", pfd.fd, timeout / 1000.0); + + pfd.revents = 0; + rc = poll(&pfd, 1, timeout); + + log_debug(7, "discovery login process returned from poll, rc %d", rc); + + if (iscsi_timer_expired(&connection_timer)) { + log_warning("Discovery login session timed out."); + rc = ISCSI_ERR_INTERNAL; + goto done; + } + + if (rc > 0) { + if (pfd.revents & (POLLIN | POLLPRI)) { + timeout = iscsi_timer_msecs_until(&connection_timer); + status = ipc->recv_conn_state(conn, &conn_state); + if (status == -EAGAIN) + goto login_repoll; + else if (status < 0) { + rc = ISCSI_ERR_TRANS; + goto done; + } + + if (conn_state != ISCSI_CONN_STATE_LOGGED_IN) + rc = ISCSI_ERR_TRANS; + else + rc = 0; + goto done; + } + + if (pfd.revents & POLLHUP) { + log_warning("discovery session" + "terminating after hangup"); + rc = ISCSI_ERR_TRANS; + goto done; + } + + if (pfd.revents & POLLNVAL) { + log_warning("discovery POLLNVAL"); + rc = ISCSI_ERR_INTERNAL; + goto done; + } + + if (pfd.revents & POLLERR) { + log_warning("discovery POLLERR"); + rc = ISCSI_ERR_INTERNAL; + goto done; + } + } else if (rc < 0) { + log_error("Login poll error"); + rc = ISCSI_ERR_INTERNAL; + goto done; + } + +done: + return rc; +} + +static int iscsi_create_session(struct iscsi_session *session, + struct iscsi_sendtargets_config *config, + char *data, unsigned int data_len) +{ + struct iscsi_conn *conn = &session->conn[0]; + int login_status, rc = 0, login_delay = 0; + uint8_t status_class = 0, status_detail = 0; + unsigned int login_failures = 0; + char serv[NI_MAXSERV]; + struct iscsi_transport *t = session->t; + +set_address: + /* + * copy the saved address to the session, + * undoing any temporary redirect + */ + conn->saddr = conn->failback_saddr; + +reconnect: + /* fix decrement and test */ + if (--session->reopen_cnt < 0) { + log_error("connection login retries (reopen_max) %d exceeded", + config->reopen_max); + rc = ISCSI_ERR_PDU_TIMEOUT; + goto login_failed; + } + +redirect_reconnect: + session->cmdsn = 1; + session->itt = 1; + session->portal_group_tag = PORTAL_GROUP_TAG_UNKNOWN; + + /* + * On reconnect, just destroy the kernel structs and start over. + */ + iscsi_destroy_session(session); + + /* slowly back off the frequency of login attempts */ + if (login_failures == 0) + login_delay = 0; + else if (login_failures < 10) + login_delay = 1; /* 10 seconds at 1 sec each */ + else if (login_failures < 20) + login_delay = 2; /* 20 seconds at 2 sec each */ + else if (login_failures < 26) + login_delay = 5; /* 30 seconds at 5 sec each */ + else if (login_failures < 34) + login_delay = 15; /* 60 seconds at 15 sec each */ + else + login_delay = 60; /* after 2 minutes, try once a minute */ + + getnameinfo((struct sockaddr *) &conn->saddr, + sizeof(conn->saddr), conn->host, + sizeof(conn->host), serv, sizeof(serv), + NI_NUMERICHOST|NI_NUMERICSERV); + + if (login_delay) { + log_debug(4, "discovery session to %s:%s sleeping for %d " + "seconds before next login attempt", + conn->host, serv, login_delay); + sleep(login_delay); + } + rc = iscsi_create_leading_conn(session); + if (rc) { + login_failures++; + goto reconnect; + } + + log_debug(1, "connected to discovery address %s", conn->host); + + log_debug(4, "discovery session to %s:%s starting iSCSI login", + conn->host, serv); + + /* + * Need to re-init settings because a previous login could + * have set them to what was negotiated for. + */ + iscsi_copy_operational_params(&session->conn[0], &config->session_conf, + &config->conn_conf); + + if (t->caps & CAP_TEXT_NEGO) { + log_debug(2, "%s discovery set params", __FUNCTION__); + rc = iscsi_session_set_params(conn); + if (rc) { + log_error("Could not set iscsi params for conn %d:%d " + "(err %d)", session->id, conn->id, rc); + rc = ISCSI_ERR_INTERNAL; + goto login_failed; + } + } + + if ((session->t->caps & CAP_LOGIN_OFFLOAD)) + goto start_conn; + + status_class = 0; + status_detail = 0; + rc = ISCSI_ERR_LOGIN; + + memset(data, 0, data_len); + login_status = iscsi_login(session, 0, data, data_len, + &status_class, &status_detail); + + switch (login_status) { + case LOGIN_OK: + case LOGIN_REDIRECT: + break; + + case LOGIN_IO_ERROR: + case LOGIN_REDIRECTION_FAILED: + /* try again */ + log_warning("retrying discovery login to %s", conn->host); + login_failures++; + goto set_address; + + default: + case LOGIN_FAILED: + case LOGIN_NEGOTIATION_FAILED: + case LOGIN_AUTHENTICATION_FAILED: + case LOGIN_VERSION_MISMATCH: + case LOGIN_INVALID_PDU: + log_error("discovery login to %s failed, giving up %d", + conn->host, login_status); + rc = ISCSI_ERR_FATAL_LOGIN; + goto login_failed; + } + + /* check the login status */ + switch (status_class) { + case ISCSI_STATUS_CLS_SUCCESS: + log_debug(4, "discovery login success to %s", conn->host); + login_failures = 0; + break; + case ISCSI_STATUS_CLS_REDIRECT: + switch (status_detail) { + /* the session IP address was changed by the login + * library, so just try again with this portal + * config but the new address. + */ + case ISCSI_LOGIN_STATUS_TGT_MOVED_TEMP: + log_warning( + "discovery login temporarily redirected to " + "%s port %s", conn->host, serv); + goto redirect_reconnect; + case ISCSI_LOGIN_STATUS_TGT_MOVED_PERM: + log_warning( + "discovery login permanently redirected to " + "%s port %s", conn->host, serv); + /* make the new address permanent */ + memset(&conn->failback_saddr, 0, + sizeof(struct sockaddr_storage)); + conn->failback_saddr = conn->saddr; + goto redirect_reconnect; + default: + log_error( + "discovery login rejected: redirection type " + "0x%x not supported", + status_detail); + goto set_address; + } + break; + case ISCSI_STATUS_CLS_INITIATOR_ERR: + switch (status_detail) { + case ISCSI_LOGIN_STATUS_AUTH_FAILED: + case ISCSI_LOGIN_STATUS_TGT_FORBIDDEN: + log_error("discovery login to %s rejected: " + "initiator failed authorization", + conn->host); + rc = ISCSI_ERR_LOGIN_AUTH_FAILED; + goto login_failed; + default: + log_error("discovery login to %s rejected: initiator " + "error (%02x/%02x), non-retryable, giving up", + conn->host, status_class, status_detail); + rc = ISCSI_ERR_FATAL_LOGIN; + } + goto login_failed; + case ISCSI_STATUS_CLS_TARGET_ERR: + log_error( + "discovery login to %s rejected: " + "target error (%02x/%02x)", + conn->host, status_class, status_detail); + login_failures++; + goto reconnect; + default: + log_error( + "discovery login to %s failed, response " + "with unknown status class 0x%x, detail 0x%x", + conn->host, + status_class, status_detail); + login_failures++; + goto reconnect; + } + + if (!(t->caps & CAP_TEXT_NEGO)) + return 0; + +start_conn: + log_debug(2, "%s discovery set neg params", __FUNCTION__); + rc = iscsi_session_set_neg_params(conn); + if (rc) { + log_error("Could not set iscsi params for conn %d:%d (err " + "%d)", session->id, conn->id, rc); + rc = ISCSI_ERR_INTERNAL; + goto login_failed; + } + + log_debug(2, "%s discovery start conn", __FUNCTION__); + if (ipc->start_conn(t->handle, session->id, conn->id, &rc) || rc) { + log_error("Cannot start conn %d:%d (err %d)", + session->id, conn->id, rc); + rc = ISCSI_ERR_INTERNAL; + goto login_failed; + } + + rc = iscsi_wait_for_login(conn); + if (!rc) + return 0; + +login_failed: + iscsi_destroy_session(session); + return rc; +} + +int discovery_sendtargets(void *fndata, struct iface_rec *iface, + struct list_head *rec_list) +{ + discovery_rec_t *drec = fndata; + iscsi_session_t *session; + struct pollfd pfd; + struct iscsi_hdr pdu_buffer; + struct iscsi_hdr *pdu = &pdu_buffer; + char *data = NULL; + int active = 0, valid_text = 0; + struct timeval connection_timer; + int timeout; + int rc = 0; + struct str_buffer sendtargets; + unsigned int data_len; + struct iscsi_sendtargets_config *config = &drec->u.sendtargets; + + /* initial setup */ + log_debug(1, "starting sendtargets discovery, address %s:%d, ", + drec->address, drec->port); + memset(&pdu_buffer, 0, sizeof (pdu_buffer)); + iscsi_timer_clear(&connection_timer); + + /* allocate a new session, and initialize default values */ + session = iscsi_alloc_session(config, iface, &rc, drec->iscsid_req_tmo); + if (rc) + return rc; + + ipc_ev_context.conn = &session->conn[0]; + ipc_register_ev_callback(&ipc_clbk); + + log_debug(4, "sendtargets discovery to %s:%d using " + "isid 0x%02x%02x%02x%02x%02x%02x", + drec->address, drec->port, session->isid[0], + session->isid[1], session->isid[2], session->isid[3], + session->isid[4], session->isid[5]); + + /* allocate data buffers for SendTargets data */ + data = malloc(session->conn[0].max_recv_dlength); + if (!data) { + rc = ISCSI_ERR_NOMEM; + goto free_session; + } + data_len = session->conn[0].max_recv_dlength; + + str_init_buffer(&sendtargets, 0); + + /* resolve the DiscoveryAddress to an IP address */ + rc = iscsi_setup_portal(&session->conn[0], drec->address, + drec->port); + if (rc) { + log_error("cannot resolve host name %s", drec->address); + goto free_sendtargets; + } + + log_debug(4, "discovery timeouts: login %d, reopen_cnt %d, auth %d.", + session->conn[0].login_timeout, session->reopen_cnt, + session->conn[0].auth_timeout); + +reconnect: + rc = iscsi_create_session(session, &drec->u.sendtargets, + data, data_len); + if (rc) + goto free_sendtargets; + + /* reinitialize */ + str_truncate_buffer(&sendtargets, 0); + + /* ask for targets */ + if (!request_targets(session)) { + goto reconnect; + } + active = 1; + + /* set timeouts */ + iscsi_timer_set(&connection_timer, session->conn[0].active_timeout); + + /* prepare to poll */ + memset(&pfd, 0, sizeof (pfd)); + pfd.fd = session->conn[0].socket_fd; + pfd.events = POLLIN | POLLPRI; + +repoll: + timeout = iscsi_timer_msecs_until(&connection_timer); + /* block until we receive a PDU, a TCP FIN, a TCP RST, + * or a timeout + */ + log_debug(4, + "discovery process %s:%d polling fd %d, " + "timeout in %f seconds", + drec->address, drec->port, pfd.fd, + timeout / 1000.0); + + pfd.revents = 0; + rc = poll(&pfd, 1, timeout); + + log_debug(7, + "discovery process to %s:%d returned from poll, rc %d", + drec->address, drec->port, rc); + + if (iscsi_timer_expired(&connection_timer)) { + log_warning("Discovery session to %s:%d timed out.", + drec->address, drec->port); + rc = ISCSI_ERR_TRANS_TIMEOUT; + goto reconnect; + } + + if (rc > 0) { + if (pfd.revents & (POLLIN | POLLPRI)) { + timeout = iscsi_timer_msecs_until(&connection_timer); + + rc = iscsi_io_recv_pdu(&session->conn[0], + pdu, ISCSI_DIGEST_NONE, data, + data_len, ISCSI_DIGEST_NONE, + timeout); + if (rc == -EAGAIN) + goto repoll; + else if (rc < 0) { + log_debug(1, "discovery session to " + "%s:%d failed to recv a PDU " + "response, terminating", + drec->address, + drec->port); + rc = ISCSI_ERR_PDU_TIMEOUT; + goto free_sendtargets; + } + + /* + * process iSCSI PDU received + */ + rc = process_recvd_pdu(pdu, drec, rec_list, + session, &sendtargets, + &active, &valid_text, data); + if (rc == DISCOVERY_NEED_RECONNECT) + goto reconnect; + + /* reset timers after receiving a PDU */ + if (active) { + iscsi_timer_set(&connection_timer, + session->conn[0].active_timeout); + goto repoll; + } + } + + if (pfd.revents & POLLHUP) { + log_warning("discovery session to %s:%d " + "terminating after hangup", + drec->address, drec->port); + rc = ISCSI_ERR_TRANS; + goto free_sendtargets; + } + + if (pfd.revents & POLLNVAL) { + log_warning("discovery POLLNVAL"); + sleep(1); + goto reconnect; + } + + if (pfd.revents & POLLERR) { + log_warning("discovery POLLERR"); + sleep(1); + goto reconnect; + } + } else if (rc < 0) { + log_error("poll error"); + rc = ISCSI_ERR; + goto free_sendtargets; + } + + log_debug(1, "discovery process to %s:%d exiting", + drec->address, drec->port); + rc = 0; + +free_sendtargets: + str_free_buffer(&sendtargets); + free(data); + iscsi_destroy_session(session); +free_session: + iscsi_free_session(session); + return rc; +} + +#ifdef SLP_ENABLE +int +slp_discovery(struct iscsi_slp_config *config) +{ + struct sigaction action; + char *pl; + unsigned short flag = 0; + + memset(&action, 0, sizeof (struct sigaction)); + action.sa_sigaction = NULL; + action.sa_flags = 0; + action.sa_handler = SIG_DFL; + sigaction(SIGTERM, &action, NULL); + sigaction(SIGINT, &action, NULL); + sigaction(SIGPIPE, &action, NULL); + + action.sa_handler = sighup_handler; + sigaction(SIGHUP, &action, NULL); + + if (iscsi_process_should_exit()) { + log_debug(1, "slp discovery process %p exiting", discovery); + exit(0); + } + + discovery->pid = getpid(); + + pl = generate_predicate_list(discovery, &flag); + + while (1) { + if (flag == SLP_MULTICAST_ENABLED) { + discovery->flag = SLP_MULTICAST_ENABLED; + slp_multicast_srv_query(discovery, pl, GENERIC_QUERY); + } + + if (flag == SLP_UNICAST_ENABLED) { + discovery->flag = SLP_UNICAST_ENABLED; + slp_unicast_srv_query(discovery, pl, GENERIC_QUERY); + } + + sleep(config->poll_interval); + } + + exit(0); +} + +#endif diff --git a/usr/discovery.h b/usr/discovery.h new file mode 100644 index 0000000..0575e2a --- /dev/null +++ b/usr/discovery.h @@ -0,0 +1,43 @@ +/* + * iSCSI discovery + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * maintained by open-iscsi@@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#ifndef DISCOVERY_H +#define DISCOVERY_H + + +/* discovery.c */ +struct discovery_rec; +struct list_head; +struct iface_rec; +struct node_rec; +struct boot_context; + +extern int discovery_isns_query(struct discovery_rec *drec, const char *iname, + const char *targetname, + struct list_head *rec_list); +extern void discovery_isns_free_servername(void); +extern int discovery_isns_set_servername(char *address, int port); +extern int discovery_isns(void *data, struct iface_rec *iface, + struct list_head *rec_list); +extern int discovery_fw(void *data, struct iface_rec *iface, + struct list_head *rec_list); +extern int discovery_sendtargets(void *data, struct iface_rec *iface, + struct list_head *rec_list); +extern int discovery_offload_sendtargets(int host_no, int do_login, + struct discovery_rec *drec); +#endif /* DISCOVERY_H */ diff --git a/usr/discoveryd.c b/usr/discoveryd.c new file mode 100644 index 0000000..1955ce9 --- /dev/null +++ b/usr/discoveryd.c @@ -0,0 +1,1107 @@ +/* + * iSCSI Initiator discovery daemon + * + * Copyright (C) 2010 Mike Christie + * Copyright (C) 2010 Red Hat, Inc. All rights reserved. + + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "discovery.h" +#include "idbm.h" +#include "list.h" +#include "iscsi_proto.h" +#include "sysdeps.h" +#include "log.h" +#include "session_mgmt.h" +#include "iscsi_util.h" +#include "event_poll.h" +#include "iface.h" +#include "session_mgmt.h" +#include "session_info.h" +#include "iscsi_err.h" +#include +#include +#include +#include + +#define DISC_DEF_POLL_INVL 30 + +static LIST_HEAD(iscsi_targets); +static int stop_discoveryd; + +static LIST_HEAD(isns_initiators); +static LIST_HEAD(isns_refresh_list); +static char *isns_entity_id = NULL; +static uint32_t isns_refresh_interval; +static int isns_register_nodes = 1; + +static void isns_reg_refresh_by_eid_qry(void *data); + +typedef void (do_disc_and_login_fn)(const char *def_iname, + struct discovery_rec *drec, int poll_inval); + +static int logout_session(void *data, struct list_head *list, + struct session_info *info) +{ + struct list_head *rec_list = data; + struct node_rec *rec; + + list_for_each_entry(rec, rec_list, list) { + if (iscsi_match_session(rec, info)) + return iscsi_logout_portal(info, list); + } + return -1; +} + +static void discoveryd_stop(void) +{ + struct node_rec *rec, *tmp_rec; + int nr_found = 0; + + if (list_empty(&iscsi_targets)) + goto done; + + /* + * User requested to just login and exit. + */ + if (!stop_discoveryd) + goto done; + + iscsi_logout_portals(&iscsi_targets, &nr_found, 1, logout_session); + list_for_each_entry_safe(rec, tmp_rec, &iscsi_targets, list) { + list_del(&rec->list); + free(rec); + } + +done: + exit(0); +} + +static void catch_signal(int signo) +{ + log_debug(1, "%d caught signal -%d...", signo, getpid()); + switch (signo) { + case SIGTERM: + stop_discoveryd = 1; + break; + default: + break; + } +} + +static void setup_signal_handler(void) +{ + struct sigaction sa_old; + struct sigaction sa_new; + + sa_new.sa_handler = catch_signal; + sigemptyset(&sa_new.sa_mask); + sa_new.sa_flags = 0; + sigaction(SIGTERM, &sa_new, &sa_old ); +} + +/* + * update_sessions - login/logout sessions + * @new_rec_list: new target portals recs bound to ifaces + * @targetname: if set we only update sessions for this target + * @iname: if set we only update session for that initiator + * + * This will login/logout of portals. When it returns the recs on + * new_rec_list will be freed or put on the iscsi_targets list. + * + * FIXME: if we are hitting a per problem this may be it. With targets + * that do a target per lun this could get ugly. + */ +static void update_sessions(struct list_head *new_rec_list, + const char *targetname, const char *iname) +{ + struct node_rec *rec, *tmp_rec; + struct list_head stale_rec_list; + int nr_found; + + INIT_LIST_HEAD(&stale_rec_list); + /* + * Check if a target portal is no longer being sent. + * Note: Due to how we reread ifaces this will also detect + * changes in ifaces being access through portals. + */ + list_for_each_entry_safe(rec, tmp_rec, &iscsi_targets, list) { + log_debug(7, "Trying to match %s %s to %s %s %s", + targetname, iname, rec->name, rec->conn[0].address, + rec->iface.name); + if (targetname && strcmp(rec->name, targetname)) + continue; + + if (iname) { + if (strlen(rec->iface.iname) && + strcmp(rec->iface.iname, iname)) + continue; + else if (strcmp(iname, isns_config.ic_source_name)) + continue; + } + + log_debug(5, "Matched %s %s, checking if in new targets.", + targetname, iname); + if (!idbm_find_rec_in_list(new_rec_list, rec->name, + rec->conn[0].address, + rec->conn[0].port, &rec->iface)) { + log_debug(5, "Not found. Marking for logout"); + list_move_tail(&rec->list, &stale_rec_list); + } + } + + list_for_each_entry_safe(rec, tmp_rec, new_rec_list, list) { + if (!iscsi_check_for_running_session(rec)) + iscsi_login_portal_nowait(rec); + + if (!idbm_find_rec_in_list(&iscsi_targets, rec->name, + rec->conn[0].address, + rec->conn[0].port, &rec->iface)) { + log_debug(5, "%s %s %s %s not on curr target list. " + "Adding.", rec->name, rec->conn[0].address, + rec->iface.name, rec->iface.iname); + list_move_tail(&rec->list, &iscsi_targets); + } else { + list_del(&rec->list); + free(rec); + } + } + + if (!list_empty(&stale_rec_list)) { + iscsi_logout_portals(&stale_rec_list, &nr_found, 0, + logout_session); + list_for_each_entry_safe(rec, tmp_rec, &stale_rec_list, list) { + list_del(&rec->list); + free(rec); + } + } +} + +static void fork_disc(const char *def_iname, struct discovery_rec *drec, + int poll_inval, do_disc_and_login_fn *do_disc_and_login) +{ + pid_t pid; + + pid = fork(); + if (pid == 0) { + setup_signal_handler(); + do_disc_and_login(def_iname, drec, poll_inval); + exit(0); + } else if (pid < 0) + log_error("Fork failed (err %d - %s). Will not be able " + "to perform discovery to %s.", + errno, strerror(errno), drec->address); + else { + shutdown_callback(pid); + log_debug(1, "iSCSI disc and login helper pid=%d", pid); + reap_inc(); + } +} + +struct isns_node_list { + isns_source_t *source; + struct list_head list; +}; + +/* iSNS */ +static int isns_build_objs(isns_portal_info_t *portal_info, + isns_object_list_t *objs) +{ + struct isns_node_list *node; + isns_object_t *inode, *entity; + unsigned int i, nportals = 1; + int rc = 0; + + log_debug(7, "isns_build_objs"); + + /* we currently just use all portals */ + if (isns_portal_is_wildcard(portal_info)) { + static isns_portal_info_t *iflist; + + nportals = isns_get_nr_portals(); + log_debug(4, "got %d portals", nportals); + if (!nportals) + return ISCSI_ERR_NO_OBJS_FOUND; + + iflist = calloc(nportals, sizeof(isns_portal_info_t)); + if (!iflist) { + log_error("Unable to allocate %d portals.", nportals); + return ISCSI_ERR_NOMEM; + } + + nportals = isns_enumerate_portals(iflist, nportals); + if (nportals == 0) { + log_error("Unable to enumerate portals - " + "no usable interfaces found"); + free(iflist); + return ISCSI_ERR_NO_OBJS_FOUND; + } + for (i = 0; i < nportals; ++i) { + iflist[i].addr.sin6_port = portal_info->addr.sin6_port; + iflist[i].proto = portal_info->proto; + } + portal_info = iflist; + } + + if (!isns_entity_id) { + isns_entity_id = calloc(1, 256); + if (!isns_entity_id) + return ISCSI_ERR_NOMEM; + + rc = getnameinfo((struct sockaddr *) &portal_info->addr, + sizeof(portal_info->addr), + isns_entity_id, 256, NULL, 0, 0); + if (rc) { + free(isns_entity_id); + isns_entity_id = NULL; + + log_error("Could not get hostname for EID."); + return ISCSI_ERR; + } + } + + entity = isns_create_entity(ISNS_ENTITY_PROTOCOL_ISCSI, isns_entity_id); + if (!entity) { + log_error("Could not create iSNS entity."); + return ISCSI_ERR_NOMEM; + } + isns_object_list_append(objs, entity); + + for (i = 0; i < nportals; ++i, ++portal_info) { + isns_object_t *portal; + + portal = isns_create_portal(portal_info, entity); + if (!portal) { + rc = ISCSI_ERR_NOMEM; + goto fail; + } + isns_object_list_append(objs, portal); + + if (!isns_object_set_uint32(portal, ISNS_TAG_SCN_PORT, + isns_portal_tcpudp_port(portal_info))) { + rc = ISCSI_ERR_INVAL; + goto fail; + } + } + + list_for_each_entry(node, &isns_initiators, list) { + inode = isns_create_storage_node2(node->source, + ISNS_ISCSI_INITIATOR_MASK, + NULL); + if (!inode) { + rc = ISCSI_ERR_NOMEM; + goto fail; + } + isns_object_list_append(objs, inode); + } + + return 0; +fail: + isns_object_list_destroy(objs); + return rc; +} + +struct isns_qry_data { + const char *iname; + const char *targetname; +}; + +static int isns_query_node(void *data, struct iface_rec *iface, + struct list_head *recs) +{ + struct isns_qry_data *qry_data = data; + int is_def_iname = 0; + const char *iname; + + if (qry_data->iname) { + if (!strcmp(qry_data->iname, isns_config.ic_source_name)) + is_def_iname = 1; + + if ((!is_def_iname || strlen(iface->iname)) && + strcmp(iface->iname, qry_data->iname)) + return 0; + + iname = qry_data->iname; + } else { + if (strlen(iface->iname)) + iname = iface->iname; + else + iname = isns_config.ic_source_name; + } + + return discovery_isns_query(NULL, iname, qry_data->targetname, recs); +} + +static int isns_disc_new_portals(const char *targetname, const char *iname) +{ + struct list_head ifaces, rec_list; + struct iface_rec *iface, *tmp_iface; + struct isns_qry_data qry_data; + int rc; + + INIT_LIST_HEAD(&rec_list); + INIT_LIST_HEAD(&ifaces); + + qry_data.targetname = targetname; + qry_data.iname = iname; + + iface_link_ifaces(&ifaces); + rc = idbm_bind_ifaces_to_nodes(isns_query_node, &qry_data, &ifaces, + &rec_list); + if (rc) { + log_error("Could not perform iSNS DevAttrQuery for node %s.", + targetname); + goto free_ifaces; + } + update_sessions(&rec_list, targetname, iname); + rc = 0; + +free_ifaces: + list_for_each_entry_safe(iface, tmp_iface, &ifaces, list) { + list_del(&iface->list); + free(iface); + } + + return rc; +} + +static void isns_reg_refresh_with_disc(void *data) +{ + int retries = 0, rc; + + log_debug(1, "Refresh registration using DevAttrQuery"); + + /* + * it is ok to block here since we are not expecting SCNs + * from the server. + */ + do { + /* + * Some servers do not support SCNs so we ping + * the server by doing discovery. + */ + rc = isns_disc_new_portals(NULL, NULL); + if (rc) { + log_debug(4, "Registration refresh using DevAttrQuery " + "failed (retires %d) err %d", retries, rc); + sleep(1); + retries++; + continue; + } + } while (rc && retries < 3); + + if (rc) + /* + * Try to reregister from scratch. + */ + isns_register_nodes = 1; +} + +struct isns_refresh_data { + isns_client_t *clnt; + isns_simple_t *qry; + uint32_t xid; + uint32_t interval; + time_t start_time; + struct list_head list; +}; + +static void isns_free_refresh_data(struct isns_refresh_data *refresh_data) +{ + list_del(&refresh_data->list); + if (refresh_data->qry) + isns_simple_free(refresh_data->qry); + if (refresh_data->clnt) + isns_client_destroy(refresh_data->clnt); + free(refresh_data); +} + +static struct isns_refresh_data *isns_find_refresh_data(uint32_t xid) +{ + struct isns_refresh_data *refresh_data; + + list_for_each_entry(refresh_data, &isns_refresh_list, list) { + if (refresh_data->xid == xid) + return refresh_data; + } + return NULL; +} + +static void isns_eid_qry_rsp(uint32_t xid, int status, isns_simple_t *rsp) +{ + struct isns_refresh_data *refresh_data; + + refresh_data = isns_find_refresh_data(xid); + if (!refresh_data) { + log_error("EID Query respond could not match xid"); + return; + } + + if (refresh_data->clnt) { + isns_client_destroy(refresh_data->clnt); + refresh_data->clnt = NULL; + } + + if (!rsp || status != ISNS_SUCCESS) { + log_debug(1, "Registration refresh using eid qry failed: %s", + isns_strerror(status)); + + isns_add_oneshot_timer(2, isns_reg_refresh_by_eid_qry, + refresh_data); + return; + } + + log_debug(1, "eid qry successful"); + refresh_data->start_time = time(NULL); + isns_add_oneshot_timer(isns_refresh_interval, + isns_reg_refresh_by_eid_qry, refresh_data); +} + +static void isns_reg_refresh_by_eid_qry(void *data) +{ + struct isns_refresh_data *refresh_data = data; + isns_attr_list_t qry_key = ISNS_ATTR_LIST_INIT; + isns_simple_t *qry; + isns_client_t *clnt; + int status, timeout; + + log_debug(1, "Refresh registration using eid qry"); + if (refresh_data->start_time + refresh_data->interval <= time(NULL)) { + log_error("Could not refresh registration with server " + "before registration period. Starting new " + "registration."); + isns_free_refresh_data(refresh_data); + isns_register_nodes = 1; + return; + } + + clnt = isns_create_default_client(NULL); + if (!clnt) { + log_error("iSNS registration refresh failed. Could not " + "connect to server."); + goto rearm; + } + refresh_data->clnt = clnt; + /* + * if a operation has failed we will want to adjust timers + * and possibly reregister. + */ + isns_socket_set_report_failure(clnt->ic_socket); + + /* + * if this is a retry or re-refresh then there will be a qry + */ + qry = refresh_data->qry; + if (qry) + goto send; + + isns_attr_list_append_string(&qry_key, ISNS_TAG_ENTITY_IDENTIFIER, + isns_entity_id); + qry = isns_create_query(clnt, &qry_key); + isns_attr_list_destroy(&qry_key); + if (!qry) + goto rearm; + isns_query_request_attr_tag(qry, ISNS_TAG_ENTITY_PROTOCOL); + refresh_data->qry = qry; + +send: + timeout = (refresh_data->start_time + refresh_data->interval) - + time(NULL); + + status = isns_simple_transmit(clnt->ic_socket, qry, NULL, + timeout, isns_eid_qry_rsp); + if (status == ISNS_SUCCESS) { + log_debug(7, "sent eid qry with xid %u", qry->is_xid); + + refresh_data->xid = qry->is_xid; + return; + } +rearm: + if (refresh_data->clnt) { + isns_client_destroy(refresh_data->clnt); + refresh_data->clnt = NULL; + } + log_debug(1, "Could not send eid qry to refresh registration."); + isns_add_oneshot_timer(2, isns_reg_refresh_by_eid_qry, refresh_data); +} + +static int isns_setup_registration_refresh(isns_simple_t *rsp, int poll_inval) +{ + isns_object_list_t objs = ISNS_OBJECT_LIST_INIT; + struct isns_refresh_data *refresh_data; + int status, i, rc = 0; + uint32_t interval = 0; + + status = isns_query_response_get_objects(rsp, &objs); + if (status) { + log_error("Unable to extract object list from " + "registration response: %s", + isns_strerror(status)); + return ISCSI_ERR; + } + + for (i = 0; i < objs.iol_count; ++i) { + isns_object_t *obj = objs.iol_data[i]; + + if (!isns_object_is_entity(obj)) + continue; + + if (isns_object_get_uint32(obj, ISNS_TAG_REGISTRATION_PERIOD, + &interval)) + break; + } + + if (!interval) + goto free_objs; + + refresh_data = calloc(1, sizeof(*refresh_data)); + if (!refresh_data) { + rc = ISCSI_ERR_NOMEM; + goto free_objs; + } + INIT_LIST_HEAD(&refresh_data->list); + list_add_tail(&refresh_data->list, &isns_refresh_list); + refresh_data->start_time = time(NULL); + + /* + * Several servers do not support SCNs properly, so for the + * registration period refresh we do a DevAttrQuery for all targets + * if the poll_inval is greater than 0. + * + * If the target does support SCNs then we just send a query + * for our entity's protocol. + */ + + /* we cut in half to give us time to handle errors */ + isns_refresh_interval = interval / 2; + if (!isns_refresh_interval) { + log_warning("iSNS Registration Period only %d seconds.", + interval); + isns_refresh_interval = interval; + } + refresh_data->interval = interval; + + if (poll_inval > 0) { + /* user wants to override server and do disc */ + if (isns_refresh_interval > poll_inval) + isns_refresh_interval = poll_inval; + isns_add_timer(isns_refresh_interval, + isns_reg_refresh_with_disc, + refresh_data); + } else + /* + * user wants to use server value so we just ping + * with a simple qry + */ + isns_add_oneshot_timer(isns_refresh_interval, + isns_reg_refresh_by_eid_qry, + refresh_data); + log_debug(5, "Got registration period of %u " + "internval. Using interval of %u", + interval, isns_refresh_interval); + +free_objs: + isns_flush_events(); + isns_object_list_destroy(&objs); + return rc; +} + +static void isns_cancel_refresh_timers(void) +{ + isns_cancel_timer(isns_reg_refresh_with_disc, NULL); + isns_cancel_timer(isns_reg_refresh_by_eid_qry, NULL); +} + +static int isns_register_objs(isns_client_t *clnt, isns_object_list_t *objs, + int poll_inval) +{ + struct isns_node_list *node; + isns_object_t *entity = NULL; + isns_simple_t *reg; + unsigned int i; + int status, rc = 0; + + log_debug(7, "isns_register_objs"); + + for (i = 0; i < objs->iol_count; ++i) { + if (isns_object_is_entity(objs->iol_data[i])) { + entity = objs->iol_data[i]; + break; + } + } + + reg = isns_create_registration(clnt, entity); + if (!reg) + return ISCSI_ERR_NOMEM; + + for (i = 0; i < objs->iol_count; ++i) + isns_registration_add_object(reg, objs->iol_data[i]); + isns_registration_set_replace(reg, 1); + + status = isns_simple_call(clnt->ic_socket, ®); + if (status != ISNS_SUCCESS) { + log_error("Could not register with iSNS server: %s", + isns_strerror(status)); + rc = ISCSI_ERR; + goto free_reg; + } + log_debug(4, "Registered objs"); + + if (!poll_inval) + goto free_reg; + + rc = isns_setup_registration_refresh(reg, poll_inval); + if (rc) + goto free_reg; + + list_for_each_entry(node, &isns_initiators, list) { + isns_simple_free(reg); + reg = isns_create_scn_registration2(clnt, + ISNS_SCN_OBJECT_UPDATED_MASK | + ISNS_SCN_OBJECT_ADDED_MASK | + ISNS_SCN_OBJECT_REMOVED_MASK | + ISNS_SCN_TARGET_AND_SELF_ONLY_MASK, + node->source); + + if (!reg) { + isns_cancel_refresh_timers(); + rc = ISCSI_ERR_NOMEM; + goto done; + } + + status = isns_simple_call(clnt->ic_socket, ®); + if (status != ISNS_SUCCESS) { + log_error("SCN registration for node %s failed: %s", + isns_source_name(node->source), + isns_strerror(status)); + /* + * if the user was going to poll then ignore error + * since user was probably using polling because SCNs + * were not supported by server + */ + if (poll_inval < 0) { + isns_cancel_refresh_timers(); + rc = ISCSI_ERR; + break; + } + } + log_debug(4, "Registered %s for SCNs", + isns_source_name(node->source)); + } + +free_reg: + isns_simple_free(reg); +done: + return rc; +} + +static int isns_scn_register(isns_socket_t *svr_sock, int poll_inval) +{ + isns_object_list_t objs = ISNS_OBJECT_LIST_INIT; + isns_portal_info_t portal_info; + isns_client_t *clnt; + int rc; + + clnt = isns_create_default_client(NULL); + if (!clnt) { + log_error("iSNS setup failed. Could not connect to server."); + return ISCSI_ERR_TRANS; + } + isns_socket_set_disconnect_fatal(clnt->ic_socket); + + log_debug(7, "isns_scn_register"); + + if (!isns_socket_get_portal_info(svr_sock, &portal_info)) { + log_error("Could not get portal info for iSNS registration."); + rc = ISCSI_ERR_NO_OBJS_FOUND; + goto destroy_clnt; + } + + rc = isns_build_objs(&portal_info, &objs); + if (rc) + goto destroy_clnt; + + rc = isns_register_objs(clnt, &objs, poll_inval); + isns_object_list_destroy(&objs); + if (!rc) + log_warning("iSNS: Registered network entity with EID %s with " + "server.", isns_entity_id); + +destroy_clnt: + isns_client_destroy(clnt); + return rc; +} + +static isns_source_t *isns_lookup_node(char *iname) +{ + struct isns_node_list *node; + + list_for_each_entry(node, &isns_initiators, list) { + if (!strcmp(iname, isns_source_name(node->source))) + return node->source; + } + return NULL; +} + +static struct isns_node_list *isns_create_node(const char *iname) +{ + isns_source_t *source; + struct isns_node_list *node; + + source = isns_source_create_iscsi(iname); + if (!source) + return NULL; + + node = calloc(1, sizeof(*node)); + if (!node) { + isns_source_release(source); + return NULL; + } + INIT_LIST_HEAD(&node->list); + node->source = source; + return node; +} + +static int isns_create_node_list(const char *def_iname) +{ + struct iface_rec *iface, *tmp_iface; + struct list_head ifaces; + struct isns_node_list *node, *tmp_node; + int rc = 0; + + INIT_LIST_HEAD(&ifaces); + iface_link_ifaces(&ifaces); + + if (def_iname) { + node = isns_create_node(def_iname); + if (!node) { + rc = ISCSI_ERR_NOMEM; + goto fail; + } + list_add_tail(&node->list, &isns_initiators); + } + + list_for_each_entry(iface, &ifaces, list) { + if (strlen(iface->iname) && + !isns_lookup_node(iface->iname)) { + node = isns_create_node(iface->iname); + if (!node) { + rc = ISCSI_ERR_NOMEM; + goto fail; + } + list_add_tail(&node->list, &isns_initiators); + } + } + /* fix me */ + rc = 0; + goto done; +fail: + list_for_each_entry_safe(node, tmp_node, &isns_initiators, list) { + list_del(&node->list); + free(node); + } + +done: + list_for_each_entry_safe(iface, tmp_iface, &ifaces, list) { + list_del(&iface->list); + free(iface); + } + return rc; +} + +static void isns_scn_callback(isns_db_t *db, uint32_t bitmap, + isns_object_template_t *node_type, + const char *node_name, const char *dst_name) +{ + log_error("SCN for initiator: %s (Target: %s, Event: %s.)", + dst_name, node_name, isns_event_string(bitmap)); + isns_disc_new_portals(node_name, dst_name); +} + +static void isns_clear_refresh_list(void) +{ + struct isns_refresh_data *refresh_data, *tmp_refresh; + + list_for_each_entry_safe(refresh_data, tmp_refresh, &isns_refresh_list, + list) + isns_free_refresh_data(refresh_data); +} + +static int isns_scn_recv(isns_server_t *svr, isns_socket_t *svr_sock, + int poll_inval) +{ + isns_message_t *msg, *rsp; + struct timeval timeout = { 0, 0 }; + time_t now, then, next_timeout; + unsigned int function; + int rc = 0; + + log_debug(1, "isns_scn_recv"); + + while (!stop_discoveryd) { + /* reap disc/login procs */ + reap_proc(); + /* + * timer func could force a scn registration so check timers + * first + */ + then = isns_run_timers(); + now = time(NULL); + next_timeout = now + 3600; + if (then && then < next_timeout) + next_timeout = then; + + if (isns_register_nodes) { + isns_clear_refresh_list(); + /* + * it is ok to block here, because the server + * should have unregistered us or this is our + * first time registerting. + */ + rc = isns_scn_register(svr_sock, poll_inval); + if (rc) { + sleep(5); + continue; + } + + isns_disc_new_portals(NULL, NULL); + if (!poll_inval) + break; + isns_register_nodes = 0; + /* + * the scn reg may have added timers or changed + * timeout values so recheck. + */ + continue; + } + + /* Determine how long we can sleep */ + if (next_timeout <= now) + continue; + timeout.tv_sec = next_timeout - now; + + if ((msg = isns_recv_message(&timeout)) == NULL) + continue; + + function = isns_message_function(msg); + if (function != ISNS_STATE_CHANGE_NOTIFICATION) { + log_warning("Discarding unexpected %s message", + isns_function_name(function)); + isns_message_release(msg); + continue; + } + + if ((rsp = isns_process_message(svr, msg)) != NULL) { + isns_socket_t *sock = isns_message_socket(msg); + + isns_socket_send(sock, rsp); + isns_message_release(rsp); + } + + isns_message_release(msg); + } + + log_debug(1, "isns_scn_recv done"); + reap_proc(); + return rc; +} + +#define ISNS_EVENTD_PIDFILE ISNS_RUNDIR"/iscsid.isns.pid" +#define ISNS_EVENTD_CTL ISNS_RUNDIR"/iscsid.isns.isnsctl" + +static int isns_eventd(const char *def_iname, char *disc_addr, int port, + int poll_inval) +{ + static isns_socket_t *svr_sock; + isns_server_t *svr; + isns_db_t *db; + struct isns_node_list *tmp_node, *node; + int rc = 0; + + isns_create_node_list(def_iname); + if (list_empty(&isns_initiators)) { + log_error("iSNS registration failed. Initiatorname not set."); + return ISCSI_ERR_INVAL; + } + + /* use def_iname or if not set the first iface's iname for the src */ + node = list_entry(isns_initiators.next, struct isns_node_list, list); + isns_assign_string(&isns_config.ic_source_name, + isns_source_name(node->source)); + isns_config.ic_security = 0; + isns_config.ic_pidfile = ISNS_EVENTD_PIDFILE; + isns_config.ic_control_socket = ISNS_EVENTD_CTL; + + if (discovery_isns_set_servername(disc_addr, port)) { + rc = ISCSI_ERR_NOMEM; + goto fail; + } + + isns_write_pidfile(isns_config.ic_pidfile); + + db = isns_db_open(NULL); + if (!db) { + log_error("iSNS setup failed. Could not create db."); + rc = ISCSI_ERR_NOMEM; + goto fail; + } + svr = isns_create_server(node->source, db, &isns_callback_service_ops); + if (!svr) { + log_error("iSNS setup failed. Could not create server."); + rc = ISCSI_ERR_TRANS; + goto fail; + } + isns_server_set_scn_callback(svr, isns_scn_callback); + + svr_sock = isns_create_server_socket(NULL, NULL, AF_INET6, SOCK_DGRAM); + if (!svr_sock) { + log_error("iSNS setup failed. Could not create server socket."); + rc = ISCSI_ERR_TRANS; + goto fail; + } + + rc = isns_scn_recv(svr, svr_sock, poll_inval); + isns_cancel_refresh_timers(); +fail: + isns_clear_refresh_list(); + + list_for_each_entry_safe(node, tmp_node, &isns_initiators, list) { + list_del(&node->list); + free(node); + } + + if (isns_entity_id) + free(isns_entity_id); + isns_entity_id = NULL; + + discovery_isns_free_servername(); + + if (isns_config.ic_source_name) + free(isns_config.ic_source_name); + isns_config.ic_source_name = NULL; + return rc; +} + +static void start_isns(const char *def_iname, struct discovery_rec *drec, + int poll_inval) +{ + int rc, port = drec->port; + + if (port < 0) + port = ISNS_DEFAULT_PORT; + + rc = isns_eventd(def_iname, drec->address, port, poll_inval); + log_debug(1, "start isns done %d.", rc); + discoveryd_stop(); +} + +/* SendTargets */ +static void __do_st_disc_and_login(struct discovery_rec *drec) +{ + struct list_head rec_list, setup_ifaces; + struct iface_rec *iface, *tmp_iface; + int rc; + + INIT_LIST_HEAD(&rec_list); + INIT_LIST_HEAD(&setup_ifaces); + + /* + * The disc daemon will try again in poll_interval secs + * so no need to retry here + */ + drec->u.sendtargets.reopen_max = 0; + + iface_link_ifaces(&setup_ifaces); + + rc = idbm_bind_ifaces_to_nodes(discovery_sendtargets, drec, + &setup_ifaces, &rec_list); + if (rc) { + log_error("Could not perform SendTargets to %s:%d.", + drec->address, drec->port); + goto free_ifaces; + } + + update_sessions(&rec_list, NULL, NULL); + +free_ifaces: + list_for_each_entry_safe(iface, tmp_iface, &setup_ifaces, list) { + list_del(&iface->list); + free(iface); + } +} + +static void do_st_disc_and_login(const char *def_iname, + struct discovery_rec *drec, int poll_inval) +{ + if (poll_inval < 0) + poll_inval = DISC_DEF_POLL_INVL; + + do { + __do_st_disc_and_login(drec); + if (!poll_inval) + break; + } while (!stop_discoveryd && !sleep(poll_inval)); + + discoveryd_stop(); +} + +static int st_start(void *data, struct discovery_rec *drec) +{ + log_debug(1, "st_start %s:%d %d", drec->address, drec->port, + drec->u.sendtargets.use_discoveryd); + if (!drec->u.sendtargets.use_discoveryd) + return ISCSI_ERR_INVAL; + + fork_disc(NULL, drec, drec->u.sendtargets.discoveryd_poll_inval, + do_st_disc_and_login); + return 0; +} + +static void discoveryd_st_start(void) +{ + idbm_for_each_st_drec(NULL, st_start); +} + +static int isns_start(void *data, struct discovery_rec *drec) +{ + log_debug(1, "isns_start %s:%d %d", drec->address, drec->port, + drec->u.isns.use_discoveryd); + if (!drec->u.isns.use_discoveryd) + return ISCSI_ERR_INVAL; + + fork_disc(data, drec, drec->u.isns.discoveryd_poll_inval, start_isns); + return 0; +} + +static void discoveryd_isns_start(const char *def_iname) +{ + idbm_for_each_isns_drec((void *)def_iname, isns_start); +} + +void discoveryd_start(const char *def_iname) +{ + discoveryd_isns_start(def_iname); + discoveryd_st_start(); +} diff --git a/usr/discoveryd.h b/usr/discoveryd.h new file mode 100644 index 0000000..9437ae9 --- /dev/null +++ b/usr/discoveryd.h @@ -0,0 +1,6 @@ +#ifndef _DISC_DAEMON_H +#define _DISC_DAEMON_H + +extern void discoveryd_start(const char *def_iname); + +#endif diff --git a/usr/ethtool-copy.h b/usr/ethtool-copy.h new file mode 100644 index 0000000..d366c3a --- /dev/null +++ b/usr/ethtool-copy.h @@ -0,0 +1,597 @@ +/* + * ethtool.h: Defines for Linux ethtool. + * + * Copyright (C) 1998 David S. Miller (davem@redhat.com) + * Copyright 2001 Jeff Garzik + * Portions Copyright 2001 Sun Microsystems (thockin@sun.com) + * Portions Copyright 2002 Intel (eli.kupermann@intel.com, + * christopher.leech@intel.com, + * scott.feldman@intel.com) + * Portions Copyright (C) Sun Microsystems 2008 + */ + +#ifndef _LINUX_ETHTOOL_H +#define _LINUX_ETHTOOL_H + +#include + +/* This should work for both 32 and 64 bit userland. */ +struct ethtool_cmd { + __u32 cmd; + __u32 supported; /* Features this interface supports */ + __u32 advertising; /* Features this interface advertises */ + __u16 speed; /* The forced speed, 10Mb, 100Mb, gigabit */ + __u8 duplex; /* Duplex, half or full */ + __u8 port; /* Which connector port */ + __u8 phy_address; + __u8 transceiver; /* Which transceiver to use */ + __u8 autoneg; /* Enable or disable autonegotiation */ + __u8 mdio_support; + __u32 maxtxpkt; /* Tx pkts before generating tx int */ + __u32 maxrxpkt; /* Rx pkts before generating rx int */ + __u16 speed_hi; + __u8 eth_tp_mdix; + __u8 reserved2; + __u32 lp_advertising; /* Features the link partner advertises */ + __u32 reserved[2]; +}; + +static inline void ethtool_cmd_speed_set(struct ethtool_cmd *ep, + __u32 speed) +{ + + ep->speed = (__u16)speed; + ep->speed_hi = (__u16)(speed >> 16); +} + +static inline __u32 ethtool_cmd_speed(struct ethtool_cmd *ep) +{ + return (ep->speed_hi << 16) | ep->speed; +} + +#define ETHTOOL_BUSINFO_LEN 32 +/* these strings are set to whatever the driver author decides... */ +struct ethtool_drvinfo { + __u32 cmd; + char driver[32]; /* driver short name, "tulip", "eepro100" */ + char version[32]; /* driver version string */ + char fw_version[32]; /* firmware version string, if applicable */ + char bus_info[ETHTOOL_BUSINFO_LEN]; /* Bus info for this IF. */ + /* For PCI devices, use pci_name(pci_dev). */ + char reserved1[32]; + char reserved2[12]; + __u32 n_priv_flags; /* number of flags valid in ETHTOOL_GPFLAGS */ + __u32 n_stats; /* number of u64's from ETHTOOL_GSTATS */ + __u32 testinfo_len; + __u32 eedump_len; /* Size of data from ETHTOOL_GEEPROM (bytes) */ + __u32 regdump_len; /* Size of data from ETHTOOL_GREGS (bytes) */ +}; + +#define SOPASS_MAX 6 +/* wake-on-lan settings */ +struct ethtool_wolinfo { + __u32 cmd; + __u32 supported; + __u32 wolopts; + __u8 sopass[SOPASS_MAX]; /* SecureOn(tm) password */ +}; + +/* for passing single values */ +struct ethtool_value { + __u32 cmd; + __u32 data; +}; + +/* for passing big chunks of data */ +struct ethtool_regs { + __u32 cmd; + __u32 version; /* driver-specific, indicates different chips/revs */ + __u32 len; /* bytes */ + __u8 data[0]; +}; + +/* for passing EEPROM chunks */ +struct ethtool_eeprom { + __u32 cmd; + __u32 magic; + __u32 offset; /* in bytes */ + __u32 len; /* in bytes */ + __u8 data[0]; +}; + +/* for configuring coalescing parameters of chip */ +struct ethtool_coalesce { + __u32 cmd; /* ETHTOOL_{G,S}COALESCE */ + + /* How many usecs to delay an RX interrupt after + * a packet arrives. If 0, only rx_max_coalesced_frames + * is used. + */ + __u32 rx_coalesce_usecs; + + /* How many packets to delay an RX interrupt after + * a packet arrives. If 0, only rx_coalesce_usecs is + * used. It is illegal to set both usecs and max frames + * to zero as this would cause RX interrupts to never be + * generated. + */ + __u32 rx_max_coalesced_frames; + + /* Same as above two parameters, except that these values + * apply while an IRQ is being serviced by the host. Not + * all cards support this feature and the values are ignored + * in that case. + */ + __u32 rx_coalesce_usecs_irq; + __u32 rx_max_coalesced_frames_irq; + + /* How many usecs to delay a TX interrupt after + * a packet is sent. If 0, only tx_max_coalesced_frames + * is used. + */ + __u32 tx_coalesce_usecs; + + /* How many packets to delay a TX interrupt after + * a packet is sent. If 0, only tx_coalesce_usecs is + * used. It is illegal to set both usecs and max frames + * to zero as this would cause TX interrupts to never be + * generated. + */ + __u32 tx_max_coalesced_frames; + + /* Same as above two parameters, except that these values + * apply while an IRQ is being serviced by the host. Not + * all cards support this feature and the values are ignored + * in that case. + */ + __u32 tx_coalesce_usecs_irq; + __u32 tx_max_coalesced_frames_irq; + + /* How many usecs to delay in-memory statistics + * block updates. Some drivers do not have an in-memory + * statistic block, and in such cases this value is ignored. + * This value must not be zero. + */ + __u32 stats_block_coalesce_usecs; + + /* Adaptive RX/TX coalescing is an algorithm implemented by + * some drivers to improve latency under low packet rates and + * improve throughput under high packet rates. Some drivers + * only implement one of RX or TX adaptive coalescing. Anything + * not implemented by the driver causes these values to be + * silently ignored. + */ + __u32 use_adaptive_rx_coalesce; + __u32 use_adaptive_tx_coalesce; + + /* When the packet rate (measured in packets per second) + * is below pkt_rate_low, the {rx,tx}_*_low parameters are + * used. + */ + __u32 pkt_rate_low; + __u32 rx_coalesce_usecs_low; + __u32 rx_max_coalesced_frames_low; + __u32 tx_coalesce_usecs_low; + __u32 tx_max_coalesced_frames_low; + + /* When the packet rate is below pkt_rate_high but above + * pkt_rate_low (both measured in packets per second) the + * normal {rx,tx}_* coalescing parameters are used. + */ + + /* When the packet rate is (measured in packets per second) + * is above pkt_rate_high, the {rx,tx}_*_high parameters are + * used. + */ + __u32 pkt_rate_high; + __u32 rx_coalesce_usecs_high; + __u32 rx_max_coalesced_frames_high; + __u32 tx_coalesce_usecs_high; + __u32 tx_max_coalesced_frames_high; + + /* How often to do adaptive coalescing packet rate sampling, + * measured in seconds. Must not be zero. + */ + __u32 rate_sample_interval; +}; + +/* for configuring RX/TX ring parameters */ +struct ethtool_ringparam { + __u32 cmd; /* ETHTOOL_{G,S}RINGPARAM */ + + /* Read only attributes. These indicate the maximum number + * of pending RX/TX ring entries the driver will allow the + * user to set. + */ + __u32 rx_max_pending; + __u32 rx_mini_max_pending; + __u32 rx_jumbo_max_pending; + __u32 tx_max_pending; + + /* Values changeable by the user. The valid values are + * in the range 1 to the "*_max_pending" counterpart above. + */ + __u32 rx_pending; + __u32 rx_mini_pending; + __u32 rx_jumbo_pending; + __u32 tx_pending; +}; + +/* for configuring link flow control parameters */ +struct ethtool_pauseparam { + __u32 cmd; /* ETHTOOL_{G,S}PAUSEPARAM */ + + /* If the link is being auto-negotiated (via ethtool_cmd.autoneg + * being true) the user may set 'autonet' here non-zero to have the + * pause parameters be auto-negotiated too. In such a case, the + * {rx,tx}_pause values below determine what capabilities are + * advertised. + * + * If 'autoneg' is zero or the link is not being auto-negotiated, + * then {rx,tx}_pause force the driver to use/not-use pause + * flow control. + */ + __u32 autoneg; + __u32 rx_pause; + __u32 tx_pause; +}; + +#define ETH_GSTRING_LEN 32 +enum ethtool_stringset { + ETH_SS_TEST = 0, + ETH_SS_STATS, + ETH_SS_PRIV_FLAGS, +}; + +/* for passing string sets for data tagging */ +struct ethtool_gstrings { + __u32 cmd; /* ETHTOOL_GSTRINGS */ + __u32 string_set; /* string set id e.c. ETH_SS_TEST, etc*/ + __u32 len; /* number of strings in the string set */ + __u8 data[0]; +}; + +enum ethtool_test_flags { + ETH_TEST_FL_OFFLINE = (1 << 0), /* online / offline */ + ETH_TEST_FL_FAILED = (1 << 1), /* test passed / failed */ +}; + +/* for requesting NIC test and getting results*/ +struct ethtool_test { + __u32 cmd; /* ETHTOOL_TEST */ + __u32 flags; /* ETH_TEST_FL_xxx */ + __u32 reserved; + __u32 len; /* result length, in number of u64 elements */ + __u64 data[0]; +}; + +/* for dumping NIC-specific statistics */ +struct ethtool_stats { + __u32 cmd; /* ETHTOOL_GSTATS */ + __u32 n_stats; /* number of u64's being returned */ + __u64 data[0]; +}; + +struct ethtool_perm_addr { + __u32 cmd; /* ETHTOOL_GPERMADDR */ + __u32 size; + __u8 data[0]; +}; + +/* boolean flags controlling per-interface behavior characteristics. + * When reading, the flag indicates whether or not a certain behavior + * is enabled/present. When writing, the flag indicates whether + * or not the driver should turn on (set) or off (clear) a behavior. + * + * Some behaviors may read-only (unconditionally absent or present). + * If such is the case, return EINVAL in the set-flags operation if the + * flag differs from the read-only value. + */ +enum ethtool_flags { + ETH_FLAG_LRO = (1 << 15), /* LRO is enabled */ +}; + +/* The following structures are for supporting RX network flow + * classification configuration. Note, all multibyte fields, e.g., + * ip4src, ip4dst, psrc, pdst, spi, etc. are expected to be in network + * byte order. + */ +struct ethtool_tcpip4_spec { + __be32 ip4src; + __be32 ip4dst; + __be16 psrc; + __be16 pdst; + __u8 tos; +}; + +struct ethtool_ah_espip4_spec { + __be32 ip4src; + __be32 ip4dst; + __be32 spi; + __u8 tos; +}; + +struct ethtool_rawip4_spec { + __be32 ip4src; + __be32 ip4dst; + __u8 hdata[64]; +}; + +struct ethtool_ether_spec { + __be16 ether_type; + __u8 frame_size; + __u8 eframe[16]; +}; + +#define ETH_RX_NFC_IP4 1 +#define ETH_RX_NFC_IP6 2 + +struct ethtool_usrip4_spec { + __be32 ip4src; + __be32 ip4dst; + __be32 l4_4_bytes; + __u8 tos; + __u8 ip_ver; + __u8 proto; +}; + +struct ethtool_rx_flow_spec { + __u32 flow_type; + union { + struct ethtool_tcpip4_spec tcp_ip4_spec; + struct ethtool_tcpip4_spec udp_ip4_spec; + struct ethtool_tcpip4_spec sctp_ip4_spec; + struct ethtool_ah_espip4_spec ah_ip4_spec; + struct ethtool_ah_espip4_spec esp_ip4_spec; + struct ethtool_rawip4_spec raw_ip4_spec; + struct ethtool_ether_spec ether_spec; + struct ethtool_usrip4_spec usr_ip4_spec; + __u8 hdata[64]; + } h_u, m_u; /* entry, mask */ + __u64 ring_cookie; + __u32 location; +}; + +struct ethtool_rxnfc { + __u32 cmd; + __u32 flow_type; + /* The rx flow hash value or the rule DB size */ + __u64 data; + struct ethtool_rx_flow_spec fs; + __u32 rule_cnt; + __u32 rule_locs[0]; +}; + +#define ETHTOOL_FLASH_MAX_FILENAME 128 +enum ethtool_flash_op_type { + ETHTOOL_FLASH_ALL_REGIONS = 0, +}; + +/* for passing firmware flashing related parameters */ +struct ethtool_flash { + __u32 cmd; + __u32 region; + char data[ETHTOOL_FLASH_MAX_FILENAME]; +}; + + +/* CMDs currently supported */ +#define ETHTOOL_GSET 0x00000001 /* Get settings. */ +#define ETHTOOL_SSET 0x00000002 /* Set settings. */ +#define ETHTOOL_GDRVINFO 0x00000003 /* Get driver info. */ +#define ETHTOOL_GREGS 0x00000004 /* Get NIC registers. */ +#define ETHTOOL_GWOL 0x00000005 /* Get wake-on-lan options. */ +#define ETHTOOL_SWOL 0x00000006 /* Set wake-on-lan options. */ +#define ETHTOOL_GMSGLVL 0x00000007 /* Get driver message level */ +#define ETHTOOL_SMSGLVL 0x00000008 /* Set driver msg level. */ +#define ETHTOOL_NWAY_RST 0x00000009 /* Restart autonegotiation. */ +#define ETHTOOL_GLINK 0x0000000a /* Get link status (ethtool_value) */ +#define ETHTOOL_GEEPROM 0x0000000b /* Get EEPROM data */ +#define ETHTOOL_SEEPROM 0x0000000c /* Set EEPROM data. */ +#define ETHTOOL_GCOALESCE 0x0000000e /* Get coalesce config */ +#define ETHTOOL_SCOALESCE 0x0000000f /* Set coalesce config. */ +#define ETHTOOL_GRINGPARAM 0x00000010 /* Get ring parameters */ +#define ETHTOOL_SRINGPARAM 0x00000011 /* Set ring parameters. */ +#define ETHTOOL_GPAUSEPARAM 0x00000012 /* Get pause parameters */ +#define ETHTOOL_SPAUSEPARAM 0x00000013 /* Set pause parameters. */ +#define ETHTOOL_GRXCSUM 0x00000014 /* Get RX hw csum enable (ethtool_value) */ +#define ETHTOOL_SRXCSUM 0x00000015 /* Set RX hw csum enable (ethtool_value) */ +#define ETHTOOL_GTXCSUM 0x00000016 /* Get TX hw csum enable (ethtool_value) */ +#define ETHTOOL_STXCSUM 0x00000017 /* Set TX hw csum enable (ethtool_value) */ +#define ETHTOOL_GSG 0x00000018 /* Get scatter-gather enable + * (ethtool_value) */ +#define ETHTOOL_SSG 0x00000019 /* Set scatter-gather enable + * (ethtool_value). */ +#define ETHTOOL_TEST 0x0000001a /* execute NIC self-test. */ +#define ETHTOOL_GSTRINGS 0x0000001b /* get specified string set */ +#define ETHTOOL_PHYS_ID 0x0000001c /* identify the NIC */ +#define ETHTOOL_GSTATS 0x0000001d /* get NIC-specific statistics */ +#define ETHTOOL_GTSO 0x0000001e /* Get TSO enable (ethtool_value) */ +#define ETHTOOL_STSO 0x0000001f /* Set TSO enable (ethtool_value) */ +#define ETHTOOL_GPERMADDR 0x00000020 /* Get permanent hardware address */ +#define ETHTOOL_GUFO 0x00000021 /* Get UFO enable (ethtool_value) */ +#define ETHTOOL_SUFO 0x00000022 /* Set UFO enable (ethtool_value) */ +#define ETHTOOL_GGSO 0x00000023 /* Get GSO enable (ethtool_value) */ +#define ETHTOOL_SGSO 0x00000024 /* Set GSO enable (ethtool_value) */ +#define ETHTOOL_GFLAGS 0x00000025 /* Get flags bitmap(ethtool_value) */ +#define ETHTOOL_SFLAGS 0x00000026 /* Set flags bitmap(ethtool_value) */ +#define ETHTOOL_GPFLAGS 0x00000027 /* Get driver-private flags bitmap */ +#define ETHTOOL_SPFLAGS 0x00000028 /* Set driver-private flags bitmap */ + +#define ETHTOOL_GRXFH 0x00000029 /* Get RX flow hash configuration */ +#define ETHTOOL_SRXFH 0x0000002a /* Set RX flow hash configuration */ +#define ETHTOOL_GGRO 0x0000002b /* Get GRO enable (ethtool_value) */ +#define ETHTOOL_SGRO 0x0000002c /* Set GRO enable (ethtool_value) */ +#define ETHTOOL_GRXRINGS 0x0000002d /* Get RX rings available for LB */ +#define ETHTOOL_GRXCLSRLCNT 0x0000002e /* Get RX class rule count */ +#define ETHTOOL_GRXCLSRULE 0x0000002f /* Get RX classification rule */ +#define ETHTOOL_GRXCLSRLALL 0x00000030 /* Get all RX classification rule */ +#define ETHTOOL_SRXCLSRLDEL 0x00000031 /* Delete RX classification rule */ +#define ETHTOOL_SRXCLSRLINS 0x00000032 /* Insert RX classification rule */ +#define ETHTOOL_FLASHDEV 0x00000033 /* Flash firmware to device */ +#define ETHTOOL_RESET 0x00000034 /* Reset hardware */ + +/* compatibility with older code */ +#define SPARC_ETH_GSET ETHTOOL_GSET +#define SPARC_ETH_SSET ETHTOOL_SSET + +/* Indicates what features are supported by the interface. */ +#define SUPPORTED_10baseT_Half (1 << 0) +#define SUPPORTED_10baseT_Full (1 << 1) +#define SUPPORTED_100baseT_Half (1 << 2) +#define SUPPORTED_100baseT_Full (1 << 3) +#define SUPPORTED_1000baseT_Half (1 << 4) +#define SUPPORTED_1000baseT_Full (1 << 5) +#define SUPPORTED_Autoneg (1 << 6) +#define SUPPORTED_TP (1 << 7) +#define SUPPORTED_AUI (1 << 8) +#define SUPPORTED_MII (1 << 9) +#define SUPPORTED_FIBRE (1 << 10) +#define SUPPORTED_BNC (1 << 11) +#define SUPPORTED_10000baseT_Full (1 << 12) +#define SUPPORTED_Pause (1 << 13) +#define SUPPORTED_Asym_Pause (1 << 14) +#define SUPPORTED_2500baseX_Full (1 << 15) +#define SUPPORTED_Backplane (1 << 16) +#define SUPPORTED_1000baseKX_Full (1 << 17) +#define SUPPORTED_10000baseKX4_Full (1 << 18) +#define SUPPORTED_10000baseKR_Full (1 << 19) +#define SUPPORTED_10000baseR_FEC (1 << 20) + +/* Indicates what features are advertised by the interface. */ +#define ADVERTISED_10baseT_Half (1 << 0) +#define ADVERTISED_10baseT_Full (1 << 1) +#define ADVERTISED_100baseT_Half (1 << 2) +#define ADVERTISED_100baseT_Full (1 << 3) +#define ADVERTISED_1000baseT_Half (1 << 4) +#define ADVERTISED_1000baseT_Full (1 << 5) +#define ADVERTISED_Autoneg (1 << 6) +#define ADVERTISED_TP (1 << 7) +#define ADVERTISED_AUI (1 << 8) +#define ADVERTISED_MII (1 << 9) +#define ADVERTISED_FIBRE (1 << 10) +#define ADVERTISED_BNC (1 << 11) +#define ADVERTISED_10000baseT_Full (1 << 12) +#define ADVERTISED_Pause (1 << 13) +#define ADVERTISED_Asym_Pause (1 << 14) +#define ADVERTISED_2500baseX_Full (1 << 15) +#define ADVERTISED_Backplane (1 << 16) +#define ADVERTISED_1000baseKX_Full (1 << 17) +#define ADVERTISED_10000baseKX4_Full (1 << 18) +#define ADVERTISED_10000baseKR_Full (1 << 19) +#define ADVERTISED_10000baseR_FEC (1 << 20) + +/* The following are all involved in forcing a particular link + * mode for the device for setting things. When getting the + * devices settings, these indicate the current mode and whether + * it was foced up into this mode or autonegotiated. + */ + +/* The forced speed, 10Mb, 100Mb, gigabit, 2.5Gb, 10GbE. */ +#define SPEED_10 10 +#define SPEED_100 100 +#define SPEED_1000 1000 +#define SPEED_2500 2500 +#define SPEED_10000 10000 + +/* Duplex, half or full. */ +#define DUPLEX_HALF 0x00 +#define DUPLEX_FULL 0x01 + +/* Which connector port. */ +#define PORT_TP 0x00 +#define PORT_AUI 0x01 +#define PORT_MII 0x02 +#define PORT_FIBRE 0x03 +#define PORT_BNC 0x04 +#define PORT_DA 0x05 +#define PORT_NONE 0xef +#define PORT_OTHER 0xff + +/* Which transceiver to use. */ +#define XCVR_INTERNAL 0x00 +#define XCVR_EXTERNAL 0x01 +#define XCVR_DUMMY1 0x02 +#define XCVR_DUMMY2 0x03 +#define XCVR_DUMMY3 0x04 + +/* Enable or disable autonegotiation. If this is set to enable, + * the forced link modes above are completely ignored. + */ +#define AUTONEG_DISABLE 0x00 +#define AUTONEG_ENABLE 0x01 + +/* Mode MDI or MDI-X */ +#define ETH_TP_MDI_INVALID 0x00 +#define ETH_TP_MDI 0x01 +#define ETH_TP_MDI_X 0x02 + +/* Wake-On-Lan options. */ +#define WAKE_PHY (1 << 0) +#define WAKE_UCAST (1 << 1) +#define WAKE_MCAST (1 << 2) +#define WAKE_BCAST (1 << 3) +#define WAKE_ARP (1 << 4) +#define WAKE_MAGIC (1 << 5) +#define WAKE_MAGICSECURE (1 << 6) /* only meaningful if WAKE_MAGIC */ + +/* L3-L4 network traffic flow types */ +#define TCP_V4_FLOW 0x01 +#define UDP_V4_FLOW 0x02 +#define SCTP_V4_FLOW 0x03 +#define AH_ESP_V4_FLOW 0x04 +#define TCP_V6_FLOW 0x05 +#define UDP_V6_FLOW 0x06 +#define SCTP_V6_FLOW 0x07 +#define AH_ESP_V6_FLOW 0x08 +#define AH_V4_FLOW 0x09 +#define ESP_V4_FLOW 0x0a +#define AH_V6_FLOW 0x0b +#define ESP_V6_FLOW 0x0c +#define IP_USER_FLOW 0x0d +#define IPV4_FLOW 0x10 +#define IPV6_FLOW 0x11 + +/* L3-L4 network traffic flow hash options */ +#define RXH_L2DA (1 << 1) +#define RXH_VLAN (1 << 2) +#define RXH_L3_PROTO (1 << 3) +#define RXH_IP_SRC (1 << 4) +#define RXH_IP_DST (1 << 5) +#define RXH_L4_B_0_1 (1 << 6) /* src port in case of TCP/UDP/SCTP */ +#define RXH_L4_B_2_3 (1 << 7) /* dst port in case of TCP/UDP/SCTP */ +#define RXH_DISCARD (1 << 31) + +#define RX_CLS_FLOW_DISC 0xffffffffffffffffULL + +/* Reset flags */ +/* The reset() operation must clear the flags for the components which + * were actually reset. On successful return, the flags indicate the + * components which were not reset, either because they do not exist + * in the hardware or because they cannot be reset independently. The + * driver must never reset any components that were not requested. + */ +enum ethtool_reset_flags { + /* These flags represent components dedicated to the interface + * the command is addressed to. Shift any flag left by + * ETH_RESET_SHARED_SHIFT to reset a shared component of the + * same type. + */ + ETH_RESET_MGMT = 1 << 0, /* Management processor */ + ETH_RESET_IRQ = 1 << 1, /* Interrupt requester */ + ETH_RESET_DMA = 1 << 2, /* DMA engine */ + ETH_RESET_FILTER = 1 << 3, /* Filtering/flow direction */ + ETH_RESET_OFFLOAD = 1 << 4, /* Protocol offload */ + ETH_RESET_MAC = 1 << 5, /* Media access controller */ + ETH_RESET_PHY = 1 << 6, /* Transceiver/PHY */ + ETH_RESET_RAM = 1 << 7, /* RAM shared between + * multiple components */ + + ETH_RESET_DEDICATED = 0x0000ffff, /* All components dedicated to + * this interface */ + ETH_RESET_ALL = 0xffffffff, /* All components used by this + * interface, even if shared */ +}; +#define ETH_RESET_SHARED_SHIFT 16 + +#endif /* _LINUX_ETHTOOL_H */ diff --git a/usr/event_poll.c b/usr/event_poll.c new file mode 100644 index 0000000..4cf4ce2 --- /dev/null +++ b/usr/event_poll.c @@ -0,0 +1,236 @@ +/* + * iSCSI daemon event handler + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * Copyright (C) 2006 Mike Christie + * Copyright (C) 2006 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@googlegroups.com + * + * Originally based on: + * (C) 2004 FUJITA Tomonori + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#include +#include +#include +#include +#include +#include +#include + +#include "mgmt_ipc.h" +#include "iscsi_ipc.h" +#include "sysfs.h" +#include "iscsid.h" +#include "log.h" +#include "iscsi_ipc.h" +#include "actor.h" +#include "initiator.h" +#include "iscsi_err.h" + +static unsigned int reap_count; + +/* track pid of reload fork, while running */ +static pid_t reload_pid = 0; +static void (*reload_callback)(void); + +#define REAP_WAKEUP 1000 /* in millisecs */ + +void reap_inc(void) +{ + reap_count++; +} + +/* track the reload process to be reaped, when done */ +void reap_track_reload_process(pid_t reload_proc_pid, void (*reload_done_callback)(void)) +{ + reload_pid = reload_proc_pid; + reload_callback = reload_done_callback; + reap_inc(); +} + +void reap_proc(void) +{ + int i, max_reaps; + pid_t rc; + + /* + * We don't really need reap_count, but calling wait() all the + * time seems excessive. + */ + max_reaps = reap_count; + for (i = 0; i < max_reaps; i++) { + rc = waitpid(0, NULL, WNOHANG); + if (rc > 0) { + if (rc == reload_pid) { + log_debug(6, "reaped reload process"); + reload_callback(); + } + reap_count--; + log_debug(6, "reaped pid %d, reap_count now %d", + (int)rc, reap_count); + } + } +} + +static LIST_HEAD(shutdown_callbacks); + +struct shutdown_callback { + struct list_head list; + pid_t pid; +}; + +int shutdown_callback(pid_t pid) +{ + struct shutdown_callback *cb; + + cb = calloc(1, sizeof(*cb)); + if (!cb) + return ENOMEM; + + INIT_LIST_HEAD(&cb->list); + cb->pid = pid; + log_debug(1, "adding %d for shutdown cb", pid); + list_add_tail(&cb->list, &shutdown_callbacks); + return 0; +} + +static void shutdown_notify_pids(void) +{ + struct shutdown_callback *cb; + + list_for_each_entry(cb, &shutdown_callbacks, list) { + log_debug(1, "Killing %d", cb->pid); + kill(cb->pid, SIGTERM); + } +} + +static int shutdown_wait_pids(void) +{ + struct shutdown_callback *cb, *tmp; + + list_for_each_entry_safe(cb, tmp, &shutdown_callbacks, list) { + /* + * the proc reaper could clean it up, so wait for any + * sign that it is gone. + */ + if (waitpid(cb->pid, NULL, WNOHANG)) { + log_debug(1, "%d done", cb->pid); + list_del(&cb->list); + free(cb); + } + } + + return list_empty(&shutdown_callbacks); +} + +#define POLL_CTRL 0 +#define POLL_IPC 1 +#define POLL_ALARM 2 +#define POLL_MAX 3 + +static volatile int event_loop_stop; +static queue_task_t *shutdown_qtask; + +void event_loop_exit(queue_task_t *qtask) +{ + shutdown_qtask = qtask; + event_loop_stop = 1; +} + +void event_loop(struct iscsi_ipc *ipc, int control_fd, int mgmt_ipc_fd) +{ + struct pollfd poll_array[POLL_MAX]; + int res, has_shutdown_children = 0; + sigset_t sigset; + int sig_fd; + + /* Mask off SIGALRM so we can recv it via signalfd */ + sigemptyset(&sigset); + sigaddset(&sigset, SIGALRM); + sigprocmask(SIG_SETMASK, &sigset, NULL); + + sig_fd = signalfd(-1, &sigset, SFD_NONBLOCK); + if (sig_fd == -1) { + log_error("signalfd failed: %m"); + return; + } + + poll_array[POLL_CTRL].fd = control_fd; + poll_array[POLL_CTRL].events = POLLIN; + poll_array[POLL_IPC].fd = mgmt_ipc_fd; + poll_array[POLL_IPC].events = POLLIN; + poll_array[POLL_ALARM].fd = sig_fd; + poll_array[POLL_ALARM].events = POLLIN; + + event_loop_stop = 0; + while (1) { + if (event_loop_stop) { + if (!has_shutdown_children) { + has_shutdown_children = 1; + shutdown_notify_pids(); + } + if (shutdown_wait_pids()) + break; + } + + /* Runs actors and may set alarm for future actors */ + actor_poll(); + + res = poll(poll_array, POLL_MAX, reap_count ? REAP_WAKEUP : -1); + + if (res > 0) { + log_debug(6, "poll result %d", res); + if (poll_array[POLL_CTRL].revents) + ipc->ctldev_handle(); + + if (poll_array[POLL_IPC].revents) + mgmt_ipc_handle(mgmt_ipc_fd); + + if (poll_array[POLL_ALARM].revents) { + struct signalfd_siginfo si; + + if (read(sig_fd, &si, sizeof(si)) == -1) { + log_error("got sigfd read() error, errno (%d), " + "exiting", errno); + break; + } else { + log_debug(1, "Poll was woken by an alarm"); + } + } + } else if (res < 0) { + if (errno == EINTR) { + log_debug(1, "event_loop interrupted"); + } else { + log_error("got poll() error (%d), errno (%d), " + "exiting", res, errno); + break; + } + } + + reap_proc(); + + /* + * flush sysfs cache since kernel objs may + * have changed as a result of handling op + */ + sysfs_cleanup(); + } + + if (shutdown_qtask) + mgmt_ipc_write_rsp(shutdown_qtask, ISCSI_SUCCESS); + + close(sig_fd); + sigprocmask(SIG_UNBLOCK, &sigset, NULL); +} diff --git a/usr/event_poll.h b/usr/event_poll.h new file mode 100644 index 0000000..f23132b --- /dev/null +++ b/usr/event_poll.h @@ -0,0 +1,32 @@ +/* + * iSCSI event poll/loop + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#ifndef EVENT_POLL_H +#define EVENT_POLL_H + +struct iscsi_ipc; +struct queue_task; + +int shutdown_callback(pid_t pid); +void reap_proc(void); +void reap_inc(void); +void reap_track_reload_process(pid_t realod_proc_pid, void (*reload_done_callback)(void)); +void event_loop(struct iscsi_ipc *ipc, int control_fd, int mgmt_ipc_fd); +void event_loop_exit(struct queue_task *qtask); + +#endif diff --git a/usr/flashnode.c b/usr/flashnode.c new file mode 100644 index 0000000..fe5ab57 --- /dev/null +++ b/usr/flashnode.c @@ -0,0 +1,615 @@ +/* + * iSCSI flashnode helpers + * + * Copyright (C) 2013 QLogic Corporation. + * Maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "idbm.h" +#include "iscsi_util.h" +#include "transport.h" +#include "iscsi_sysfs.h" +#include "list.h" +#include "sysdeps.h" +#include "idbm_fields.h" +#include "iscsi_err.h" +#include "iscsi_ipc.h" +#include "iscsi_netlink.h" +#include "flashnode.h" +#include "iscsi_settings.h" + +char key[NAME_MAXVAL]; + +char *to_key(const char *fmt) +{ + int i = 0; + memset(key, 0, sizeof(key)); + sprintf(key, fmt, i); + return key; +} + +int flashnode_info_print_flat(void *data, struct flashnode_rec *fnode, + uint32_t host_no, uint32_t flashnode_idx) +{ + printf("%s: [%d] ", fnode->transport_name, flashnode_idx); + if (!strlen((char *)fnode->conn[0].ipaddress)) + printf("%s:", UNKNOWN_VALUE); + else if (strchr((char *)fnode->conn[0].ipaddress, '.')) + printf("%s:", fnode->conn[0].ipaddress); + else + printf("[%s]:", fnode->conn[0].ipaddress); + + if (!fnode->conn[0].port) + printf("%s,", UNKNOWN_VALUE); + else + printf("%u,", fnode->conn[0].port); + + printf("%u ", fnode->sess.tpgt); + + if (!strlen(fnode->sess.targetname)) + printf("%s\n", UNKNOWN_VALUE); + else + printf("%s\n", fnode->sess.targetname); + + return 0; +} + +static int flashnode_fill_isid(struct flashnode_rec *fnode, struct iovec *iov) +{ + struct iscsi_flashnode_param_info *fnode_param; + struct nlattr *attr; + int len; + uint8_t isid[6]; + + len = sizeof(struct iscsi_flashnode_param_info) + 6; + iov->iov_base = iscsi_nla_alloc(ISCSI_FLASHNODE_ISID, len); + if (!iov->iov_base) + return 1; + + attr = iov->iov_base; + iov->iov_len = NLA_ALIGN(attr->nla_len); + + fnode_param = (struct iscsi_flashnode_param_info *)ISCSI_NLA_DATA(attr); + fnode_param->param = ISCSI_FLASHNODE_ISID; + fnode_param->len = 6; + + sscanf(fnode->sess.isid, "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", + &isid[0], &isid[1], &isid[2], &isid[3], &isid[4], &isid[5]); + + memcpy(fnode_param->value, isid, fnode_param->len); + return 0; +} + +static int flashnode_fill_ipv4_addr(struct flashnode_rec *fnode, + struct iovec *iov, int param_type) +{ + struct iscsi_flashnode_param_info *fnode_param; + struct nlattr *attr; + int len; + int rc; + + len = sizeof(struct iscsi_flashnode_param_info) + 4; + iov->iov_base = iscsi_nla_alloc(param_type, len); + if (!iov->iov_base) + return 1; + + attr = iov->iov_base; + iov->iov_len = NLA_ALIGN(attr->nla_len); + + fnode_param = (struct iscsi_flashnode_param_info *)ISCSI_NLA_DATA(attr); + fnode_param->param = param_type; + fnode_param->len = 4; + + switch (param_type) { + case ISCSI_FLASHNODE_IPADDR: + rc = inet_pton(AF_INET, (char *)fnode->conn[0].ipaddress, + fnode_param->value); + break; + case ISCSI_FLASHNODE_REDIRECT_IPADDR: + rc = inet_pton(AF_INET, (char *)fnode->conn[0].redirect_ipaddr, + fnode_param->value); + break; + default: + goto free; + } + + if (rc <= 0) + goto free; + + return 0; + +free: + free(iov->iov_base); + iov->iov_base = NULL; + iov->iov_len = 0; + return 1; +} + +static int flashnode_fill_ipv6_addr(struct flashnode_rec *fnode, + struct iovec *iov, int param_type) +{ + struct iscsi_flashnode_param_info *fnode_param; + struct nlattr *attr; + int len; + int rc; + + len = sizeof(struct iscsi_flashnode_param_info) + 16; + iov->iov_base = iscsi_nla_alloc(param_type, len); + if (!iov->iov_base) + return 1; + + attr = iov->iov_base; + iov->iov_len = NLA_ALIGN(attr->nla_len); + + fnode_param = (struct iscsi_flashnode_param_info *)ISCSI_NLA_DATA(attr); + fnode_param->param = param_type; + fnode_param->len = 16; + + switch (param_type) { + case ISCSI_FLASHNODE_IPADDR: + rc = inet_pton(AF_INET6, (char *)fnode->conn[0].ipaddress, + fnode_param->value); + break; + case ISCSI_FLASHNODE_REDIRECT_IPADDR: + rc = inet_pton(AF_INET6, (char *)fnode->conn[0].redirect_ipaddr, + fnode_param->value); + break; + case ISCSI_FLASHNODE_LINK_LOCAL_IPV6: + rc = inet_pton(AF_INET6, (char *)fnode->conn[0].link_local_ipv6, + fnode_param->value); + break; + default: + goto free; + } + + if (rc <= 0) + goto free; + + return 0; + +free: + free(iov->iov_base); + iov->iov_base = NULL; + iov->iov_len = 0; + return 1; +} + +static int flashnode_fill_ipaddr(struct flashnode_rec *fnode, struct iovec *iov, + int param_type) +{ + int rc = 0; + + if (!strncmp(fnode->sess.portal_type, "ipv4", 4)) + rc = flashnode_fill_ipv4_addr(fnode, iov, param_type); + else + rc = flashnode_fill_ipv6_addr(fnode, iov, param_type); + + return rc; +} + +static int flashnode_fill_uint8(struct flashnode_rec *fnode, struct iovec *iov, + int param_type, uint8_t val) +{ + struct iscsi_flashnode_param_info *fnode_param; + struct nlattr *attr; + int len; + + len = sizeof(struct iscsi_flashnode_param_info) + 1; + iov->iov_base = iscsi_nla_alloc(param_type, len); + if (!iov->iov_base) + return 1; + + attr = iov->iov_base; + iov->iov_len = NLA_ALIGN(attr->nla_len); + + fnode_param = (struct iscsi_flashnode_param_info *)ISCSI_NLA_DATA(attr); + fnode_param->param = param_type; + fnode_param->len = 1; + fnode_param->value[0] = val; + return 0; +} + +static int flashnode_fill_uint16(struct flashnode_rec *fnode, struct iovec *iov, + int param_type, uint16_t val) +{ + struct iscsi_flashnode_param_info *fnode_param; + struct nlattr *attr; + int len; + + len = sizeof(struct iscsi_flashnode_param_info) + 2; + iov->iov_base = iscsi_nla_alloc(param_type, len); + if (!iov->iov_base) + return 1; + + attr = iov->iov_base; + iov->iov_len = NLA_ALIGN(attr->nla_len); + + fnode_param = (struct iscsi_flashnode_param_info *)ISCSI_NLA_DATA(attr); + fnode_param->param = param_type; + fnode_param->len = 2; + memcpy(fnode_param->value, &val, fnode_param->len); + return 0; +} + +static int flashnode_fill_uint32(struct flashnode_rec *fnode, struct iovec *iov, + int param_type, uint32_t val) +{ + struct iscsi_flashnode_param_info *fnode_param; + struct nlattr *attr; + int len; + + len = sizeof(struct iscsi_flashnode_param_info) + 4; + iov->iov_base = iscsi_nla_alloc(param_type, len); + if (!iov->iov_base) + return 1; + + attr = iov->iov_base; + iov->iov_len = NLA_ALIGN(attr->nla_len); + + fnode_param = (struct iscsi_flashnode_param_info *)ISCSI_NLA_DATA(attr); + fnode_param->param = param_type; + fnode_param->len = 4; + memcpy(fnode_param->value, &val, fnode_param->len); + return 0; +} + +static int flashnode_fill_str(struct flashnode_rec *fnode, struct iovec *iov, + int param_type, char *buf, int buflen) +{ + struct iscsi_flashnode_param_info *fnode_param; + struct nlattr *attr; + int len; + + len = sizeof(struct iscsi_flashnode_param_info) + buflen; + iov->iov_base = iscsi_nla_alloc(param_type, len); + if (!iov->iov_base) + return 1; + + attr = iov->iov_base; + iov->iov_len = NLA_ALIGN(attr->nla_len); + + fnode_param = (struct iscsi_flashnode_param_info *)ISCSI_NLA_DATA(attr); + fnode_param->param = param_type; + fnode_param->len = buflen; + memcpy(fnode_param->value, buf, fnode_param->len); + return 0; +} + +int flashnode_build_config(struct list_head *params, + struct flashnode_rec *fnode, struct iovec *iovs) +{ + struct user_param *param; + struct iovec *iov = NULL; + int count = 0; + int port = 3260; + + /* start at 2, because 0 is for nlmsghdr and 1 for event */ + iov = iovs + 2; + + list_for_each_entry(param, params, list) { + if (!strcmp(param->name, FLASHNODE_SESS_AUTO_SND_TGT_DISABLE)) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_AUTO_SND_TGT_DISABLE, + fnode->sess.auto_snd_tgt_disable)) + count++; + } else if (!strcmp(param->name, + FLASHNODE_SESS_DISCOVERY_SESS)) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_DISCOVERY_SESS, + fnode->sess.discovery_session)) + count++; + } else if (!strcmp(param->name, FLASHNODE_SESS_ENTRY_EN)) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_ENTRY_EN, + fnode->sess.entry_enable)) + count++; + } else if (!strcmp(param->name, FLASHNODE_SESS_IMM_DATA_EN)) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_IMM_DATA_EN, + fnode->sess.immediate_data)) + count++; + } else if (!strcmp(param->name, + FLASHNODE_SESS_INITIAL_R2T_EN)) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_INITIAL_R2T_EN, + fnode->sess.initial_r2t)) + count++; + } else if (!strcmp(param->name, + FLASHNODE_SESS_DATASEQ_INORDER)) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_DATASEQ_INORDER, + fnode->sess.data_seq_in_order)) + count++; + } else if (!strcmp(param->name, FLASHNODE_SESS_PDU_INORDER)) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_PDU_INORDER, + fnode->sess.data_pdu_in_order)) + count++; + } else if (!strcmp(param->name, FLASHNODE_SESS_CHAP_AUTH_EN)) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_CHAP_AUTH_EN, + fnode->sess.chap_auth_en)) + count++; + } else if (!strcmp(param->name, + FLASHNODE_SESS_DISCOVERY_LOGOUT_EN)) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_DISCOVERY_LOGOUT_EN, + fnode->sess.discovery_logout_en)) + count++; + } else if (!strcmp(param->name, FLASHNODE_SESS_BIDI_CHAP_EN )) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_BIDI_CHAP_EN, + fnode->sess.bidi_chap_en)) + count++; + } else if (!strcmp(param->name, + FLASHNODE_SESS_DISCOVERY_AUTH_OPTIONAL)) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_DISCOVERY_AUTH_OPTIONAL, + fnode->sess.discovery_auth_optional)) + count++; + } else if (!strcmp(param->name, FLASHNODE_SESS_ERL)) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_ERL, + fnode->sess.erl)) + count++; + } else if (!strcmp(param->name, + FLASHNODE_SESS_DEF_TIME2WAIT)) { + if (!flashnode_fill_uint16(fnode, &iov[count], + ISCSI_FLASHNODE_DEF_TIME2WAIT, + fnode->sess.def_time2wait)) + count++; + } else if (!strcmp(param->name, + FLASHNODE_SESS_DEF_TIME2RETAIN)) { + if (!flashnode_fill_uint16(fnode, &iov[count], + ISCSI_FLASHNODE_DEF_TIME2RETAIN, + fnode->sess.def_time2retain)) + count++; + } else if (!strcmp(param->name, FLASHNODE_SESS_MAX_R2T)) { + if (!flashnode_fill_uint16(fnode, &iov[count], + ISCSI_FLASHNODE_MAX_R2T, + fnode->sess.max_outstanding_r2t)) + count++; + } else if (!strcmp(param->name, FLASHNODE_SESS_TSID)) { + if (!flashnode_fill_uint16(fnode, &iov[count], + ISCSI_FLASHNODE_TSID, + fnode->sess.tsid)) + count++; + } else if (!strcmp(param->name, FLASHNODE_SESS_MAX_BURST)) { + if (!flashnode_fill_uint32(fnode, &iov[count], + ISCSI_FLASHNODE_MAX_BURST, + fnode->sess.max_burst_len)) + count++; + } else if (!strcmp(param->name, + FLASHNODE_SESS_DEF_TASKMGMT_TMO)) { + if (!flashnode_fill_uint16(fnode, &iov[count], + ISCSI_FLASHNODE_DEF_TASKMGMT_TMO, + fnode->sess.def_taskmgmt_tmo)) + count++; + } else if (!strcmp(param->name, FLASHNODE_SESS_NAME)) { + if (!flashnode_fill_str(fnode, &iov[count], + ISCSI_FLASHNODE_NAME, + fnode->sess.targetname, + sizeof(fnode->sess.targetname))) + count++; + } else if (!strcmp(param->name, FLASHNODE_SESS_FIRST_BURST)) { + if (!flashnode_fill_uint32(fnode, &iov[count], + ISCSI_FLASHNODE_FIRST_BURST, + fnode->sess.first_burst_len)) + count++; + } else if (!strcmp(param->name, FLASHNODE_SESS_ISID)) { + if (!flashnode_fill_isid(fnode, &iov[count])) + count++; + } else if (!strcmp(param->name, FLASHNODE_SESS_ALIAS)) { + if (!flashnode_fill_str(fnode, &iov[count], + ISCSI_FLASHNODE_ALIAS, + fnode->sess.targetalias, + sizeof(fnode->sess.targetalias))) + count++; + } else if (!strcmp(param->name, FLASHNODE_SESS_TPGT)) { + if (!flashnode_fill_uint16(fnode, &iov[count], + ISCSI_FLASHNODE_TPGT, + fnode->sess.tpgt)) + count++; + } else if (!strcmp(param->name, + FLASHNODE_SESS_DISCOVERY_PARENT_IDX)) { + if (!flashnode_fill_uint16(fnode, &iov[count], + ISCSI_FLASHNODE_DISCOVERY_PARENT_IDX, + fnode->sess.discovery_parent_idx)) + count++; + } else if (!strcmp(param->name, + FLASHNODE_SESS_DISCOVERY_PARENT_TYPE)) { + if (!flashnode_fill_str(fnode, &iov[count], + ISCSI_FLASHNODE_DISCOVERY_PARENT_TYPE, + fnode->sess.discovery_parent_type, + sizeof(fnode->sess.discovery_parent_type))) + count++; + } else if (!strcmp(param->name, FLASHNODE_SESS_PORTAL_TYPE)) { + if (!flashnode_fill_str(fnode, &iov[count], + ISCSI_FLASHNODE_PORTAL_TYPE, + fnode->sess.portal_type, + sizeof(fnode->sess.portal_type))) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_SESS_CHAP_OUT_IDX))) { + if (!flashnode_fill_uint32(fnode, &iov[count], + ISCSI_FLASHNODE_CHAP_OUT_IDX, + fnode->sess.chap_out_idx)) + count++; + } else if (!strcmp(param->name, to_key(FLASHNODE_CONN_PORT))) { + if (fnode->conn[0].port) + port = fnode->conn[0].port; + if (!flashnode_fill_uint16(fnode, &iov[count], + ISCSI_FLASHNODE_PORT, port)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_IPADDR))) { + if (!flashnode_fill_ipaddr(fnode, &iov[count], + ISCSI_FLASHNODE_IPADDR)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_MAX_RECV_DLENGTH))) { + if (!flashnode_fill_uint32(fnode, &iov[count], + ISCSI_FLASHNODE_MAX_RECV_DLENGTH, + fnode->conn[0].max_recv_dlength)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_IS_FW_ASSIGNED_IPV6))) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_IS_FW_ASSIGNED_IPV6, + fnode->conn[0].is_fw_assigned_ipv6)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_HDR_DGST_EN))) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_HDR_DGST_EN, + fnode->conn[0].header_digest_en)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_DATA_DGST_EN))) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_DATA_DGST_EN, + fnode->conn[0].data_digest_en)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_SNACK_REQ_EN))) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_SNACK_REQ_EN, + fnode->conn[0].snack_req_en)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_TCP_TIMESTAMP_STAT))) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_TCP_TIMESTAMP_STAT, + fnode->conn[0].tcp_timestamp_stat)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_TCP_NAGLE_DISABLE))) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_TCP_NAGLE_DISABLE, + fnode->conn[0].tcp_nagle_disable)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_TCP_WSF_DISABLE))) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_TCP_WSF_DISABLE, + fnode->conn[0].tcp_wsf_disable)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_TCP_TIMER_SCALE))) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_TCP_TIMER_SCALE, + fnode->conn[0].tcp_timer_scale)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_TCP_TIMESTAMP_EN))) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_TCP_TIMESTAMP_EN, + fnode->conn[0].tcp_timestamp_en)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_IP_FRAG_DISABLE))) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_IP_FRAG_DISABLE, + fnode->conn[0].fragment_disable)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_MAX_XMIT_DLENGTH))) { + if (!flashnode_fill_uint32(fnode, &iov[count], + ISCSI_FLASHNODE_MAX_XMIT_DLENGTH, + fnode->conn[0].max_xmit_dlength)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_KEEPALIVE_TMO))) { + if (!flashnode_fill_uint16(fnode, &iov[count], + ISCSI_FLASHNODE_KEEPALIVE_TMO, + fnode->conn[0].keepalive_tmo)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_REDIRECT_IPADDR))) { + if (!flashnode_fill_ipaddr(fnode, &iov[count], + ISCSI_FLASHNODE_REDIRECT_IPADDR)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_MAX_SEGMENT_SIZE))) { + if (!flashnode_fill_uint32(fnode, &iov[count], + ISCSI_FLASHNODE_MAX_SEGMENT_SIZE, + fnode->conn[0].max_segment_size)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_LOCAL_PORT))) { + if (!flashnode_fill_uint16(fnode, &iov[count], + ISCSI_FLASHNODE_LOCAL_PORT, + fnode->conn[0].local_port)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_IPV4_TOS))) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_IPV4_TOS, + fnode->conn[0].ipv4_tos)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_IPV6_TC))) { + if (!flashnode_fill_uint8(fnode, &iov[count], + ISCSI_FLASHNODE_IPV6_TC, + fnode->conn[0].ipv6_traffic_class)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_IPV6_FLOW_LABEL))) { + if (!flashnode_fill_uint16(fnode, &iov[count], + ISCSI_FLASHNODE_IPV6_FLOW_LABEL, + fnode->conn[0].ipv6_flow_lbl)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_LINK_LOCAL_IPV6))) { + if (!flashnode_fill_ipv6_addr(fnode, &iov[count], + ISCSI_FLASHNODE_LINK_LOCAL_IPV6)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_TCP_XMIT_WSF))) { + if (!flashnode_fill_uint32(fnode, &iov[count], + ISCSI_FLASHNODE_TCP_XMIT_WSF, + fnode->conn[0].tcp_xmit_wsf)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_TCP_RECV_WSF))) { + if (!flashnode_fill_uint32(fnode, &iov[count], + ISCSI_FLASHNODE_TCP_RECV_WSF, + fnode->conn[0].tcp_recv_wsf)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_STATSN))) { + if (!flashnode_fill_uint32(fnode, &iov[count], + ISCSI_FLASHNODE_STATSN, + fnode->conn[0].stat_sn)) + count++; + } else if (!strcmp(param->name, + to_key(FLASHNODE_CONN_EXP_STATSN))) { + if (!flashnode_fill_uint32(fnode, &iov[count], + ISCSI_FLASHNODE_EXP_STATSN, + fnode->conn[0].exp_stat_sn)) + count++; + } + } + + return count; +} diff --git a/usr/flashnode.h b/usr/flashnode.h new file mode 100644 index 0000000..2950fb5 --- /dev/null +++ b/usr/flashnode.h @@ -0,0 +1,129 @@ +/* + * iSCSI flashnode helpers + * + * Copyright (C) 2013 QLogic Corporation. + * Maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#ifndef FLASHNODE_H +#define FLASHNODE_H +#include +#include +#include + +#include "types.h" +#include "config.h" +#include "auth.h" + +#define MAX_FLASHNODE_IDX UINT_MAX + +typedef enum portal_type { + IPV4, + IPV6, +} portal_type_e; + +typedef struct flashnode_sess_rec { + char targetname[TARGET_NAME_MAXLEN]; + char targetalias[TARGET_NAME_MAXLEN]; + char username[AUTH_STR_MAX_LEN]; + char username_in[AUTH_STR_MAX_LEN]; + char password[AUTH_STR_MAX_LEN]; + char password_in[AUTH_STR_MAX_LEN]; + /* indicates if discovery was done through iSNS discovery service + * or through sendTarget */ + char discovery_parent_type[ISCSI_MAX_STR_LEN]; + char isid[16]; + char portal_type[5]; /* ipv4 or ipv6 */ + unsigned first_burst_len; + unsigned max_burst_len; + uint16_t def_time2wait; + uint16_t def_time2retain; + uint16_t max_outstanding_r2t; + uint16_t tsid; + uint16_t def_taskmgmt_tmo; + uint16_t tpgt; + uint16_t chap_out_idx; + uint16_t chap_in_idx; + /* index of iSCSI discovery session if the entry is + * discovered by iSCSI discovery session + */ + uint16_t discovery_parent_idx; + /* Firmware auto sendtarget discovery disable */ + uint8_t auto_snd_tgt_disable; + uint8_t discovery_session; + /* indicates if this flashnode entry is enabled or disabled */ + uint8_t entry_enable; + uint8_t immediate_data; + uint8_t initial_r2t; + uint8_t data_seq_in_order; + uint8_t data_pdu_in_order; + uint8_t chap_auth_en; + /* enables firmware to auto logout the discovery session on discovery + * completion + */ + uint8_t discovery_logout_en; + uint8_t bidi_chap_en; + /* makes authentication for discovery session optional */ + uint8_t discovery_auth_optional; + uint8_t erl; + uint8_t is_boot_target; +} flashnode_sess_rec_t; + +typedef struct flashnode_conn_rec { + char ipaddress[NI_MAXHOST]; + char redirect_ipaddr[NI_MAXHOST]; + char link_local_ipv6[NI_MAXHOST]; + unsigned max_recv_dlength; + unsigned max_xmit_dlength; + unsigned max_segment_size; + unsigned tcp_xmit_wsf; + unsigned tcp_recv_wsf; + uint32_t stat_sn; + uint32_t exp_stat_sn; + uint16_t keepalive_tmo; + uint16_t port; + uint16_t local_port; + uint16_t ipv6_flow_lbl; + /* Link local IPv6 address is assigned by firmware or driver */ + uint8_t is_fw_assigned_ipv6; + uint8_t header_digest_en; + uint8_t data_digest_en; + uint8_t snack_req_en; + /* tcp timestamp negotiation status */ + uint8_t tcp_timestamp_stat; + uint8_t tcp_nagle_disable; + /* tcp window scale factor */ + uint8_t tcp_wsf_disable; + uint8_t tcp_timer_scale; + uint8_t tcp_timestamp_en; + uint8_t fragment_disable; + uint8_t ipv4_tos; + uint8_t ipv6_traffic_class; +} flashnode_conn_rec_t; + +struct flashnode_rec { + struct list_head list; + char transport_name[ISCSI_TRANSPORT_NAME_MAXLEN]; + flashnode_sess_rec_t sess; + flashnode_conn_rec_t conn[ISCSI_CONN_MAX]; +}; + +extern int flashnode_info_print_flat(void *data, struct flashnode_rec *tgt, + uint32_t host_no, uint32_t flashnode_idx); +extern int iscsi_logout_flashnode_sid(struct iscsi_transport *t, + uint32_t host_no, uint32_t sid); +extern int flashnode_build_config(struct list_head *params, + struct flashnode_rec *flashnode, + struct iovec *iovs); +#endif diff --git a/usr/host.c b/usr/host.c new file mode 100644 index 0000000..c94b6d1 --- /dev/null +++ b/usr/host.c @@ -0,0 +1,451 @@ +/* + * iSCSI host helpers + * + * Copyright (C) 2008 Mike Christie + * Copyright (C) 2008 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#include +#include +#include + +#include "list.h" +#include "iscsi_util.h" +#include "log.h" +#include "iscsi_sysfs.h" +#include "version.h" +#include "iscsi_settings.h" +#include "mgmt_ipc.h" +#include "host.h" +#include "session_info.h" +#include "transport.h" +#include "initiator.h" +#include "iface.h" +#include "iscsi_err.h" +#include "iscsi_netlink.h" + +struct _host_info_print_tree_arg { + unsigned int flags; + struct iscsi_session **ses; + uint32_t se_count; +}; + +static int match_host_to_session(uint32_t host_no, struct iscsi_session *se) +{ + uint32_t sid = 0; + uint32_t info_host_no; + int rc; + + sid = iscsi_session_sid_get(se); + + info_host_no = iscsi_sysfs_get_host_no_from_sid(sid, &rc); + if (rc) { + log_error("could not get host_no for session%d err %d.", + sid, rc); + return 0; + } + + return host_no == info_host_no; +} + +static void print_host_info(struct iface_rec *iface, char *prefix) +{ + if (strlen(iface->transport_name)) + printf("%sTransport: %s\n", prefix, + iface->transport_name); + else + printf("%sTransport: %s\n", prefix, UNKNOWN_VALUE); + + if (strlen(iface->iname)) + printf("%sInitiatorname: %s\n", prefix, + iface->iname); + else + printf("%sInitiatorname: %s\n", prefix, UNKNOWN_VALUE); + + if (!strlen(iface->ipaddress)) + printf("%sIPaddress: %s\n", prefix, UNKNOWN_VALUE); + else if (strchr(iface->ipaddress, '.')) + printf("%sIPaddress: %s\n", prefix, iface->ipaddress); + else + printf("%sIPaddress: [%s]\n", prefix, iface->ipaddress); + + if (strlen(iface->hwaddress)) + printf("%sHWaddress: %s\n", prefix, iface->hwaddress); + else + printf("%sHWaddress: %s\n", prefix, UNKNOWN_VALUE); + + if (strlen(iface->netdev)) + printf("%sNetdev: %s\n", prefix, iface->netdev); + else + printf("%sNetdev: %s\n", prefix, UNKNOWN_VALUE); +} + +static int host_info_print_flat(void *data, struct host_info *hinfo) +{ + struct iface_rec *iface = &hinfo->iface; + + if (strlen(iface->transport_name)) + printf("%s: ", iface->transport_name); + else + printf("%s: ", UNKNOWN_VALUE); + + printf("[%u] ", hinfo->host_no); + + if (!strlen(iface->ipaddress)) + printf("%s,", UNKNOWN_VALUE); + else if (strchr(iface->ipaddress, '.')) + printf("%s,", iface->ipaddress); + else + printf("[%s],", iface->ipaddress); + + if (strlen(iface->hwaddress)) + printf("[%s],", iface->hwaddress); + else + printf("[%s],", UNKNOWN_VALUE); + + if (strlen(iface->netdev)) + printf("%s ", iface->netdev); + else + printf("%s ", UNKNOWN_VALUE); + + if (strlen(iface->iname)) + printf("%s\n", iface->iname); + else + printf("%s\n", UNKNOWN_VALUE); + return 0; +} + +static int print_host_iface(void *data, struct iface_rec *iface) +{ + char *prefix = data; + + printf("%s**********\n", prefix); + printf("%sInterface:\n", prefix); + printf("%s**********\n", prefix); + + printf("%sKernel Name: %s\n", prefix, iface->name); + + if (!strlen(iface->ipaddress)) + printf("%sIPaddress: %s\n", prefix, UNKNOWN_VALUE); + else if (strchr(iface->ipaddress, '.')) { + printf("%sIPaddress: %s\n", prefix, iface->ipaddress); + + if (!strlen(iface->gateway)) + printf("%sGateway: %s\n", prefix, UNKNOWN_VALUE); + else + printf("%sGateway: %s\n", prefix, iface->gateway); + if (!strlen(iface->subnet_mask)) + printf("%sSubnet: %s\n", prefix, UNKNOWN_VALUE); + else + printf("%sSubnet: %s\n", prefix, iface->subnet_mask); + if (!strlen(iface->bootproto)) + printf("%sBootProto: %s\n", prefix, UNKNOWN_VALUE); + else + printf("%sBootProto: %s\n", prefix, iface->bootproto); + } else { + printf("%sIPaddress: [%s]\n", prefix, iface->ipaddress); + + if (!strlen(iface->ipv6_autocfg)) + printf("%sIPaddress Autocfg: %s\n", prefix, + UNKNOWN_VALUE); + else + printf("%sIPaddress Autocfg: %s\n", prefix, + iface->ipv6_autocfg); + if (!strlen(iface->ipv6_linklocal)) + printf("%sLink Local Address: %s\n", prefix, + UNKNOWN_VALUE); + else + printf("%sLink Local Address: [%s]\n", prefix, + iface->ipv6_linklocal); + if (!strlen(iface->linklocal_autocfg)) + printf("%sLink Local Autocfg: %s\n", prefix, + UNKNOWN_VALUE); + else + printf("%sLink Local Autocfg: %s\n", prefix, + iface->linklocal_autocfg); + if (!strlen(iface->ipv6_router)) + printf("%sRouter Address: %s\n", prefix, + UNKNOWN_VALUE); + else + printf("%sRouter Address: [%s]\n", prefix, + iface->ipv6_router); + } + + if (!strlen(iface->port_state)) + printf("%sPort State: %s\n", prefix, UNKNOWN_VALUE); + else + printf("%sPort State: %s\n", prefix, iface->port_state); + + if (!strlen(iface->port_speed)) + printf("%sPort Speed: %s\n", prefix, UNKNOWN_VALUE); + else + printf("%sPort Speed: %s\n", prefix, iface->port_speed); + + if (!iface->port) + printf("%sPort: %s\n", prefix, UNKNOWN_VALUE); + else + printf("%sPort: %u\n", prefix, iface->port); + + if (!iface->mtu) + printf("%sMTU: %s\n", prefix, UNKNOWN_VALUE); + else + printf("%sMTU: %u\n", prefix, iface->mtu); + + if (iface->vlan_id == UINT16_MAX) + printf("%sVLAN ID: %s\n", prefix, UNKNOWN_VALUE); + else + printf("%sVLAN ID: %u\n", prefix, iface->vlan_id); + + if (iface->vlan_priority == UINT8_MAX) + printf("%sVLAN priority: %s\n", prefix, UNKNOWN_VALUE); + else + printf("%sVLAN priority: %u\n", prefix, iface->vlan_priority); + return 0; +} + +static void print_host_ifaces(struct host_info *hinfo, char *prefix) +{ + int nr_found = 0; + + iscsi_sysfs_for_each_iface_on_host(prefix, hinfo->host_no, &nr_found, + print_host_iface); +} + +static int host_info_print_tree(void *data, struct host_info *hinfo) +{ + unsigned int session_info_flags = 0; + struct _host_info_print_tree_arg *arg = data; + struct iscsi_session **ses = NULL; + struct iscsi_session **matched_ses = NULL; + uint32_t se_count = 0; + uint32_t matched_se_count = 0; + uint32_t i = 0; + char state[SCSI_MAX_STATE_VALUE]; + + if (arg == NULL) + return -EINVAL; + + session_info_flags = arg->flags; + ses = arg->ses; + se_count = arg->se_count; + + printf("Host Number: %u\n", hinfo->host_no); + if (!iscsi_sysfs_get_host_state(state, hinfo->host_no)) + printf("\tState: %s\n", state); + else + printf("\tState: Unknown\n"); + print_host_info(&hinfo->iface, "\t"); + + print_host_ifaces(hinfo, "\t"); + + if ((!session_info_flags) || (!se_count)) + return 0; + + matched_ses = calloc(se_count, sizeof(struct iscsi_session *)); + if (matched_ses == NULL) + return -ENOMEM; + + for (i = 0; i < se_count; ++i) + if (match_host_to_session(hinfo->host_no, ses[i])) + matched_ses[matched_se_count++] = ses[i]; + + if (!matched_se_count) + goto out; + + printf("\t*********\n"); + printf("\tSessions:\n"); + printf("\t*********\n"); + session_info_print_tree(matched_ses, matched_se_count, "\t", + session_info_flags, 0/* don't show password */); +out: + free(matched_ses); + return 0; +} + +int host_info_print(int info_level, uint32_t host_no, + struct iscsi_session **ses, uint32_t se_count) + +{ + int num_found = 0, err = 0; + char *version; + unsigned int flags = 0; + struct _host_info_print_tree_arg arg; + + switch (info_level) { + case 0: + case -1: + err = iscsi_sysfs_for_each_host(NULL, &num_found, + host_info_print_flat); + break; + case 4: + version = iscsi_sysfs_get_iscsi_kernel_version(); + if (version) { + printf("iSCSI Transport Class version %s\n", + version); + printf("version %s\n", ISCSI_VERSION_STR); + free(version); + } + + flags |= SESSION_INFO_SCSI_DEVS; + /* fall through */ + case 3: + flags |= SESSION_INFO_ISCSI_PARAMS; + /* fall through */ + case 2: + flags |= SESSION_INFO_ISCSI_STATE | SESSION_INFO_IFACE; + /* fall through */ + case 1: + arg.flags = flags; + arg.ses = ses; + arg.se_count = se_count; + if (host_no != -1) { + struct host_info hinfo; + + memset(&hinfo, 0, sizeof(struct host_info)); + hinfo.host_no = host_no; + iscsi_sysfs_get_hostinfo_by_host_no(&hinfo); + host_info_print_tree(&arg, &hinfo); + num_found = 1; + break; + } + + transport_probe_for_offload(); + err = iscsi_sysfs_for_each_host(&arg, &num_found, + host_info_print_tree); + break; + default: + log_error("Invalid info level %d. Try 0 - 4.", info_level); + return ISCSI_ERR_INVAL; + } + + if (err) { + log_error("Can not get list of iSCSI hosts: %s", + iscsi_err_to_str(err)); + return err; + } else if (!num_found) { + log_error("No iSCSI hosts."); + return ISCSI_ERR_NO_OBJS_FOUND; + } + return 0; +} + +static int chap_fill_param_uint(struct iovec *iov, int param, + uint32_t param_val, int param_len) +{ + struct iscsi_param_info *param_info; + struct nlattr *attr; + int len; + uint8_t val8 = 0; + uint16_t val16 = 0; + uint32_t val32 = 0; + char *val = NULL; + + len = sizeof(struct iscsi_param_info) + param_len; + iov->iov_base = iscsi_nla_alloc(param, len); + if (!iov->iov_base) + return 1; + + attr = iov->iov_base; + iov->iov_len = NLA_ALIGN(attr->nla_len); + + param_info = (struct iscsi_param_info *)ISCSI_NLA_DATA(attr); + param_info->param = param; + param_info->len = param_len; + + switch (param_len) { + case 1: + val8 = (uint8_t)param_val; + val = (char *)&val8; + break; + + case 2: + val16 = (uint16_t)param_val; + val = (char *)&val16; + break; + + case 4: + val32 = (uint32_t)param_val; + val = (char *)&val32; + break; + + default: + goto free; + } + memcpy(param_info->value, val, param_len); + + return 0; + +free: + free(iov->iov_base); + iov->iov_base = NULL; + iov->iov_len = 0; + return 1; +} + +static int chap_fill_param_str(struct iovec *iov, int param, char *param_val, + int param_len) +{ + struct iscsi_param_info *param_info; + struct nlattr *attr; + int len; + + len = sizeof(struct iscsi_param_info) + param_len; + iov->iov_base = iscsi_nla_alloc(param, len); + if (!iov->iov_base) + return 1; + + attr = iov->iov_base; + iov->iov_len = NLA_ALIGN(attr->nla_len); + + param_info = (struct iscsi_param_info *)ISCSI_NLA_DATA(attr); + param_info->param = param; + param_info->len = param_len; + memcpy(param_info->value, param_val, param_len); + return 0; +} + +int chap_build_config(struct iscsi_chap_rec *crec, struct iovec *iovs) +{ + struct iovec *iov = NULL; + int count = 0; + + /* start at 2, because 0 is for nlmsghdr and 1 for event */ + iov = iovs + 2; + + if (!chap_fill_param_uint(&iov[count], ISCSI_CHAP_PARAM_INDEX, + crec->chap_tbl_idx, + sizeof(crec->chap_tbl_idx))) + count++; + + if (!chap_fill_param_uint(&iov[count], ISCSI_CHAP_PARAM_CHAP_TYPE, + crec->chap_type, sizeof(crec->chap_type))) + count++; + + if (!chap_fill_param_str(&iov[count], ISCSI_CHAP_PARAM_USERNAME, + crec->username, strlen(crec->username))) + count++; + + if (!chap_fill_param_str(&iov[count], ISCSI_CHAP_PARAM_PASSWORD, + (char *)crec->password, + strlen((char *)crec->password))) + count++; + + if (!chap_fill_param_uint(&iov[count], ISCSI_CHAP_PARAM_PASSWORD_LEN, + crec->password_length, + sizeof(crec->password_length))) + count++; + + return count; +} diff --git a/usr/host.h b/usr/host.h new file mode 100644 index 0000000..f521fd2 --- /dev/null +++ b/usr/host.h @@ -0,0 +1,25 @@ +#ifndef ISCSI_HOST_H +#define ISCSI_HOST_H +#include + +#include + +#include "types.h" +#include "config.h" + +#define MAX_HOST_NO UINT_MAX + +#define MAX_CHAP_ENTRIES 2048 +#define MAX_CHAP_BUF_SZ 4096 +#define REQ_CHAP_BUF_SZ (MAX_CHAP_BUF_SZ + sizeof(struct iscsi_uevent)) + +struct host_info { + struct iface_rec iface; + uint32_t host_no; +}; + +extern int host_info_print(int info_level, uint32_t host_no, + struct iscsi_session **ses, uint32_t se_count); +extern int chap_build_config(struct iscsi_chap_rec *crec, struct iovec *iovs); + +#endif diff --git a/usr/idbm.c b/usr/idbm.c new file mode 100644 index 0000000..be4d4e3 --- /dev/null +++ b/usr/idbm.c @@ -0,0 +1,3068 @@ +/* + * iSCSI Discovery Database Library + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * Copyright (C) 2006 Mike Christie + * Copyright (C) 2006 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "idbm.h" +#include "idbm_fields.h" +#include "log.h" +#include "iscsi_util.h" +#include "iscsi_settings.h" +#include "transport.h" +#include "iscsi_sysfs.h" +#include "iface.h" +#include "sysdeps.h" +#include "fw_context.h" +#include "iscsi_err.h" + +#define IDBM_HIDE 0 /* Hide parameter when print. */ +#define IDBM_SHOW 1 /* Show parameter when print. */ +#define IDBM_MASKED 2 /* Show "stars" instead of real value when print */ + +static struct idbm *db; + +#define __recinfo_str(_key, _info, _rec, _name, _show, _n, _mod) do { \ + _info[_n].type = TYPE_STR; \ + strlcpy(_info[_n].name, _key, NAME_MAXVAL); \ + if (strlen((char*)_rec->_name)) \ + strlcpy((char*)_info[_n].value, (char*)_rec->_name, \ + VALUE_MAXVAL); \ + _info[_n].data = &_rec->_name; \ + _info[_n].data_len = sizeof(_rec->_name); \ + _info[_n].visible = _show; \ + _info[_n].can_modify = _mod; \ + _n++; \ +} while(0) + +#define __recinfo_int(_key, _info, _rec, _name, _show, _n, _mod) do { \ + _info[_n].type = TYPE_INT; \ + strlcpy(_info[_n].name, _key, NAME_MAXVAL); \ + snprintf(_info[_n].value, VALUE_MAXVAL, "%" PRIi32, _rec->_name); \ + _info[_n].data = &_rec->_name; \ + _info[_n].data_len = sizeof(_rec->_name); \ + _info[_n].visible = _show; \ + _info[_n].can_modify = _mod; \ + _n++; \ +} while(0) + +#define __recinfo_uint8(_key, _info, _rec, _name, _show, _n, _mod) do { \ + _info[_n].type = TYPE_UINT8; \ + strlcpy(_info[_n].name, _key, NAME_MAXVAL); \ + snprintf(_info[_n].value, VALUE_MAXVAL, "%" PRIu8, _rec->_name); \ + _info[_n].data = &_rec->_name; \ + _info[_n].data_len = sizeof(_rec->_name); \ + _info[_n].visible = _show; \ + _info[_n].can_modify = _mod; \ + _n++; \ +} while (0) + +#define __recinfo_uint16(_key, _info, _rec, _name, _show, _n, _mod) do { \ + _info[_n].type = TYPE_UINT16; \ + strlcpy(_info[_n].name, _key, NAME_MAXVAL); \ + snprintf(_info[_n].value, VALUE_MAXVAL, "%" PRIu16, _rec->_name); \ + _info[_n].data = &_rec->_name; \ + _info[_n].data_len = sizeof(_rec->_name); \ + _info[_n].visible = _show; \ + _info[_n].can_modify = _mod; \ + _n++; \ +} while (0) + +#define __recinfo_uint32(_key, _info, _rec, _name, _show, _n, _mod) do { \ + _info[_n].type = TYPE_UINT32; \ + strlcpy(_info[_n].name, _key, NAME_MAXVAL); \ + snprintf(_info[_n].value, VALUE_MAXVAL, "%" PRIu32, _rec->_name); \ + _info[_n].data = &_rec->_name; \ + _info[_n].data_len = sizeof(_rec->_name); \ + _info[_n].visible = _show; \ + _info[_n].can_modify = _mod; \ + _n++; \ +} while (0) + +#define __recinfo_int_o2(_key,_info,_rec,_name,_show,_op0,_op1,_n, _mod) do { \ + _info[_n].type = TYPE_INT_O; \ + strlcpy(_info[_n].name, _key, NAME_MAXVAL); \ + if (_rec->_name == 0) strlcpy(_info[_n].value, _op0, VALUE_MAXVAL); \ + if (_rec->_name == 1) strlcpy(_info[_n].value, _op1, VALUE_MAXVAL); \ + _info[_n].data = &_rec->_name; \ + _info[_n].data_len = sizeof(_rec->_name); \ + _info[_n].visible = _show; \ + _info[_n].opts[0] = _op0; \ + _info[_n].opts[1] = _op1; \ + _info[_n].numopts = 2; \ + _info[_n].can_modify = _mod; \ + _n++; \ +} while(0) + +#define __recinfo_int_o3(_key,_info,_rec,_name,_show,_op0,_op1,_op2,_n, \ + _mod) do { \ + __recinfo_int_o2(_key,_info,_rec,_name,_show,_op0,_op1,_n, _mod); \ + _n--; \ + if (_rec->_name == 2) strlcpy(_info[_n].value, _op2, VALUE_MAXVAL);\ + _info[_n].opts[2] = _op2; \ + _info[_n].numopts = 3; \ + _n++; \ +} while(0) + +#define __recinfo_int_o4(_key,_info,_rec,_name,_show,_op0,_op1,_op2,_op3,_n, \ + _mod) do { \ + __recinfo_int_o3(_key,_info,_rec,_name,_show,_op0,_op1,_op2,_n, _mod); \ + _n--; \ + if (_rec->_name == 3) strlcpy(_info[_n].value, _op3, VALUE_MAXVAL); \ + _info[_n].opts[3] = _op3; \ + _info[_n].numopts = 4; \ + _n++; \ +} while(0) + +#define __recinfo_int_o5(_key,_info,_rec,_name,_show,_op0,_op1,_op2,_op3, \ + _op4,_n, _mod) do { \ + __recinfo_int_o4(_key,_info,_rec,_name,_show,_op0,_op1,_op2,_op3, \ + _n,_mod); \ + _n--; \ + if (_rec->_name == 4) strlcpy(_info[_n].value, _op4, VALUE_MAXVAL); \ + _info[_n].opts[4] = _op4; \ + _info[_n].numopts = 5; \ + _n++; \ +} while(0) + +#define __recinfo_int_o6(_key,_info,_rec,_name,_show,_op0,_op1,_op2, \ + _op3,_op4,_op5,_n,_mod) do { \ + __recinfo_int_o5(_key,_info,_rec,_name,_show,_op0,_op1,_op2,_op3, \ + _op4,_n,_mod); \ + _n--; \ + if (_rec->_name == 5) strlcpy(_info[_n].value, _op5, VALUE_MAXVAL); \ + _info[_n].opts[5] = _op5; \ + _info[_n].numopts = 6; \ + _n++; \ +} while(0) + +static void +idbm_recinfo_discovery(discovery_rec_t *r, recinfo_t *ri) +{ + int num = 0; + + __recinfo_int_o2(DISC_STARTUP, ri, r, startup, IDBM_SHOW, + "manual", "automatic", num, 1); + __recinfo_int_o6(DISC_TYPE, ri, r, type, IDBM_SHOW, + "sendtargets", "isns", "offload_send_targets", "slp", + "static", "fw", num, 0); + switch (r->type) { + case DISCOVERY_TYPE_SENDTARGETS: + __recinfo_str(DISC_ST_ADDR, ri, r, + address, IDBM_SHOW, num, 0); + __recinfo_int(DISC_ST_PORT, ri, r, + port, IDBM_SHOW, num, 0); + __recinfo_int_o2(DISC_ST_AUTH_METHOD, ri, r, + u.sendtargets.auth.authmethod, + IDBM_SHOW, "None", "CHAP", num, 1); + __recinfo_str(DISC_ST_USERNAME, ri, r, + u.sendtargets.auth.username, IDBM_SHOW, num, 1); + __recinfo_str(DISC_ST_PASSWORD, ri, r, + u.sendtargets.auth.password, IDBM_MASKED, num, 1); + __recinfo_int(DISC_ST_PASSWORD_LEN, ri, r, + u.sendtargets.auth.password_length, IDBM_HIDE, num, 1); + __recinfo_str(DISC_ST_USERNAME_IN, ri, r, + u.sendtargets.auth.username_in, IDBM_SHOW, num, 1); + __recinfo_str(DISC_ST_PASSWORD_IN, ri, r, + u.sendtargets.auth.password_in, IDBM_MASKED, num, 1); + __recinfo_int(DISC_ST_PASSWORD_IN_LEN, ri, r, + u.sendtargets.auth.password_in_length, IDBM_HIDE, + num, 1); + __recinfo_int(DISC_ST_LOGIN_TMO, ri, r, + u.sendtargets.conn_timeo.login_timeout, + IDBM_SHOW, num, 1); + __recinfo_int_o2(DISC_ST_USE_DISC_DAEMON, ri, r, + u.sendtargets.use_discoveryd, + IDBM_SHOW, "No", "Yes", num, 1); + __recinfo_int(DISC_ST_DISC_DAEMON_POLL_INVAL, ri, r, + u.sendtargets.discoveryd_poll_inval, + IDBM_SHOW, num, 1); + __recinfo_int(DISC_ST_REOPEN_MAX, ri, r, + u.sendtargets.reopen_max, + IDBM_SHOW, num, 1); + __recinfo_int(DISC_ST_AUTH_TMO, ri, r, + u.sendtargets.conn_timeo.auth_timeout, + IDBM_SHOW, num, 1); + __recinfo_int(DISC_ST_ACTIVE_TMO, ri, r, + u.sendtargets.conn_timeo.active_timeout, + IDBM_SHOW, num, 1); + __recinfo_int(DISC_ST_MAX_RECV_DLEN, ri, r, + u.sendtargets.conn_conf.MaxRecvDataSegmentLength, + IDBM_SHOW, num, 1); + break; + case DISCOVERY_TYPE_ISNS: + __recinfo_str(DISC_ISNS_ADDR, ri, r, + address, IDBM_SHOW, num, 0); + __recinfo_int(DISC_ISNS_PORT, ri, r, + port, IDBM_SHOW, num, 0); + __recinfo_int_o2(DISC_ISNS_USE_DISC_DAEMON, ri, r, + u.isns.use_discoveryd, + IDBM_SHOW, "No", "Yes", num, 1); + __recinfo_int(DISC_ISNS_DISC_DAEMON_POLL_INVAL, ri, r, + u.isns.discoveryd_poll_inval, + IDBM_SHOW, num, 1); + break; + default: + break; + } +} + +void +idbm_recinfo_node(node_rec_t *r, recinfo_t *ri) +{ + int num = 0, i; + int iface_type; + + iface_type = iface_get_iptype(&r->iface); + + __recinfo_str(NODE_NAME, ri, r, name, IDBM_SHOW, num, 0); + __recinfo_int(NODE_TPGT, ri, r, tpgt, IDBM_SHOW, num, 0); + __recinfo_int_o3(NODE_STARTUP, ri, r, startup, + IDBM_SHOW, "manual", "automatic", "onboot", num, 1); + __recinfo_int_o2(NODE_LEADING_LOGIN, ri, r, leading_login, IDBM_SHOW, + "No", "Yes", num, 1); + /* + * Note: because we do not add the iface.iscsi_ifacename to + * sysfs iscsiadm does some weird matching. We can change the iface + * values if a session is not running, but node record ifaces values + * have to be changed and so do the iface record ones. + * + * Users should nornmally not want to change the iface ones + * in the node record directly and instead do it through + * the iface mode which will do the right thing (although that + * needs some locking). + */ + __recinfo_str(IFACE_HWADDR, ri, r, iface.hwaddress, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_IPADDR, ri, r, iface.ipaddress, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_ISCSINAME, ri, r, iface.name, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_NETNAME, ri, r, iface.netdev, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_GATEWAY, ri, r, iface.gateway, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_SUBNET_MASK, ri, r, iface.subnet_mask, IDBM_SHOW, num, 1); + __recinfo_uint8(IFACE_PREFIX_LEN, ri, r, iface.prefix_len, + IDBM_SHOW, num, 1); + /* + * svn 780 compat: older versions used node.transport_name and + * rec->transport_name + */ + __recinfo_str(IFACE_TRANSPORTNAME, ri, r, iface.transport_name, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_INAME, ri, r, iface.iname, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_STATE, ri, r, iface.state, IDBM_SHOW, num, 1); + __recinfo_uint16(IFACE_VLAN_ID, ri, r, iface.vlan_id, IDBM_SHOW, num, + 1); + __recinfo_uint8(IFACE_VLAN_PRIORITY, ri, r, iface.vlan_priority, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_VLAN_STATE, ri, r, iface.vlan_state, IDBM_SHOW, + num, 1); + __recinfo_int(IFACE_NUM, ri, r, iface.iface_num, IDBM_SHOW, num, 1); + __recinfo_uint16(IFACE_MTU, ri, r, iface.mtu, IDBM_SHOW, num, 1); + __recinfo_uint16(IFACE_PORT, ri, r, iface.port, IDBM_SHOW, num, 1); + + if (iface_type == ISCSI_IFACE_TYPE_IPV4) { + __recinfo_str(IFACE_BOOT_PROTO, ri, r, iface.bootproto, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_DHCP_ALT_CID, ri, r, + iface.dhcp_alt_client_id_state, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_DHCP_ALT_CID_STR, ri, r, + iface.dhcp_alt_client_id, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_DHCP_DNS, ri, r, iface.dhcp_dns, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_DHCP_LEARN_IQN, ri, r, + iface.dhcp_learn_iqn, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_DHCP_REQ_VID, ri, r, + iface.dhcp_req_vendor_id_state, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_DHCP_VID, ri, r, iface.dhcp_vendor_id_state, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_DHCP_VID_STR, ri, r, iface.dhcp_vendor_id, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_DHCP_SLP_DA, ri, r, iface.dhcp_slp_da, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_FRAGMENTATION, ri, r, iface.fragmentation, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_GRAT_ARP, ri, r, iface.gratuitous_arp, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_IN_FORWARD, ri, r, + iface.incoming_forwarding, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_TOS_STATE, ri, r, iface.tos_state, + IDBM_SHOW, num, 1); + __recinfo_uint8(IFACE_TOS, ri, r, iface.tos, IDBM_SHOW, num, 1); + __recinfo_uint8(IFACE_TTL, ri, r, iface.ttl, IDBM_SHOW, num, 1); + } else if (iface_type == ISCSI_IFACE_TYPE_IPV6) { + __recinfo_str(IFACE_IPV6_AUTOCFG, ri, r, iface.ipv6_autocfg, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_LINKLOCAL_AUTOCFG, ri, r, + iface.linklocal_autocfg, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_ROUTER_AUTOCFG, ri, r, iface.router_autocfg, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_LINKLOCAL, ri, r, iface.ipv6_linklocal, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_ROUTER, ri, r, iface.ipv6_router, + IDBM_SHOW, num, 1); + __recinfo_uint8(IFACE_DUP_ADDR_DETECT_CNT, ri, r, + iface.dup_addr_detect_cnt, IDBM_SHOW, num, 1); + __recinfo_uint32(IFACE_FLOW_LABEL, ri, r, iface.flow_label, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_GRAT_NEIGHBOR_ADV, ri, r, + iface.gratuitous_neighbor_adv, IDBM_SHOW, num, 1); + __recinfo_uint8(IFACE_HOP_LIMIT, ri, r, iface.hop_limit, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_MLD, ri, r, iface.mld, IDBM_SHOW, num, 1); + __recinfo_uint32(IFACE_ND_REACHABLE_TMO, ri, r, + iface.nd_reachable_tmo, IDBM_SHOW, num, 1); + __recinfo_uint32(IFACE_ND_REXMIT_TIME, ri, r, + iface.nd_rexmit_time, IDBM_SHOW, num, 1); + __recinfo_uint32(IFACE_ND_STALE_TMO, ri, r, iface.nd_stale_tmo, + IDBM_SHOW, num, 1); + __recinfo_uint32(IFACE_RTR_ADV_LINK_MTU, ri, r, + iface.router_adv_link_mtu, IDBM_SHOW, num, 1); + __recinfo_uint8(IFACE_TRAFFIC_CLASS, ri, r, iface.traffic_class, + IDBM_SHOW, num, 1); + } + + __recinfo_str(IFACE_DELAYED_ACK, ri, r, iface.delayed_ack, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_TCP_NAGLE, ri, r, iface.nagle, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_TCP_WSF_STATE, ri, r, iface.tcp_wsf_state, + IDBM_SHOW, num, 1); + __recinfo_uint8(IFACE_TCP_WSF, ri, r, iface.tcp_wsf, IDBM_SHOW, num, 1); + __recinfo_uint8(IFACE_TCP_TIMER_SCALE, ri, r, iface.tcp_timer_scale, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_TCP_TIMESTAMP, ri, r, iface.tcp_timestamp, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_REDIRECT, ri, r, iface.redirect, IDBM_SHOW, num, 1); + __recinfo_uint16(IFACE_DEF_TMF_TMO, ri, r, iface.def_task_mgmt_tmo, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_HDRDGST, ri, r, iface.header_digest, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_DATADGST, ri, r, iface.data_digest, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_IMM_DATA, ri, r, iface.immediate_data, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_INITIAL_R2T, ri, r, iface.initial_r2t, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_DSEQ_INORDER, ri, r, iface.data_seq_inorder, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_DPDU_INORDER, ri, r, iface.data_pdu_inorder, + IDBM_SHOW, num, 1); + __recinfo_uint8(IFACE_ERL, ri, r, iface.erl, IDBM_SHOW, num, 1); + __recinfo_uint32(IFACE_MAX_RECV_DLEN, ri, r, iface.max_recv_dlength, + IDBM_SHOW, num, 1); + __recinfo_uint32(IFACE_FIRST_BURST, ri, r, iface.first_burst_len, + IDBM_SHOW, num, 1); + __recinfo_uint16(IFACE_MAX_R2T, ri, r, iface.max_out_r2t, IDBM_SHOW, + num, 1); + __recinfo_uint32(IFACE_MAX_BURST, ri, r, iface.max_burst_len, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_CHAP_AUTH, ri, r, iface.chap_auth, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_BIDI_CHAP, ri, r, iface.bidi_chap, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_STRICT_LOGIN_COMP, ri, r, iface.strict_login_comp, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_DISCOVERY_AUTH, ri, r, iface.discovery_auth, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_DISCOVERY_LOGOUT, ri, r, iface.discovery_logout, + IDBM_SHOW, num, 1); + + + __recinfo_str(NODE_DISC_ADDR, ri, r, disc_address, IDBM_SHOW, + num, 0); + __recinfo_int(NODE_DISC_PORT, ri, r, disc_port, IDBM_SHOW, + num, 0); + __recinfo_int_o6(NODE_DISC_TYPE, ri, r, disc_type, IDBM_SHOW, + "send_targets", "isns", "offload_send_targets", "slp", + "static", "fw", num, 0); + __recinfo_int(SESSION_INIT_CMDSN, ri, r, + session.initial_cmdsn, IDBM_SHOW, num, 1); + __recinfo_int(SESSION_INIT_LOGIN_RETRY, ri, r, + session.initial_login_retry_max, IDBM_SHOW, num, 1); + __recinfo_int(SESSION_XMIT_THREAD_PRIORITY, ri, r, + session.xmit_thread_priority, IDBM_SHOW, num, 1); + __recinfo_int(SESSION_CMDS_MAX, ri, r, + session.cmds_max, IDBM_SHOW, num, 1); + __recinfo_int(SESSION_QDEPTH, ri, r, + session.queue_depth, IDBM_SHOW, num, 1); + __recinfo_int(SESSION_NR_SESSIONS, ri, r, + session.nr_sessions, IDBM_SHOW, num, 1); + __recinfo_int_o2(SESSION_AUTH_METHOD, ri, r, session.auth.authmethod, + IDBM_SHOW, "None", "CHAP", num, 1); + __recinfo_str(SESSION_USERNAME, ri, r, + session.auth.username, IDBM_SHOW, num, 1); + __recinfo_str(SESSION_PASSWORD, ri, r, + session.auth.password, IDBM_MASKED, num, 1); + __recinfo_int(SESSION_PASSWORD_LEN, ri, r, + session.auth.password_length, IDBM_HIDE, num, 1); + __recinfo_str(SESSION_USERNAME_IN, ri, r, + session.auth.username_in, IDBM_SHOW, num, 1); + __recinfo_str(SESSION_PASSWORD_IN, ri, r, + session.auth.password_in, IDBM_MASKED, num, 1); + __recinfo_int(SESSION_PASSWORD_IN_LEN, ri, r, + session.auth.password_in_length, IDBM_HIDE, num, 1); + __recinfo_int(SESSION_REPLACEMENT_TMO, ri, r, + session.timeo.replacement_timeout, + IDBM_SHOW, num, 1); + __recinfo_int(SESSION_ABORT_TMO, ri, r, + session.err_timeo.abort_timeout, + IDBM_SHOW, num, 1); + __recinfo_int(SESSION_LU_RESET_TMO, ri, r, + session.err_timeo.lu_reset_timeout, + IDBM_SHOW, num, 1); + __recinfo_int(SESSION_TGT_RESET_TMO, ri, r, + session.err_timeo.tgt_reset_timeout, + IDBM_SHOW, num, 1); + __recinfo_int(SESSION_HOST_RESET_TMO, ri, r, + session.err_timeo.host_reset_timeout, + IDBM_SHOW, num, 1); + __recinfo_int_o2(SESSION_FAST_ABORT, ri, r, + session.iscsi.FastAbort, IDBM_SHOW, "No", "Yes", + num, 1); + __recinfo_int_o2(SESSION_INITIAL_R2T, ri, r, + session.iscsi.InitialR2T, IDBM_SHOW, + "No", "Yes", num, 1); + __recinfo_int_o2(SESSION_IMM_DATA, ri, r, + session.iscsi.ImmediateData, + IDBM_SHOW, "No", "Yes", num, 1); + __recinfo_int(SESSION_FIRST_BURST, ri, r, + session.iscsi.FirstBurstLength, IDBM_SHOW, num, 1); + __recinfo_int(SESSION_MAX_BURST, ri, r, + session.iscsi.MaxBurstLength, IDBM_SHOW, num, 1); + __recinfo_int(SESSION_DEF_TIME2RETAIN, ri, r, + session.iscsi.DefaultTime2Retain, IDBM_SHOW, num, 1); + __recinfo_int(SESSION_DEF_TIME2WAIT, ri, r, + session.iscsi.DefaultTime2Wait, IDBM_SHOW, num, 1); + __recinfo_int(SESSION_MAX_CONNS, ri, r, + session.iscsi.MaxConnections, IDBM_SHOW, num, 1); + __recinfo_int(SESSION_MAX_R2T, ri, r, + session.iscsi.MaxOutstandingR2T, IDBM_SHOW, num, 1); + __recinfo_int(SESSION_ERL, ri, r, + session.iscsi.ERL, IDBM_SHOW, num, 1); + __recinfo_int_o2(SESSION_SCAN, ri, r, + session.scan, IDBM_SHOW, "manual", "auto", + num, 1); + __recinfo_int(SESSION_REOPEN_MAX, ri, r, + session.reopen_max, IDBM_SHOW, num, 1); + + for (i = 0; i < ISCSI_CONN_MAX; i++) { + char key[NAME_MAXVAL]; + + sprintf(key, CONN_ADDR, i); + __recinfo_str(key, ri, r, conn[i].address, IDBM_SHOW, num, 0); + sprintf(key, CONN_PORT, i); + __recinfo_int(key, ri, r, conn[i].port, IDBM_SHOW, num, 0); + sprintf(key, CONN_STARTUP, i); + __recinfo_int_o3(key, ri, r, conn[i].startup, IDBM_SHOW, + "manual", "automatic", "onboot", num, 1); + sprintf(key, CONN_WINDOW_SIZE, i); + __recinfo_int(key, ri, r, conn[i].tcp.window_size, + IDBM_SHOW, num, 1); + sprintf(key, CONN_SERVICE_TYPE, i); + __recinfo_int(key, ri, r, conn[i].tcp.type_of_service, + IDBM_SHOW, num, 1); + sprintf(key, CONN_LOGOUT_TMO, i); + __recinfo_int(key, ri, r, conn[i].timeo.logout_timeout, + IDBM_SHOW, num, 1); + sprintf(key, CONN_LOGIN_TMO, i); + __recinfo_int(key, ri, r, conn[i].timeo.login_timeout, + IDBM_SHOW, num, 1); + sprintf(key, CONN_AUTH_TMO, i); + __recinfo_int(key, ri, r, conn[i].timeo.auth_timeout, + IDBM_SHOW, num, 1); + + sprintf(key, CONN_NOP_INT, i); + __recinfo_int(key, ri, r, conn[i].timeo.noop_out_interval, + IDBM_SHOW, num, 1); + sprintf(key, CONN_NOP_TMO, i); + __recinfo_int(key, ri, r, conn[i].timeo.noop_out_timeout, + IDBM_SHOW, num, 1); + + sprintf(key, CONN_MAX_XMIT_DLEN, i); + __recinfo_int(key, ri, r, + conn[i].iscsi.MaxXmitDataSegmentLength, IDBM_SHOW, + num, 1); + sprintf(key, CONN_MAX_RECV_DLEN, i); + __recinfo_int(key, ri, r, + conn[i].iscsi.MaxRecvDataSegmentLength, IDBM_SHOW, + num, 1); + sprintf(key, CONN_HDR_DIGEST, i); + __recinfo_int_o4(key, ri, r, conn[i].iscsi.HeaderDigest, + IDBM_SHOW, "None", "CRC32C", "CRC32C,None", + "None,CRC32C", num, 1); + sprintf(key, CONN_DATA_DIGEST, i); + __recinfo_int_o4(key, ri, r, conn[i].iscsi.DataDigest, IDBM_SHOW, + "None", "CRC32C", "CRC32C,None", + "None,CRC32C", num, 1); + sprintf(key, CONN_IFMARKER, i); + __recinfo_int_o2(key, ri, r, conn[i].iscsi.IFMarker, IDBM_SHOW, + "No", "Yes", num, 1); + sprintf(key, CONN_OFMARKER, i); + __recinfo_int_o2(key, ri, r, conn[i].iscsi.OFMarker, IDBM_SHOW, + "No", "Yes", num, 1); + } +} + +void idbm_recinfo_iface(iface_rec_t *r, recinfo_t *ri) +{ + int num = 0; + int iface_type; + + iface_type = iface_get_iptype(r); + + __recinfo_str(IFACE_ISCSINAME, ri, r, name, IDBM_SHOW, num, 0); + __recinfo_str(IFACE_NETNAME, ri, r, netdev, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_IPADDR, ri, r, ipaddress, IDBM_SHOW, num, 1); + __recinfo_uint8(IFACE_PREFIX_LEN, ri, r, prefix_len, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_HWADDR, ri, r, hwaddress, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_TRANSPORTNAME, ri, r, transport_name, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_INAME, ri, r, iname, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_STATE, ri, r, state, IDBM_SHOW, num, 1); + __recinfo_uint16(IFACE_VLAN_ID, ri, r, vlan_id, IDBM_SHOW, num, 1); + __recinfo_uint8(IFACE_VLAN_PRIORITY, ri, r, vlan_priority, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_VLAN_STATE, ri, r, vlan_state, IDBM_SHOW, num, 1); + __recinfo_int(IFACE_NUM, ri, r, iface_num, IDBM_SHOW, num, 1); + __recinfo_uint16(IFACE_MTU, ri, r, mtu, IDBM_SHOW, num, 1); + __recinfo_uint16(IFACE_PORT, ri, r, port, IDBM_SHOW, num, 1); + + if (iface_type == ISCSI_IFACE_TYPE_IPV4) { + __recinfo_str(IFACE_BOOT_PROTO, ri, r, bootproto, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_SUBNET_MASK, ri, r, subnet_mask, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_GATEWAY, ri, r, gateway, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_DHCP_ALT_CID, ri, r, + dhcp_alt_client_id_state, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_DHCP_ALT_CID_STR, ri, r, dhcp_alt_client_id, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_DHCP_DNS, ri, r, dhcp_dns, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_DHCP_LEARN_IQN, ri, r, dhcp_learn_iqn, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_DHCP_REQ_VID, ri, r, + dhcp_req_vendor_id_state, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_DHCP_VID, ri, r, dhcp_vendor_id_state, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_DHCP_VID_STR, ri, r, dhcp_vendor_id, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_DHCP_SLP_DA, ri, r, dhcp_slp_da, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_FRAGMENTATION, ri, r, fragmentation, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_GRAT_ARP, ri, r, gratuitous_arp, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_IN_FORWARD, ri, r, incoming_forwarding, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_TOS_STATE, ri, r, tos_state, IDBM_SHOW, + num, 1); + __recinfo_uint8(IFACE_TOS, ri, r, tos, IDBM_SHOW, num, 1); + __recinfo_uint8(IFACE_TTL, ri, r, ttl, IDBM_SHOW, num, 1); + } else if (iface_type == ISCSI_IFACE_TYPE_IPV6) { + __recinfo_str(IFACE_IPV6_AUTOCFG, ri, r, ipv6_autocfg, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_LINKLOCAL_AUTOCFG, ri, r, linklocal_autocfg, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_ROUTER_AUTOCFG, ri, r, router_autocfg, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_LINKLOCAL, ri, r, ipv6_linklocal, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_ROUTER, ri, r, ipv6_router, IDBM_SHOW, + num, 1); + __recinfo_uint8(IFACE_DUP_ADDR_DETECT_CNT, ri, r, + dup_addr_detect_cnt, IDBM_SHOW, num, 1); + __recinfo_uint32(IFACE_FLOW_LABEL, ri, r, flow_label, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_GRAT_NEIGHBOR_ADV, ri, r, + gratuitous_neighbor_adv, IDBM_SHOW, num, 1); + __recinfo_uint8(IFACE_HOP_LIMIT, ri, r, hop_limit, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_MLD, ri, r, mld, IDBM_SHOW, num, 1); + __recinfo_uint32(IFACE_ND_REACHABLE_TMO, ri, r, + nd_reachable_tmo, IDBM_SHOW, num, 1); + __recinfo_uint32(IFACE_ND_REXMIT_TIME, ri, r, nd_rexmit_time, + IDBM_SHOW, num, 1); + __recinfo_uint32(IFACE_ND_STALE_TMO, ri, r, nd_stale_tmo, + IDBM_SHOW, num, 1); + __recinfo_uint32(IFACE_RTR_ADV_LINK_MTU, ri, r, + router_adv_link_mtu, IDBM_SHOW, num, 1); + __recinfo_uint8(IFACE_TRAFFIC_CLASS, ri, r, traffic_class, + IDBM_SHOW, num, 1); + } + + __recinfo_str(IFACE_DELAYED_ACK, ri, r, delayed_ack, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_TCP_NAGLE, ri, r, nagle, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_TCP_WSF_STATE, ri, r, tcp_wsf_state, IDBM_SHOW, + num, 1); + __recinfo_uint8(IFACE_TCP_WSF, ri, r, tcp_wsf, IDBM_SHOW, num, 1); + __recinfo_uint8(IFACE_TCP_TIMER_SCALE, ri, r, tcp_timer_scale, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_TCP_TIMESTAMP, ri, r, tcp_timestamp, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_REDIRECT, ri, r, redirect, IDBM_SHOW, num, 1); + __recinfo_uint16(IFACE_DEF_TMF_TMO, ri, r, def_task_mgmt_tmo, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_HDRDGST, ri, r, header_digest, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_DATADGST, ri, r, data_digest, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_IMM_DATA, ri, r, immediate_data, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_INITIAL_R2T, ri, r, initial_r2t, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_DSEQ_INORDER, ri, r, data_seq_inorder, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_DPDU_INORDER, ri, r, data_pdu_inorder, IDBM_SHOW, + num, 1); + __recinfo_uint8(IFACE_ERL, ri, r, erl, IDBM_SHOW, num, 1); + __recinfo_uint32(IFACE_MAX_RECV_DLEN, ri, r, max_recv_dlength, + IDBM_SHOW, num, 1); + __recinfo_uint32(IFACE_FIRST_BURST, ri, r, first_burst_len, IDBM_SHOW, + num, 1); + __recinfo_uint16(IFACE_MAX_R2T, ri, r, max_out_r2t, IDBM_SHOW, num, 1); + __recinfo_uint32(IFACE_MAX_BURST, ri, r, max_burst_len, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_CHAP_AUTH, ri, r, chap_auth, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_BIDI_CHAP, ri, r, bidi_chap, IDBM_SHOW, num, 1); + __recinfo_str(IFACE_STRICT_LOGIN_COMP, ri, r, strict_login_comp, + IDBM_SHOW, num, 1); + __recinfo_str(IFACE_DISCOVERY_AUTH, ri, r, discovery_auth, IDBM_SHOW, + num, 1); + __recinfo_str(IFACE_DISCOVERY_LOGOUT, ri, r, discovery_logout, + IDBM_SHOW, num, 1); +} + +void idbm_recinfo_host_chap(struct iscsi_chap_rec *r, recinfo_t *ri) +{ + int num = 0; + + __recinfo_uint16(HOST_AUTH_INDEX, ri, r, chap_tbl_idx, IDBM_SHOW, + num, 1); + + if (r->chap_type == CHAP_TYPE_OUT) { + __recinfo_str(HOST_AUTH_USERNAME, ri, r, username, IDBM_SHOW, + num, 1); + __recinfo_str(HOST_AUTH_PASSWORD, ri, r, password, IDBM_MASKED, + num, 1); + __recinfo_int(HOST_AUTH_PASSWORD_LEN, ri, r, password_length, + IDBM_HIDE, num, 1); + } else { + __recinfo_str(HOST_AUTH_USERNAME_IN, ri, r, username, IDBM_SHOW, + num, 1); + __recinfo_str(HOST_AUTH_PASSWORD_IN, ri, r, password, + IDBM_MASKED, num, 1); + __recinfo_int(HOST_AUTH_PASSWORD_IN_LEN, ri, r, password_length, + IDBM_HIDE, num, 1); + } +} + +void idbm_recinfo_flashnode(struct flashnode_rec *r, recinfo_t *ri) +{ + int num = 0; + int i; + + __recinfo_uint8(FLASHNODE_SESS_AUTO_SND_TGT_DISABLE, ri, r, + sess.auto_snd_tgt_disable, IDBM_SHOW, num, 1); + __recinfo_uint8(FLASHNODE_SESS_DISCOVERY_SESS, ri, r, + sess.discovery_session, IDBM_SHOW, num, 1); + __recinfo_str(FLASHNODE_SESS_PORTAL_TYPE, ri, r, sess.portal_type, + IDBM_SHOW, num, 1); + __recinfo_uint8(FLASHNODE_SESS_ENTRY_EN, ri, r, + sess.entry_enable, IDBM_SHOW, num, 1); + __recinfo_uint8(FLASHNODE_SESS_IMM_DATA_EN, ri, r, sess.immediate_data, + IDBM_SHOW, num, 1); + __recinfo_uint8(FLASHNODE_SESS_INITIAL_R2T_EN, ri, r, sess.initial_r2t, + IDBM_SHOW, num, 1); + __recinfo_uint8(FLASHNODE_SESS_DATASEQ_INORDER, ri, r, + sess.data_seq_in_order, IDBM_SHOW, num, 1); + __recinfo_uint8(FLASHNODE_SESS_PDU_INORDER, ri, r, + sess.data_pdu_in_order, IDBM_SHOW, num, 1); + __recinfo_uint8(FLASHNODE_SESS_CHAP_AUTH_EN, ri, r, sess.chap_auth_en, + IDBM_SHOW, num, 1); + __recinfo_uint8(FLASHNODE_SESS_DISCOVERY_LOGOUT_EN, ri, r, + sess.discovery_logout_en, IDBM_SHOW, num, 1); + __recinfo_uint8(FLASHNODE_SESS_BIDI_CHAP_EN, ri, r, sess.bidi_chap_en, + IDBM_SHOW, num, 1); + __recinfo_uint8(FLASHNODE_SESS_DISCOVERY_AUTH_OPTIONAL, ri, r, + sess.discovery_auth_optional, IDBM_SHOW, num, 1); + __recinfo_uint8(FLASHNODE_SESS_ERL, ri, r, sess.erl, IDBM_SHOW, num, 1); + __recinfo_uint32(FLASHNODE_SESS_FIRST_BURST, ri, r, + sess.first_burst_len, IDBM_SHOW, num, 1); + __recinfo_uint16(FLASHNODE_SESS_DEF_TIME2WAIT, ri, r, + sess.def_time2wait, IDBM_SHOW, num, 1); + __recinfo_uint16(FLASHNODE_SESS_DEF_TIME2RETAIN, ri, r, + sess.def_time2retain, IDBM_SHOW, num, 1); + __recinfo_uint16(FLASHNODE_SESS_MAX_R2T, ri, r, + sess.max_outstanding_r2t, IDBM_SHOW, num, 1); + __recinfo_str(FLASHNODE_SESS_ISID, ri, r, sess.isid, IDBM_SHOW, num, 1); + __recinfo_uint16(FLASHNODE_SESS_TSID, ri, r, sess.tsid, IDBM_SHOW, + num, 1); + __recinfo_uint32(FLASHNODE_SESS_MAX_BURST, ri, r, sess.max_burst_len, + IDBM_SHOW, num, 1); + __recinfo_uint16(FLASHNODE_SESS_DEF_TASKMGMT_TMO, ri, r, + sess.def_taskmgmt_tmo, IDBM_SHOW, num, 1); + __recinfo_str(FLASHNODE_SESS_ALIAS, ri, r, sess.targetalias, IDBM_SHOW, + num, 1); + __recinfo_str(FLASHNODE_SESS_NAME, ri, r, sess.targetname, IDBM_SHOW, + num, 1); + __recinfo_uint16(FLASHNODE_SESS_DISCOVERY_PARENT_IDX, ri, r, + sess.discovery_parent_idx, IDBM_SHOW, num, 1); + __recinfo_str(FLASHNODE_SESS_DISCOVERY_PARENT_TYPE, ri, r, + sess.discovery_parent_type, IDBM_SHOW, num, 1); + __recinfo_uint16(FLASHNODE_SESS_TPGT, ri, r, sess.tpgt, IDBM_SHOW, + num, 1); + __recinfo_uint16(FLASHNODE_SESS_CHAP_OUT_IDX, ri, r, sess.chap_out_idx, + IDBM_SHOW, num, 1); + __recinfo_uint16(FLASHNODE_SESS_CHAP_IN_IDX, ri, r, sess.chap_in_idx, + IDBM_SHOW, num, 1); + __recinfo_str(FLASHNODE_SESS_USERNAME, ri, r, sess.username, IDBM_SHOW, + num, 1); + __recinfo_str(FLASHNODE_SESS_USERNAME_IN, ri, r, sess.username_in, + IDBM_SHOW, num, 1); + __recinfo_str(FLASHNODE_SESS_PASSWORD, ri, r, sess.password, IDBM_SHOW, + num, 1); + __recinfo_str(FLASHNODE_SESS_PASSWORD_IN, ri, r, sess.password_in, + IDBM_SHOW, num, 1); + __recinfo_uint8(FLASHNODE_SESS_IS_BOOT_TGT, ri, r, sess.is_boot_target, + IDBM_SHOW, num, 1); + + for (i = 0; i < ISCSI_CONN_MAX; i++) { + char key[NAME_MAXVAL]; + + sprintf(key, FLASHNODE_CONN_IS_FW_ASSIGNED_IPV6, i); + __recinfo_uint8(key, ri, r, conn[i].is_fw_assigned_ipv6, + IDBM_SHOW, num, 1); + sprintf(key, FLASHNODE_CONN_HDR_DGST_EN, i); + __recinfo_uint8(key, ri, r, conn[i].header_digest_en, IDBM_SHOW, + num, 1); + sprintf(key, FLASHNODE_CONN_DATA_DGST_EN, i); + __recinfo_uint8(key, ri, r, conn[i].data_digest_en, IDBM_SHOW, + num, 1); + sprintf(key, FLASHNODE_CONN_SNACK_REQ_EN, i); + __recinfo_uint8(key, ri, r, conn[i].snack_req_en, IDBM_SHOW, + num, 1); + sprintf(key, FLASHNODE_CONN_TCP_TIMESTAMP_STAT, i); + __recinfo_uint8(key, ri, r, conn[i].tcp_timestamp_stat, + IDBM_SHOW, num, 1); + sprintf(key, FLASHNODE_CONN_TCP_NAGLE_DISABLE, i); + __recinfo_uint8(key, ri, r, conn[i].tcp_nagle_disable, + IDBM_SHOW, num, 1); + sprintf(key, FLASHNODE_CONN_TCP_WSF_DISABLE, i); + __recinfo_uint8(key, ri, r, conn[i].tcp_wsf_disable, IDBM_SHOW, + num, 1); + sprintf(key, FLASHNODE_CONN_TCP_TIMER_SCALE, i); + __recinfo_uint8(key, ri, r, conn[i].tcp_timer_scale, IDBM_SHOW, + num, 1); + sprintf(key, FLASHNODE_CONN_TCP_TIMESTAMP_EN, i); + __recinfo_uint8(key, ri, r, conn[i].tcp_timestamp_en, + IDBM_SHOW, num, 1); + sprintf(key, FLASHNODE_CONN_IP_FRAG_DISABLE, i); + __recinfo_uint8(key, ri, r, conn[i].fragment_disable, IDBM_SHOW, + num, 1); + sprintf(key, FLASHNODE_CONN_MAX_XMIT_DLENGTH, i); + __recinfo_uint32(key, ri, r, conn[i].max_xmit_dlength, + IDBM_SHOW, num, 1); + sprintf(key, FLASHNODE_CONN_MAX_RECV_DLENGTH, i); + __recinfo_uint32(key, ri, r, conn[i].max_recv_dlength, + IDBM_SHOW, num, 1); + sprintf(key, FLASHNODE_CONN_KEEPALIVE_TMO, i); + __recinfo_uint16(key, ri, r, conn[i].keepalive_tmo, IDBM_SHOW, + num, 1); + sprintf(key, FLASHNODE_CONN_PORT, i); + __recinfo_uint16(key, ri, r, conn[i].port, IDBM_SHOW, num, 1); + sprintf(key, FLASHNODE_CONN_IPADDR, i); + __recinfo_str(key, ri, r, conn[i].ipaddress, IDBM_SHOW, num, 1); + sprintf(key, FLASHNODE_CONN_REDIRECT_IPADDR, i); + __recinfo_str(key, ri, r, conn[i].redirect_ipaddr, IDBM_SHOW, + num, 1); + sprintf(key, FLASHNODE_CONN_MAX_SEGMENT_SIZE, i); + __recinfo_uint32(key, ri, r, conn[i].max_segment_size, + IDBM_SHOW, num, 1); + sprintf(key, FLASHNODE_CONN_LOCAL_PORT, i); + __recinfo_uint16(key, ri, r, conn[i].local_port, IDBM_SHOW, + num, 1); + sprintf(key, FLASHNODE_CONN_IPV4_TOS, i); + __recinfo_uint8(key, ri, r, conn[i].ipv4_tos, IDBM_SHOW, + num, 1); + sprintf(key, FLASHNODE_CONN_IPV6_TC, i); + __recinfo_uint8(key, ri, r, conn[i].ipv6_traffic_class, + IDBM_SHOW, num, 1); + sprintf(key, FLASHNODE_CONN_IPV6_FLOW_LABEL, i); + __recinfo_uint16(key, ri, r, conn[i].ipv6_flow_lbl, IDBM_SHOW, + num, 1); + sprintf(key, FLASHNODE_CONN_LINK_LOCAL_IPV6, i); + __recinfo_str(key, ri, r, conn[i].link_local_ipv6, IDBM_SHOW, + num, 1); + sprintf(key, FLASHNODE_CONN_TCP_XMIT_WSF, i); + __recinfo_uint32(key, ri, r, conn[i].tcp_xmit_wsf, IDBM_SHOW, + num, 1); + sprintf(key, FLASHNODE_CONN_TCP_RECV_WSF, i); + __recinfo_uint32(key, ri, r, conn[i].tcp_recv_wsf, IDBM_SHOW, + num, 1); + sprintf(key, FLASHNODE_CONN_STATSN, i); + __recinfo_uint32(key, ri, r, conn[i].stat_sn, IDBM_SHOW, + num, 1); + sprintf(key, FLASHNODE_CONN_EXP_STATSN, i); + __recinfo_uint32(key, ri, r, conn[i].exp_stat_sn, IDBM_SHOW, + num, 1); + } +} + +recinfo_t *idbm_recinfo_alloc(int max_keys) +{ + recinfo_t *info; + + info = malloc(sizeof(recinfo_t)*max_keys); + if (!info) + return NULL; + memset(info, 0, sizeof(recinfo_t)*max_keys); + return info; +} + +void idbm_print(int type, void *rec, int show, FILE *f) +{ + int i; + recinfo_t *info; + + info = idbm_recinfo_alloc(MAX_KEYS); + if (!info) + return; + + switch (type) { + case IDBM_PRINT_TYPE_DISCOVERY: + idbm_recinfo_discovery((discovery_rec_t*)rec, info); + break; + case IDBM_PRINT_TYPE_NODE: + idbm_recinfo_node((node_rec_t*)rec, info); + break; + case IDBM_PRINT_TYPE_IFACE: + idbm_recinfo_iface((struct iface_rec *)rec, info); + break; + case IDBM_PRINT_TYPE_HOST_CHAP: + idbm_recinfo_host_chap((struct iscsi_chap_rec *)rec, info); + break; + case IDBM_PRINT_TYPE_FLASHNODE: + idbm_recinfo_flashnode((struct flashnode_rec *)rec, info); + break; + } + + fprintf(f, "%s\n", ISCSI_BEGIN_REC); + for (i = 0; i < MAX_KEYS; i++) { + if (!info[i].visible) + continue; + if (!show && info[i].visible == IDBM_MASKED) { + if (*(char*)info[i].data) { + fprintf(f, "%s = ********\n", info[i].name); + continue; + } + /* fall through */ + } + + if (strlen(info[i].value)) + fprintf(f, "%s = %s\n", info[i].name, info[i].value); + else if (f == stdout) + fprintf(f, "%s = \n", info[i].name); + } + fprintf(f, "%s\n", ISCSI_END_REC); + + free(info); +} + +static void +idbm_setup_session_defaults(struct iscsi_session_operational_config *conf) +{ + conf->InitialR2T = 0; + conf->ImmediateData = 1; + conf->FirstBurstLength = DEF_INI_FIRST_BURST_LEN; + conf->MaxBurstLength = DEF_INI_MAX_BURST_LEN; + conf->DefaultTime2Wait = ISCSI_DEF_TIME2WAIT; + conf->DefaultTime2Retain = 0; + conf->MaxConnections = 1; + conf->MaxOutstandingR2T = 1; + conf->ERL = 0; + conf->FastAbort = 1; +} + +static void idbm_setup_conn_defaults(struct iscsi_conn_operational_config *conf) +{ + conf->MaxXmitDataSegmentLength = 0; + conf->MaxRecvDataSegmentLength = DEF_INI_MAX_RECV_SEG_LEN; + conf->HeaderDigest = CONFIG_DIGEST_NEVER; + conf->DataDigest = CONFIG_DIGEST_NEVER; + conf->IFMarker = 0; + conf->OFMarker = 0; +} + +static void +idbm_discovery_setup_defaults(discovery_rec_t *rec, discovery_type_e type) +{ + memset(rec, 0, sizeof(discovery_rec_t)); + + rec->startup = ISCSI_STARTUP_MANUAL; + rec->type = type; + rec->iscsid_req_tmo = -1; + switch (type) { + case DISCOVERY_TYPE_SENDTARGETS: + rec->u.sendtargets.discoveryd_poll_inval = 30; + rec->u.sendtargets.use_discoveryd = 0; + rec->u.sendtargets.reopen_max = 5; + rec->u.sendtargets.auth.authmethod = 0; + rec->u.sendtargets.auth.password_length = 0; + rec->u.sendtargets.auth.password_in_length = 0; + rec->u.sendtargets.conn_timeo.login_timeout=15; + rec->u.sendtargets.conn_timeo.auth_timeout = 45; + rec->u.sendtargets.conn_timeo.active_timeout=30; + idbm_setup_session_defaults(&rec->u.sendtargets.session_conf); + idbm_setup_conn_defaults(&rec->u.sendtargets.conn_conf); + /* override def setting */ + rec->u.sendtargets.conn_conf.MaxRecvDataSegmentLength = + DEF_INI_DISC_MAX_RECV_SEG_LEN; + break; + case DISCOVERY_TYPE_SLP: + rec->u.slp.interfaces = NULL; + rec->u.slp.scopes = NULL; + rec->u.slp.poll_interval = 5 * 60; /* 5 minutes */ + rec->u.slp.auth.authmethod = 0; + rec->u.slp.auth.password_length = 0; + rec->u.slp.auth.password_in_length = 0; + rec->u.slp.auth.password_in_length = 0; + break; + case DISCOVERY_TYPE_ISNS: + rec->u.isns.use_discoveryd = 0; + rec->u.isns.discoveryd_poll_inval = -1; + break; + default: + break; + } +} + +int idbm_rec_update_param(recinfo_t *info, char *name, char *value, + int line_number) +{ + int i; + int passwd_done = 0; + char passwd_len[8]; + +setup_passwd_len: + for (i=0; i '%s'", name, + info[i].value, value); + /* parse recinfo by type */ + if (info[i].type == TYPE_INT) { + if (!info[i].data) + continue; + + *(int*)info[i].data = + strtoul(value, NULL, 10); + goto updated; + } else if (info[i].type == TYPE_UINT8) { + if (!info[i].data) + continue; + + *(uint8_t *)info[i].data = + strtoul(value, NULL, 10); + goto updated; + } else if (info[i].type == TYPE_UINT16) { + if (!info[i].data) + continue; + + *(uint16_t *)info[i].data = + strtoul(value, NULL, 10); + goto updated; + } else if (info[i].type == TYPE_UINT32) { + if (!info[i].data) + continue; + + *(uint32_t *)info[i].data = + strtoul(value, NULL, 10); + goto updated; + } else if (info[i].type == TYPE_STR) { + if (!info[i].data) + continue; + + strlcpy((char*)info[i].data, + value, info[i].data_len); + goto updated; + } + for (j=0; jnrec); + idbm_discovery_setup_defaults(&db->drec_st, DISCOVERY_TYPE_SENDTARGETS); + idbm_discovery_setup_defaults(&db->drec_slp, DISCOVERY_TYPE_SLP); + idbm_discovery_setup_defaults(&db->drec_isns, DISCOVERY_TYPE_ISNS); + + idbm_recinfo_discovery(&db->drec_st, db->dinfo_st); + idbm_recinfo_discovery(&db->drec_slp, db->dinfo_slp); + idbm_recinfo_discovery(&db->drec_isns, db->dinfo_isns); + idbm_recinfo_node(&db->nrec, db->ninfo); + + if (!db->get_config_file) { + log_debug(1, "Could not get config file. No config file fn"); + return; + } + + config_file = db->get_config_file(); + if (!config_file) { + log_debug(1, "Could not get config file for sync config"); + return; + } + + f = fopen(config_file, "r"); + if (!f) { + log_debug(1, "cannot open configuration file %s. " + "Default location is %s.", + config_file, CONFIG_FILE); + return; + } + log_debug(5, "updating defaults from '%s'", config_file); + + idbm_recinfo_config(db->dinfo_st, f); + idbm_recinfo_config(db->dinfo_slp, f); + idbm_recinfo_config(db->dinfo_isns, f); + idbm_recinfo_config(db->ninfo, f); + fclose(f); + + /* update password lengths */ + if (*db->drec_st.u.sendtargets.auth.password) + db->drec_st.u.sendtargets.auth.password_length = + strlen((char*)db->drec_st.u.sendtargets.auth.password); + if (*db->drec_st.u.sendtargets.auth.password_in) + db->drec_st.u.sendtargets.auth.password_in_length = + strlen((char*)db->drec_st.u.sendtargets.auth.password_in); + if (*db->drec_slp.u.slp.auth.password) + db->drec_slp.u.slp.auth.password_length = + strlen((char*)db->drec_slp.u.slp.auth.password); + if (*db->drec_slp.u.slp.auth.password_in) + db->drec_slp.u.slp.auth.password_in_length = + strlen((char*)db->drec_slp.u.slp.auth.password_in); + if (*db->nrec.session.auth.password) + db->nrec.session.auth.password_length = + strlen((char*)db->nrec.session.auth.password); + if (*db->nrec.session.auth.password_in) + db->nrec.session.auth.password_in_length = + strlen((char*)db->nrec.session.auth.password_in); +} + +void idbm_node_setup_from_conf(node_rec_t *rec) +{ + memset(rec, 0, sizeof(*rec)); + idbm_node_setup_defaults(rec); + idbm_sync_config(); + memcpy(rec, &db->nrec, sizeof(*rec)); +} + +int idbm_print_discovery_info(discovery_rec_t *rec, int show) +{ + idbm_print(IDBM_PRINT_TYPE_DISCOVERY, rec, show, stdout); + return 1; +} + +int idbm_print_node_info(void *data, node_rec_t *rec) +{ + int show = *((int *)data); + + idbm_print(IDBM_PRINT_TYPE_NODE, rec, show, stdout); + return 0; +} + +int idbm_print_host_chap_info(struct iscsi_chap_rec *chap) +{ + /* User only calls this to print chap so always print */ + idbm_print(IDBM_PRINT_TYPE_HOST_CHAP, chap, 1, stdout); + return 0; +} + +int idbm_print_flashnode_info(struct flashnode_rec *fnode) +{ + idbm_print(IDBM_PRINT_TYPE_FLASHNODE, fnode, 1, stdout); + return 0; +} + +int idbm_print_node_flat(void *data, node_rec_t *rec) +{ + if (strchr(rec->conn[0].address, '.')) + printf("%s:%d,%d %s\n", rec->conn[0].address, rec->conn[0].port, + rec->tpgt, rec->name); + else + printf("[%s]:%d,%d %s\n", rec->conn[0].address, + rec->conn[0].port, rec->tpgt, rec->name); + return 0; +} + +int idbm_print_node_tree(struct node_rec *last_rec, struct node_rec *rec, + char *prefix) +{ + if (!last_rec || strcmp(last_rec->name, rec->name)) { + printf("%sTarget: %s\n", prefix, rec->name); + if (last_rec) + memset(last_rec, 0, sizeof(node_rec_t)); + } + + if (!last_rec || + ((strcmp(last_rec->conn[0].address, rec->conn[0].address) || + last_rec->conn[0].port != rec->conn[0].port))) { + if (strchr(rec->conn[0].address, '.')) + printf("%s\tPortal: %s:%d,%d\n", prefix, + rec->conn[0].address, + rec->conn[0].port, rec->tpgt); + else + printf("%s\tPortal: [%s]:%d,%d\n", prefix, + rec->conn[0].address, + rec->conn[0].port, rec->tpgt); + } + + if (last_rec) + memcpy(last_rec, rec, sizeof(node_rec_t)); + return 0; +} + +int idbm_print_node_and_iface_tree(void *data, node_rec_t *rec) +{ + idbm_print_node_tree(data, rec, ""); + printf("\t\tIface Name: %s\n", rec->iface.name); + return 0; +} + +static int +get_params_from_disc_link(char *link, char **target, char **tpgt, + char **address, char **port, char **ifaceid) +{ + (*target) = link; + *address = strchr(*target, ','); + if (!(*address)) + return ISCSI_ERR_INVAL; + *(*address)++ = '\0'; + *port = strchr(*address, ','); + if (!(*port)) + return ISCSI_ERR_INVAL; + *(*port)++ = '\0'; + *tpgt = strchr(*port, ','); + if (!(*tpgt)) + return ISCSI_ERR_INVAL; + *(*tpgt)++ = '\0'; + *ifaceid = strchr(*tpgt, ','); + if (!(*ifaceid)) + return ISCSI_ERR_INVAL; + *(*ifaceid)++ = '\0'; + return 0; +} + +int idbm_lock(void) +{ + int fd, i, ret; + + if (db->refs > 0) { + db->refs++; + return 0; + } + + if (access(LOCK_DIR, F_OK) != 0) { + if (mkdir(LOCK_DIR, 0660) != 0) { + log_error("Could not open %s: %s", LOCK_DIR, + strerror(errno)); + return ISCSI_ERR_IDBM; + } + } + + fd = open(LOCK_FILE, O_RDWR | O_CREAT, 0666); + if (fd >= 0) + close(fd); + + for (i = 0; i < 3000; i++) { + ret = link(LOCK_FILE, LOCK_WRITE_FILE); + if (ret == 0) + break; + + if (errno != EEXIST) { + log_error("Maybe you are not root?"); + log_error("Could not lock discovery DB: %s: %s", + LOCK_WRITE_FILE, strerror(errno)); + return ISCSI_ERR_IDBM; + } else if (i == 0) + log_debug(2, "Waiting for discovery DB lock"); + + usleep(10000); + } + + db->refs = 1; + return 0; +} + +void idbm_unlock(void) +{ + if (db->refs > 1) { + db->refs--; + return; + } + + db->refs = 0; + unlink(LOCK_WRITE_FILE); +} + +/* + * Backwards Compat: + * If the portal is a file then we are doing the old style default + * session behavior (svn pre 780). + */ +static FILE *idbm_open_rec_r(char *portal, char *config) +{ + struct stat statb; + + log_debug(5, "Looking for config file %s config %s.", portal, config); + + if (stat(portal, &statb)) { + log_debug(5, "Could not stat %s err %d.", portal, errno); + return NULL; + } + + if (S_ISDIR(statb.st_mode)) { + strlcat(portal, "/", PATH_MAX); + strlcat(portal, config, PATH_MAX); + } + return fopen(portal, "r"); +} + +/* + * When the disable_lock param is true, the idbm_lock/idbm_unlock needs + * to be holt by the caller, this will avoid overwriting each other in + * case of updating(read-modify-write) the recs in parallel. + */ +static int __idbm_rec_read(node_rec_t *out_rec, char *conf, bool disable_lock) +{ + recinfo_t *info; + FILE *f; + int rc = 0; + + info = idbm_recinfo_alloc(MAX_KEYS); + if (!info) + return ISCSI_ERR_NOMEM; + + if (!disable_lock) { + rc = idbm_lock(); + if (rc) + goto free_info; + } + + f = fopen(conf, "r"); + if (!f) { + log_debug(5, "Could not open %s err %s", conf, + strerror(errno)); + rc = ISCSI_ERR_IDBM; + goto unlock; + } + + memset(out_rec, 0, sizeof(*out_rec)); + idbm_node_setup_defaults(out_rec); + idbm_recinfo_node(out_rec, info); + idbm_recinfo_config(info, f); + fclose(f); + +unlock: + if (!disable_lock) + idbm_unlock(); +free_info: + free(info); + return rc; +} + +/* + * When the disable_lock param is true, the idbm_lock/idbm_unlock needs + * to be holt by the caller, this will avoid overwriting each other in + * case of updating(read-modify-write) the recs in parallel. + */ +int +idbm_rec_read(node_rec_t *out_rec, char *targetname, int tpgt, + char *ip, int port, struct iface_rec *iface, bool disable_lock) +{ + struct stat statb; + char *portal; + int rc; + + portal = calloc(1, PATH_MAX); + if (!portal) + return ISCSI_ERR_IDBM; + + /* try old style portal as config */ + snprintf(portal, PATH_MAX, "%s/%s/%s,%d", NODE_CONFIG_DIR, + targetname, ip, port); + log_debug(5, "rec read looking for config file %s.", portal); + if (!stat(portal, &statb)) + goto read; + + snprintf(portal, PATH_MAX, "%s/%s/%s,%d,%d/%s", NODE_CONFIG_DIR, + targetname, ip, port, tpgt, iface->name); + log_debug(5, "rec read looking for config file %s.", portal); + if (!strlen(iface->name)) { + rc = ISCSI_ERR_INVAL; + goto free_portal; + } + + if (stat(portal, &statb)) { + log_debug(5, "Could not stat %s: %s.", portal, strerror(errno)); + free(portal); + return ISCSI_ERR_IDBM; + } + +read: + rc = __idbm_rec_read(out_rec, portal, disable_lock); +free_portal: + free(portal); + return rc; +} + +static int print_discovered_flat(void *data, node_rec_t *rec) +{ + struct discovery_rec *drec = data; + + if (rec->disc_type != drec->type) + goto no_match; + + if (drec->type == DISCOVERY_TYPE_SENDTARGETS || + drec->type == DISCOVERY_TYPE_ISNS) { + if (rec->disc_port != drec->port || + strcmp(rec->disc_address, drec->address)) + goto no_match; + } + + idbm_print_node_flat(NULL, rec); + return 0; +no_match: + return -1; +} + +struct discovered_tree_info { + struct discovery_rec *drec; + struct node_rec *last_rec; +}; + +static int print_discovered_tree(void *data, node_rec_t *rec) +{ + struct discovered_tree_info *tree_info = data; + struct discovery_rec *drec = tree_info->drec; + + if (rec->disc_type != drec->type) + goto no_match; + + if (strlen(drec->address)) { + if (rec->disc_port != drec->port || + strcmp(rec->disc_address, drec->address)) + goto no_match; + } + + idbm_print_node_and_iface_tree(tree_info->last_rec, rec); + return 0; +no_match: + return -1; +} + +static int idbm_print_discovered(discovery_rec_t *drec, int info_level) +{ + int num_found = 0; + + if (info_level < 1) + idbm_for_each_rec(&num_found, drec, print_discovered_flat, false); + else { + struct discovered_tree_info tree_info; + struct node_rec last_rec; + + memset(&last_rec, 0, sizeof(struct node_rec)); + + tree_info.drec = drec; + tree_info.last_rec = &last_rec; + + idbm_for_each_rec(&num_found, &tree_info, print_discovered_tree, false); + } + return num_found; +} + +static int idbm_for_each_drec(int type, char *config_root, void *data, + idbm_drec_op_fn *fn) +{ + DIR *entity_dirfd; + struct dirent *entity_dent; + int found = 0; + discovery_rec_t drec; + char *tmp_port; + + entity_dirfd = opendir(config_root); + if (!entity_dirfd) + return found; + + while ((entity_dent = readdir(entity_dirfd))) { + if (!strcmp(entity_dent->d_name, ".") || + !strcmp(entity_dent->d_name, "..")) + continue; + + log_debug(5, "found %s", entity_dent->d_name); + + tmp_port = strchr(entity_dent->d_name, ','); + if (!tmp_port) + continue; + /* + * pre 872 tools dumped the target portal symlinks in the isns + * dir instead of the server. If we find one of those links + * (by checking if there is a valid port) we skip it. + */ + if (strchr(tmp_port, ':') || strchr(tmp_port, '.')) + continue; + *tmp_port++ = '\0'; + + memset(&drec, 0, sizeof(drec)); + if (idbm_discovery_read(&drec, type, entity_dent->d_name, + atoi(tmp_port))) { + log_error("Could not read discovery record for " + "%s:%s.", entity_dent->d_name, tmp_port); + continue; + } + + if (!fn(data, &drec)) + found++; + } + closedir(entity_dirfd); + return found; +} + +int idbm_for_each_st_drec(void *data, idbm_drec_op_fn *fn) +{ + return idbm_for_each_drec(DISCOVERY_TYPE_SENDTARGETS, ST_CONFIG_DIR, + data, fn); +} + +int idbm_for_each_isns_drec(void *data, idbm_drec_op_fn *fn) +{ + return idbm_for_each_drec(DISCOVERY_TYPE_ISNS, ISNS_CONFIG_DIR, + data, fn); +} + +static int __idbm_print_all_by_drec(void *data, struct discovery_rec *drec) +{ + int info_level = *(int *)data; + + if (info_level >= 1) { + printf("DiscoveryAddress: %s,%d\n", + drec->address, drec->port); + idbm_print_discovered(drec, info_level); + } else + printf("%s:%d via %s\n", drec->address, drec->port, + drec->type == DISCOVERY_TYPE_ISNS ? + "isns" : "sendtargets"); + return 0; +} + +static int idbm_print_all_st(int info_level) +{ + int rc; + + rc = idbm_for_each_st_drec(&info_level, __idbm_print_all_by_drec); + if (rc < 0) + return 0; + return rc; +} + +static int idbm_print_all_isns(int info_level) +{ + int rc; + + rc = idbm_for_each_isns_drec(&info_level, __idbm_print_all_by_drec); + if (rc < 0) + return 0; + return rc; +} + +int idbm_print_all_discovery(int info_level) +{ + discovery_rec_t *drec; + int found = 0, tmp; + + if (info_level < 1) { + found = idbm_print_all_st(info_level); + found += idbm_print_all_isns(info_level); + return found; + } + + drec = calloc(1, sizeof(*drec)); + if (!drec) + return 0; + + tmp = 0; + printf("SENDTARGETS:\n"); + tmp = idbm_print_all_st(info_level); + if (!tmp) + printf("No targets found.\n"); + found += tmp; + tmp = 0; + + printf("iSNS:\n"); + tmp = idbm_print_all_isns(info_level); + if (!tmp) { + /* + * pre 872 tools did not store the server ip,port so + * we drop down here, to just look for target portals. + */ + drec->type = DISCOVERY_TYPE_ISNS; + tmp = idbm_print_discovered(drec, info_level); + if (!tmp) + printf("No targets found.\n"); + } + found += tmp; + tmp = 0; + + printf("STATIC:\n"); + drec->type = DISCOVERY_TYPE_STATIC; + tmp = idbm_print_discovered(drec, info_level); + if (!tmp) + printf("No targets found.\n"); + found += tmp; + tmp = 0; + + printf("FIRMWARE:\n"); + drec->type = DISCOVERY_TYPE_FW; + tmp = idbm_print_discovered(drec, info_level); + if (!tmp) + printf("No targets found.\n"); + found += tmp; + + free(drec); + return found; +} + +/** + * idbm_for_each_iface - iterate over bound iface recs + * @found: nr of recs found so far + * @data: data pointer passed to fn + * @fn: iterator function ran over each bound iface rec + * @targetname: rec's target name + * @tpgt: rec's portal group tag + * @ip: rec's ip address + * @port: rec's port + * + * This will run fn over all recs with the {targetname,tpgt,ip,port} + * id. It does not iterate over the ifaces setup in /etc/iscsi/ifaces. + * + * fn should return -1 if it skipped the rec, an ISCSI_ERR error code if + * the operation failed or 0 if fn was run successfully. + */ +static int idbm_for_each_iface(int *found, void *data, idbm_iface_op_fn *fn, + char *targetname, int tpgt, char *ip, int port, + bool ruw_lock) +{ + DIR *iface_dirfd; + struct dirent *iface_dent; + struct stat statb; + node_rec_t rec; + int rc = 0; + char *portal; + + portal = calloc(1, PATH_MAX); + if (!portal) + return ISCSI_ERR_NOMEM; + + if (tpgt >= 0) + goto read_iface; + + /* old style portal as a config */ + snprintf(portal, PATH_MAX, "%s/%s/%s,%d", NODE_CONFIG_DIR, targetname, + ip, port); + if (stat(portal, &statb)) { + log_error("iface iter could not stat %s.", portal); + rc = ISCSI_ERR_IDBM; + goto free_portal; + } + + rc = __idbm_rec_read(&rec, portal, ruw_lock); + if (rc) + goto free_portal; + + rc = fn(data, &rec); + if (!rc) + (*found)++; + else if (rc == -1) + rc = 0; + goto free_portal; + +read_iface: + snprintf(portal, PATH_MAX, "%s/%s/%s,%d,%d", NODE_CONFIG_DIR, + targetname, ip, port, tpgt); + + iface_dirfd = opendir(portal); + if (!iface_dirfd) { + log_error("iface iter could not read dir %s.", portal); + rc = ISCSI_ERR_IDBM; + goto free_portal; + } + + while ((iface_dent = readdir(iface_dirfd))) { + int curr_rc; + + if (!strcmp(iface_dent->d_name, ".") || + !strcmp(iface_dent->d_name, "..")) + continue; + + log_debug(5, "iface iter found %s.", iface_dent->d_name); + memset(portal, 0, PATH_MAX); + snprintf(portal, PATH_MAX, "%s/%s/%s,%d,%d/%s", NODE_CONFIG_DIR, + targetname, ip, port, tpgt, iface_dent->d_name); + if (__idbm_rec_read(&rec, portal, ruw_lock)) + continue; + + curr_rc = fn(data, &rec); + /* less than zero means it was not a match */ + if (curr_rc > 0 && !rc) + rc = curr_rc; + else if (curr_rc == 0) + (*found)++; + } + + closedir(iface_dirfd); +free_portal: + free(portal); + return rc; +} + +/* + * backwards compat + * The portal could be a file or dir with interfaces + */ +int idbm_for_each_portal(int *found, void *data, idbm_portal_op_fn *fn, + char *targetname, bool ruw_lock) +{ + DIR *portal_dirfd; + struct dirent *portal_dent; + int rc = 0; + char *portal; + + portal = calloc(1, PATH_MAX); + if (!portal) + return ISCSI_ERR_NOMEM; + + snprintf(portal, PATH_MAX, "%s/%s", NODE_CONFIG_DIR, targetname); + portal_dirfd = opendir(portal); + if (!portal_dirfd) { + rc = ISCSI_ERR_IDBM; + goto done; + } + + while ((portal_dent = readdir(portal_dirfd))) { + char *tmp_port, *tmp_tpgt; + int curr_rc; + + if (!strcmp(portal_dent->d_name, ".") || + !strcmp(portal_dent->d_name, "..")) + continue; + + log_debug(5, "found %s", portal_dent->d_name); + tmp_port = strchr(portal_dent->d_name, ','); + if (!tmp_port) + continue; + *tmp_port++ = '\0'; + tmp_tpgt = strchr(tmp_port, ','); + if (tmp_tpgt) + *tmp_tpgt++ = '\0'; + + curr_rc = fn(found, data, targetname, + tmp_tpgt ? atoi(tmp_tpgt) : -1, + portal_dent->d_name, atoi(tmp_port), + ruw_lock); + /* less than zero means it was not a match */ + if (curr_rc > 0 && !rc) + rc = curr_rc; + } + closedir(portal_dirfd); +done: + free(portal); + return rc; +} + +int idbm_for_each_node(int *found, void *data, idbm_node_op_fn *fn, bool ruw_lock) +{ + DIR *node_dirfd; + struct dirent *node_dent; + int rc = 0; + + *found = 0; + + node_dirfd = opendir(NODE_CONFIG_DIR); + if (!node_dirfd) + /* on start up node dir may not be created */ + return 0; + + while ((node_dent = readdir(node_dirfd))) { + int curr_rc; + + if (!strcmp(node_dent->d_name, ".") || + !strcmp(node_dent->d_name, "..")) + continue; + + log_debug(5, "searching %s", node_dent->d_name); + curr_rc = fn(found, data, node_dent->d_name, ruw_lock); + /* less than zero means it was not a match */ + if (curr_rc > 0 && !rc) + rc = curr_rc; + } + + closedir(node_dirfd); + return rc; +} + +static int iface_fn(void *data, node_rec_t *rec) +{ + struct rec_op_data *op_data = data; + + return op_data->fn(op_data->data, rec); +} + +static int portal_fn(int *found, void *data, char *targetname, + int tpgt, char *ip, int port, bool ruw_lock) +{ + int rc; + + if (ruw_lock) { + rc = idbm_lock(); + if (rc) + return rc; + } + + rc = idbm_for_each_iface(found, data, iface_fn, targetname, + tpgt, ip, port, ruw_lock); + if (ruw_lock) + idbm_unlock(); + + return rc; +} + +static int node_fn(int *found, void *data, char *targetname, bool ruw_lock) +{ + return idbm_for_each_portal(found, data, portal_fn, targetname, ruw_lock); +} + +int idbm_for_each_rec(int *found, void *data, idbm_iface_op_fn *fn, bool ruw_lock) +{ + struct rec_op_data op_data; + + memset(&op_data, 0, sizeof(struct rec_op_data)); + op_data.data = data; + op_data.fn = fn; + + return idbm_for_each_node(found, &op_data, node_fn, ruw_lock); +} + +static struct { + char *config_root; + char *config_name; +} disc_type_to_config_vals[] = { + { ST_CONFIG_DIR, ST_CONFIG_NAME }, + { ISNS_CONFIG_DIR, ISNS_CONFIG_NAME }, +}; + +int +idbm_discovery_read(discovery_rec_t *out_rec, int drec_type, + char *addr, int port) +{ + recinfo_t *info; + char *portal; + int rc = 0; + FILE *f; + + if (drec_type > 1) + return ISCSI_ERR_INVAL; + + memset(out_rec, 0, sizeof(discovery_rec_t)); + out_rec->iscsid_req_tmo = -1; + + info = idbm_recinfo_alloc(MAX_KEYS); + if (!info) + return ISCSI_ERR_NOMEM; + + portal = malloc(PATH_MAX); + if (!portal) { + rc = ISCSI_ERR_NOMEM; + goto free_info; + } + + snprintf(portal, PATH_MAX, "%s/%s,%d", + disc_type_to_config_vals[drec_type].config_root, + addr, port); + log_debug(5, "Looking for config file %s", portal); + + rc = idbm_lock(); + if (rc) + goto free_info; + + f = idbm_open_rec_r(portal, + disc_type_to_config_vals[drec_type].config_name); + if (!f) { + log_debug(1, "Could not open %s: %s", portal, + strerror(errno)); + rc = ISCSI_ERR_IDBM; + goto unlock; + } + + idbm_discovery_setup_defaults(out_rec, drec_type); + idbm_recinfo_discovery(out_rec, info); + idbm_recinfo_config(info, f); + fclose(f); + +unlock: + idbm_unlock(); +free_info: + free(portal); + free(info); + return rc; +} + +/* + * Backwards Compat: + * If the portal is a file then we are doing the old style default + * session behavior (svn pre 780). + */ +static FILE *idbm_open_rec_w(char *portal, char *config) +{ + struct stat statb; + FILE *f; + int err; + + log_debug(5, "Looking for config file %s", portal); + + err = stat(portal, &statb); + if (err) + goto mkdir_portal; + + if (!S_ISDIR(statb.st_mode)) { + /* + * Old style portal as a file. Let's update it. + */ + if (unlink(portal)) { + log_error("Could not convert %s to %s/%s. " + "err %d", portal, portal, + config, errno); + return NULL; + } + +mkdir_portal: + if (mkdir(portal, 0660) != 0) { + log_error("Could not make dir %s err %d", + portal, errno); + return NULL; + } + } + + strlcat(portal, "/", PATH_MAX); + strlcat(portal, config, PATH_MAX); + f = fopen(portal, "w"); + if (!f) + log_error("Could not open %s err %d", portal, errno); + return f; +} + +/* + * When the disable_lock param is true, the idbm_lock/idbm_unlock needs + * to be holt by the caller, this will avoid overwriting each other in + * case of updating(read-modify-write) the recs in parallel. + */ +static int idbm_rec_write(node_rec_t *rec, bool disable_lock) +{ + struct stat statb; + FILE *f; + char *portal; + int rc = 0; + + portal = malloc(PATH_MAX); + if (!portal) { + log_error("Could not alloc portal"); + return ISCSI_ERR_NOMEM; + } + + snprintf(portal, PATH_MAX, "%s", NODE_CONFIG_DIR); + if (access(portal, F_OK) != 0) { + if (mkdir(portal, 0660) != 0) { + log_error("Could not make %s: %s", portal, + strerror(errno)); + rc = ISCSI_ERR_IDBM; + goto free_portal; + } + } + + snprintf(portal, PATH_MAX, "%s/%s", NODE_CONFIG_DIR, rec->name); + if (access(portal, F_OK) != 0) { + if (mkdir(portal, 0660) != 0) { + log_error("Could not make %s: %s", portal, + strerror(errno)); + rc = ISCSI_ERR_IDBM; + goto free_portal; + } + } + + snprintf(portal, PATH_MAX, "%s/%s/%s,%d", NODE_CONFIG_DIR, + rec->name, rec->conn[0].address, rec->conn[0].port); + log_debug(5, "Looking for config file %s", portal); + + if (!disable_lock) { + rc = idbm_lock(); + if (rc) + goto free_portal; + } + + rc = stat(portal, &statb); + if (rc) { + rc = 0; + /* + * older iscsiadm versions had you create the config then set + * set the tgpt. In new versions you must pass all the info in + * from the start + */ + if (rec->tpgt == PORTAL_GROUP_TAG_UNKNOWN) + /* drop down to old style portal as config */ + goto open_conf; + else + goto mkdir_portal; + } + + if (!S_ISDIR(statb.st_mode)) { + /* + * older iscsiadm versions had you create the config then set + * set the tgpt. In new versions you must pass all the info in + * from the start + */ + if (rec->tpgt == PORTAL_GROUP_TAG_UNKNOWN) + /* drop down to old style portal as config */ + goto open_conf; + /* + * Old style portal as a file, but with tpgt. Let's update it. + */ + if (unlink(portal)) { + log_error("Could not convert %s: %s", portal, + strerror(errno)); + rc = ISCSI_ERR_IDBM; + goto unlock; + } + } else { + rc = ISCSI_ERR_INVAL; + goto unlock; + } + +mkdir_portal: + snprintf(portal, PATH_MAX, "%s/%s/%s,%d,%d", NODE_CONFIG_DIR, + rec->name, rec->conn[0].address, rec->conn[0].port, rec->tpgt); + if (stat(portal, &statb)) { + if (mkdir(portal, 0660) != 0) { + log_error("Could not make dir %s: %s", + portal, strerror(errno)); + rc = ISCSI_ERR_IDBM; + goto unlock; + } + } + + snprintf(portal, PATH_MAX, "%s/%s/%s,%d,%d/%s", NODE_CONFIG_DIR, + rec->name, rec->conn[0].address, rec->conn[0].port, rec->tpgt, + rec->iface.name); +open_conf: + f = fopen(portal, "w"); + if (!f) { + log_error("Could not open %s: %s", portal, strerror(errno)); + rc = ISCSI_ERR_IDBM; + goto unlock; + } + + idbm_print(IDBM_PRINT_TYPE_NODE, rec, 1, f); + fclose(f); +unlock: + if (!disable_lock) + idbm_unlock(); +free_portal: + free(portal); + return rc; +} + +static int +idbm_discovery_write(discovery_rec_t *rec) +{ + FILE *f; + char *portal; + int rc = 0; + + if (rec->type > 1) + return ISCSI_ERR_INVAL; + + portal = malloc(PATH_MAX); + if (!portal) { + log_error("Could not alloc portal"); + return ISCSI_ERR_NOMEM; + } + + rc = idbm_lock(); + if (rc) + goto free_portal; + + snprintf(portal, PATH_MAX, "%s", + disc_type_to_config_vals[rec->type].config_root); + if (access(portal, F_OK) != 0) { + if (mkdir(portal, 0660) != 0) { + log_error("Could not make %s: %s", portal, + strerror(errno)); + rc = ISCSI_ERR_IDBM; + goto unlock; + } + } + + snprintf(portal, PATH_MAX, "%s/%s,%d", + disc_type_to_config_vals[rec->type].config_root, + rec->address, rec->port); + + f = idbm_open_rec_w(portal, + disc_type_to_config_vals[rec->type].config_name); + if (!f) { + log_error("Could not open %s: %s", portal, strerror(errno)); + rc = ISCSI_ERR_IDBM; + goto unlock; + } + + idbm_print(IDBM_PRINT_TYPE_DISCOVERY, rec, 1, f); + fclose(f); +unlock: + idbm_unlock(); +free_portal: + free(portal); + return rc; +} + +int idbm_add_discovery(discovery_rec_t *newrec) +{ + discovery_rec_t rec; + + if (!idbm_discovery_read(&rec, newrec->type, newrec->address, + newrec->port)) { + log_debug(7, "disc rec already exists"); + /* fall through */ + } else + log_debug(7, "adding new DB record"); + + return idbm_discovery_write(newrec); +} + +static int setup_disc_to_node_link(char *disc_portal, node_rec_t *rec) +{ + struct stat statb; + int rc = 0; + + switch (rec->disc_type) { + case DISCOVERY_TYPE_SENDTARGETS: + /* st dir setup when we create its discovery node */ + snprintf(disc_portal, PATH_MAX, "%s/%s,%d/%s,%s,%d,%d,%s", + ST_CONFIG_DIR, + rec->disc_address, rec->disc_port, rec->name, + rec->conn[0].address, rec->conn[0].port, rec->tpgt, + rec->iface.name); + break; + case DISCOVERY_TYPE_FW: + if (access(FW_CONFIG_DIR, F_OK) != 0) { + if (mkdir(FW_CONFIG_DIR, 0660) != 0) { + log_error("Could not make %s: %s", + FW_CONFIG_DIR, strerror(errno)); + rc = ISCSI_ERR_IDBM; + } + } + + snprintf(disc_portal, PATH_MAX, "%s/%s,%s,%d,%d,%s", + FW_CONFIG_DIR, rec->name, + rec->conn[0].address, rec->conn[0].port, rec->tpgt, + rec->iface.name); + break; + case DISCOVERY_TYPE_STATIC: + if (access(STATIC_CONFIG_DIR, F_OK) != 0) { + if (mkdir(STATIC_CONFIG_DIR, 0660) != 0) { + log_error("Could not make %s; %s", + STATIC_CONFIG_DIR, strerror(errno)); + rc = ISCSI_ERR_IDBM; + } + } + + snprintf(disc_portal, PATH_MAX, "%s/%s,%s,%d,%d,%s", + STATIC_CONFIG_DIR, rec->name, + rec->conn[0].address, rec->conn[0].port, rec->tpgt, + rec->iface.name); + break; + case DISCOVERY_TYPE_ISNS: + if (access(ISNS_CONFIG_DIR, F_OK) != 0) { + if (mkdir(ISNS_CONFIG_DIR, 0660) != 0) { + log_error("Could not make %s: %s", + ISNS_CONFIG_DIR, strerror(errno)); + rc = ISCSI_ERR_IDBM; + } + } + + /* + * Older tools lumped all portals together in the + * isns config dir. In 2.0-872, the isns dir added + * a isns server (ddress and port) dir like sendtargets. + * + * If we found a older style link we return that so it + * can be removed. If this function is called for + * addition of a rec then the older link should have been + * removed and we break down below. + */ + snprintf(disc_portal, PATH_MAX, "%s/%s,%s,%d,%d,%s", + ISNS_CONFIG_DIR, + rec->name, rec->conn[0].address, + rec->conn[0].port, rec->tpgt, rec->iface.name); + if (!stat(disc_portal, &statb)) { + log_debug(7, "using old style isns dir %s.", + disc_portal); + break; + } + + snprintf(disc_portal, PATH_MAX, "%s/%s,%d", + ISNS_CONFIG_DIR, rec->disc_address, rec->disc_port); + if (!stat(disc_portal, &statb) && S_ISDIR(statb.st_mode)) { + /* + * if there is a dir for this isns server then + * assume we are using the new style links + */ + snprintf(disc_portal, PATH_MAX, + "%s/%s,%d/%s,%s,%d,%d,%s", + ISNS_CONFIG_DIR, rec->disc_address, + rec->disc_port, rec->name, + rec->conn[0].address, rec->conn[0].port, + rec->tpgt, rec->iface.name); + break; + } + + /* adding a older link */ + snprintf(disc_portal, PATH_MAX, "%s/%s,%s,%d,%d,%s", + ISNS_CONFIG_DIR, rec->name, rec->conn[0].address, + rec->conn[0].port, rec->tpgt, rec->iface.name); + break; + case DISCOVERY_TYPE_SLP: + default: + rc = ISCSI_ERR_INVAL; + } + + return rc; +} + +int idbm_add_node(node_rec_t *newrec, discovery_rec_t *drec, int overwrite) +{ + node_rec_t rec; + char *node_portal = NULL, *disc_portal; + int rc; + + rc = idbm_lock(); + if (rc) + return rc; + + if (!idbm_rec_read(&rec, newrec->name, newrec->tpgt, + newrec->conn[0].address, newrec->conn[0].port, + &newrec->iface, true)) { + if (!overwrite) { + rc = 0; + goto unlock; + } + + rc = idbm_delete_node(&rec); + if (rc) + goto unlock; + log_debug(7, "overwriting existing record"); + } else + log_debug(7, "adding new DB record"); + + if (drec) { + newrec->disc_type = drec->type; + newrec->disc_port = drec->port; + strcpy(newrec->disc_address, drec->address); + } + + rc = idbm_rec_write(newrec, true); + /* + * if a old app passed in a bogus tpgt then we do not create links + * since it will set a different tpgt in another iscsiadm call + */ + if (rc || !drec || newrec->tpgt == PORTAL_GROUP_TAG_UNKNOWN) + goto unlock; + + node_portal = calloc(2, PATH_MAX); + if (!node_portal) { + rc = ISCSI_ERR_NOMEM; + goto unlock; + } + + disc_portal = node_portal + PATH_MAX; + snprintf(node_portal, PATH_MAX, "%s/%s/%s,%d,%d", NODE_CONFIG_DIR, + newrec->name, newrec->conn[0].address, newrec->conn[0].port, + newrec->tpgt); + rc = setup_disc_to_node_link(disc_portal, newrec); + if (rc) + goto unlock; + + log_debug(7, "node addition making link from %s to %s", node_portal, + disc_portal); + + if (symlink(node_portal, disc_portal)) { + if (errno == EEXIST) + log_debug(7, "link from %s to %s exists", node_portal, + disc_portal); + else { + rc = ISCSI_ERR_IDBM; + log_error("Could not make link from disc source %s to " + "node %s: %s", disc_portal, node_portal, + strerror(errno)); + } + } +unlock: + idbm_unlock(); + free(node_portal); + return rc; +} + +static int idbm_bind_iface_to_nodes(idbm_disc_nodes_fn *disc_node_fn, + void *data, struct iface_rec *iface, + struct list_head *bound_recs) +{ + struct node_rec *rec, *tmp; + struct list_head new_recs; + int rc; + + INIT_LIST_HEAD(&new_recs); + rc = disc_node_fn(data, iface, &new_recs); + if (rc) + return rc; + + list_for_each_entry_safe(rec, tmp, &new_recs, list) { + list_del_init(&rec->list); + list_add_tail(&rec->list, bound_recs); + iface_copy(&rec->iface, iface); + } + return 0; +} + +static int +discovery_error_fatal(int err) +{ + switch (err) { + /* No error */ + case ISCSI_SUCCESS: + /* Transport errors or timeouts are not fatal */ + case ISCSI_ERR_TRANS: + case ISCSI_ERR_TRANS_TIMEOUT: + return 0; + } + return 1; +} + +int idbm_bind_ifaces_to_nodes(idbm_disc_nodes_fn *disc_node_fn, + void *data, struct list_head *ifaces, + struct list_head *bound_recs) +{ + struct list_head def_ifaces; + struct node_rec *rec, *tmp_rec; + struct iface_rec *iface, *tmp_iface; + struct iscsi_transport *t; + int rc = 0, found = 0; + + INIT_LIST_HEAD(&def_ifaces); + + if (!ifaces || list_empty(ifaces)) { + iface_link_ifaces(&def_ifaces); + + list_for_each_entry_safe(iface, tmp_iface, &def_ifaces, list) { + list_del(&iface->list); + t = iscsi_sysfs_get_transport_by_name(iface->transport_name); + /* + * only auto bind to software iscsi if it is + * not the default iface (that is handled below) + */ + if (!t || strcmp(t->name, DEFAULT_TRANSPORT) || + !strcmp(iface->name, DEFAULT_IFACENAME)) { + free(iface); + continue; + } + + rc = idbm_bind_iface_to_nodes(disc_node_fn, data, iface, + bound_recs); + free(iface); + if (discovery_error_fatal(rc)) + goto fail; + found = 1; + } + + /* create default iface with old/default behavior */ + if (!found) { + struct iface_rec def_iface; + + memset(&def_iface, 0, sizeof(struct iface_rec)); + iface_setup_defaults(&def_iface); + return idbm_bind_iface_to_nodes(disc_node_fn, data, + &def_iface, bound_recs); + } + } else { + list_for_each_entry(iface, ifaces, list) { + if (strcmp(iface->name, DEFAULT_IFACENAME) && + !iface_is_valid(iface)) { + log_error("iface %s is not valid. Will not " + "bind node to it. Iface settings " + iface_fmt, iface->name, + iface_str(iface)); + continue; + } + + rc = idbm_bind_iface_to_nodes(disc_node_fn, data, iface, + bound_recs); + if (discovery_error_fatal(rc)) + goto fail; + } + } + return 0; + +fail: + list_for_each_entry_safe(iface, tmp_iface, &def_ifaces, list) { + list_del(&iface->list); + free(iface); + } + + list_for_each_entry_safe(rec, tmp_rec, bound_recs, list) { + list_del(&rec->list); + free(rec); + } + return rc; +} + +static void idbm_rm_disc_node_links(char *disc_dir) +{ + char *target = NULL, *tpgt = NULL, *port = NULL; + char *address = NULL, *iface_id = NULL; + DIR *disc_dirfd; + struct dirent *disc_dent; + node_rec_t *rec; + + rec = calloc(1, sizeof(*rec)); + if (!rec) + return; + + disc_dirfd = opendir(disc_dir); + if (!disc_dirfd) + goto free_rec; + + /* rm links to nodes */ + while ((disc_dent = readdir(disc_dirfd))) { + if (!strcmp(disc_dent->d_name, ".") || + !strcmp(disc_dent->d_name, "..")) + continue; + + + if (get_params_from_disc_link(disc_dent->d_name, &target, &tpgt, + &address, &port, &iface_id)) { + log_error("Improperly formed disc to node link"); + continue; + } + + log_debug(5, "disc removal removing link %s %s %s %s", + target, address, port, iface_id); + + memset(rec, 0, sizeof(*rec)); + strlcpy(rec->name, target, TARGET_NAME_MAXLEN); + rec->tpgt = atoi(tpgt); + rec->conn[0].port = atoi(port); + strlcpy(rec->conn[0].address, address, NI_MAXHOST); + strlcpy(rec->iface.name, iface_id, ISCSI_MAX_IFACE_LEN); + + if (idbm_delete_node(rec)) + log_error("Could not delete node %s/%s/%s,%s/%s", + NODE_CONFIG_DIR, target, address, port, + iface_id); + } + + closedir(disc_dirfd); +free_rec: + free(rec); +} + +int idbm_delete_discovery(discovery_rec_t *drec) +{ + char *portal; + struct stat statb; + int rc = 0; + + portal = calloc(1, PATH_MAX); + if (!portal) + return ISCSI_ERR_NOMEM; + + snprintf(portal, PATH_MAX, "%s/%s,%d", + disc_type_to_config_vals[drec->type].config_root, + drec->address, drec->port); + log_debug(5, "Removing config file %s", portal); + + if (stat(portal, &statb)) { + log_debug(5, "Could not stat %s to delete disc err %d", + portal, errno); + goto free_portal; + } + + if (S_ISDIR(statb.st_mode)) { + strlcat(portal, "/", PATH_MAX); + strlcat(portal, + disc_type_to_config_vals[drec->type].config_name, + PATH_MAX); + } + + if (unlink(portal)) + log_debug(5, "Could not remove %s err %d", portal, errno); + + memset(portal, 0, PATH_MAX); + snprintf(portal, PATH_MAX, "%s/%s,%d", + disc_type_to_config_vals[drec->type].config_root, + drec->address, drec->port); + idbm_rm_disc_node_links(portal); + + /* rm portal dir */ + if (S_ISDIR(statb.st_mode)) { + memset(portal, 0, PATH_MAX); + snprintf(portal, PATH_MAX, "%s/%s,%d", + disc_type_to_config_vals[drec->type].config_root, + drec->address, drec->port); + rmdir(portal); + } + +free_portal: + free(portal); + return rc; +} + +/* + * Backwards Compat or SLP: + * if there is no link then this is pre svn 780 version where + * we did not link the disc source and node + */ +static int idbm_remove_disc_to_node_link(node_rec_t *rec, + char *portal) +{ + int rc = 0; + struct stat statb; + node_rec_t *tmprec; + + tmprec = malloc(sizeof(*tmprec)); + if (!tmprec) + return ISCSI_ERR_NOMEM; + + memset(portal, 0, PATH_MAX); + snprintf(portal, PATH_MAX, "%s/%s/%s,%d,%d/%s", NODE_CONFIG_DIR, + rec->name, rec->conn[0].address, rec->conn[0].port, rec->tpgt, + rec->iface.name); + + rc = __idbm_rec_read(tmprec, portal, false); + if (rc) { + /* old style recs will not have tpgt or a link so skip */ + rc = 0; + goto done; + } + + log_debug(7, "found drec %s %d", + tmprec->disc_address, tmprec->disc_port); + /* rm link from discovery source to node */ + memset(portal, 0, PATH_MAX); + rc = setup_disc_to_node_link(portal, tmprec); + if (rc) + goto done; + + rc = idbm_lock(); + if (rc) + goto done; + + if (!stat(portal, &statb)) { + if (unlink(portal)) { + log_error("Could not remove link %s: %s", + portal, strerror(errno)); + rc = ISCSI_ERR_IDBM; + } else + log_debug(7, "rmd %s", portal); + } else + log_debug(7, "Could not stat %s", portal); + idbm_unlock(); + +done: + free(tmprec); + return rc; +} + +static int st_disc_filter(const struct dirent *dir) +{ + return strcmp(dir->d_name, ".") && strcmp(dir->d_name, "..") && + strcmp(dir->d_name, ST_CONFIG_NAME); +} + +int idbm_delete_node(node_rec_t *rec) +{ + struct stat statb; + char *portal; + int rc = 0, dir_rm_rc = 0; + + portal = calloc(1, PATH_MAX); + if (!portal) + return ISCSI_ERR_NOMEM; + + rc = idbm_remove_disc_to_node_link(rec, portal); + if (rc) + goto free_portal; + + memset(portal, 0, PATH_MAX); + snprintf(portal, PATH_MAX, "%s/%s/%s,%d", NODE_CONFIG_DIR, + rec->name, rec->conn[0].address, rec->conn[0].port); + log_debug(5, "Removing config file %s iface id %s", + portal, rec->iface.name); + + rc = idbm_lock(); + if (rc) + goto free_portal; + + if (!stat(portal, &statb)) + goto rm_conf; + + snprintf(portal, PATH_MAX, "%s/%s/%s,%d,%d/%s", NODE_CONFIG_DIR, + rec->name, rec->conn[0].address, rec->conn[0].port, + rec->tpgt, rec->iface.name); + log_debug(5, "Removing config file %s", portal); + + if (!stat(portal, &statb)) + goto rm_conf; + + log_error("Could not stat %s to delete node: %s", + portal, strerror(errno)); + rc = ISCSI_ERR_IDBM; + goto unlock; + +rm_conf: + if (unlink(portal)) { + log_error("Could not remove %s: %s", portal, strerror(errno)); + rc = ISCSI_ERR_IDBM; + goto unlock; + } + + memset(portal, 0, PATH_MAX); + snprintf(portal, PATH_MAX, "%s/%s/%s,%d,%d", NODE_CONFIG_DIR, + rec->name, rec->conn[0].address, rec->conn[0].port, + rec->tpgt); + if (!stat(portal, &statb)) { + struct dirent **namelist; + int n, i; + + memset(portal, 0, PATH_MAX); + snprintf(portal, PATH_MAX, "%s/%s/%s,%d,%d", NODE_CONFIG_DIR, + rec->name, rec->conn[0].address, rec->conn[0].port, + rec->tpgt); + n = scandir(portal, &namelist, st_disc_filter, alphasort); + if (n < 0) + goto free_portal; + if (n == 0) + dir_rm_rc = rmdir(portal); + + for (i = 0; i < n; i++) + free(namelist[i]); + free(namelist); + } + /* rm target dir */ + if (!dir_rm_rc) { + memset(portal, 0, PATH_MAX); + snprintf(portal, PATH_MAX, "%s/%s", NODE_CONFIG_DIR, rec->name); + rmdir(portal); + } +unlock: + idbm_unlock(); +free_portal: + free(portal); + return rc; +} + +void +idbm_sendtargets_defaults(struct iscsi_sendtargets_config *cfg) +{ + idbm_sync_config(); + memcpy(cfg, &db->drec_st.u.sendtargets, + sizeof(struct iscsi_sendtargets_config)); +} + +void +idbm_isns_defaults(struct iscsi_isns_config *cfg) +{ + idbm_sync_config(); + memcpy(cfg, &db->drec_isns.u.isns, + sizeof(struct iscsi_isns_config)); +} + +void +idbm_slp_defaults(struct iscsi_slp_config *cfg) +{ + memcpy(cfg, &db->drec_slp.u.slp, + sizeof(struct iscsi_slp_config)); +} + +int +idbm_session_autoscan(struct iscsi_session *session) +{ + if (session) + return session->nrec.session.scan; + return db->nrec.session.scan; +} + +struct user_param *idbm_alloc_user_param(char *name, char *value) +{ + struct user_param *param; + + param = calloc(1, sizeof(*param)); + if (!param) + return NULL; + + INIT_LIST_HEAD(¶m->list); + + param->name = strdup(name); + if (!param->name) + goto free_param; + + param->value = strdup(value); + if (!param->value) + goto free_name; + + return param; + +free_name: + free(param->name); +free_param: + free(param); + return NULL; +} + +int idbm_node_set_rec_from_param(struct list_head *params, node_rec_t *rec, + int verify) +{ + struct user_param *param; + recinfo_t *info; + int rc = 0; + + if (list_empty(params)) + return 0; + + info = idbm_recinfo_alloc(MAX_KEYS); + if (!info) + return ISCSI_ERR_NOMEM; + + idbm_recinfo_node(rec, info); + + if (verify) { + list_for_each_entry(param, params, list) { + rc = idbm_verify_param(info, param->name); + if (rc) + goto free_info; + } + } + + list_for_each_entry(param, params, list) { + rc = idbm_rec_update_param(info, param->name, param->value, 0); + if (rc) { + if (rc == ISCSI_ERR_INVAL) + log_error("Unknown parameter %s.", param->name); + goto free_info; + } + } + +free_info: + free(info); + return rc; +} + +int idbm_node_set_param(void *data, node_rec_t *rec) +{ + int rc; + + rc = idbm_node_set_rec_from_param(data, rec, 1); + if (rc) + return rc; + + return idbm_rec_write(rec, true); +} + +int idbm_discovery_set_param(void *data, discovery_rec_t *rec) +{ + struct list_head *params = data; + struct user_param *param; + recinfo_t *info; + int rc = 0; + + info = idbm_recinfo_alloc(MAX_KEYS); + if (!info) + return ISCSI_ERR_NOMEM; + + idbm_recinfo_discovery((discovery_rec_t *)rec, info); + + list_for_each_entry(param, params, list) { + rc = idbm_verify_param(info, param->name); + if (rc) + goto free_info; + } + + list_for_each_entry(param, params, list) { + rc = idbm_rec_update_param(info, param->name, param->value, 0); + if (rc) + goto free_info; + } + + rc = idbm_discovery_write((discovery_rec_t *)rec); + if (rc) + goto free_info; + +free_info: + free(info); + return rc; +} + +int idbm_init(idbm_get_config_file_fn *fn) +{ + /* make sure root db dir is there */ + if (access(ISCSI_CONFIG_ROOT, F_OK) != 0) { + if (mkdir(ISCSI_CONFIG_ROOT, 0660) != 0) { + log_error("Could not make %s %d", ISCSI_CONFIG_ROOT, + errno); + return errno; + } + } + + db = malloc(sizeof(idbm_t)); + if (!db) { + log_error("out of memory on idbm allocation"); + return ISCSI_ERR_NOMEM; + } + memset(db, 0, sizeof(idbm_t)); + db->get_config_file = fn; + return 0; +} + +void idbm_terminate(void) +{ + if (db) + free(db); +} + +/** + * idbm_create_rec - allocate and setup a node record + * @targetname: target name + * @tgpt: target portal group + * @ip: ip address of portal + * @port: port of portal + * @iface: iscsi iface info + * @verbose: flag indicating whether to log ifaces setup errors + * + * The iface only needs to have the name set. This function will + * read in the other values. + */ +struct node_rec *idbm_create_rec(char *targetname, int tpgt, char *ip, + int port, struct iface_rec *iface, + int verbose) +{ + struct node_rec *rec; + + rec = calloc(1, sizeof(*rec)); + if (!rec) { + log_error("Could not not allocate memory to create node " + "record."); + return NULL; + } + + idbm_node_setup_defaults(rec); + if (targetname) + strlcpy(rec->name, targetname, TARGET_NAME_MAXLEN); + rec->tpgt = tpgt; + rec->conn[0].port = port; + if (ip) + strlcpy(rec->conn[0].address, ip, NI_MAXHOST); + memset(&rec->iface, 0, sizeof(struct iface_rec)); + if (iface) { + iface_copy(&rec->iface, iface); + if (strlen(iface->name)) { + if (iface_conf_read(&rec->iface)) { + if (verbose) + log_error("Could not read iface info " + "for %s.", iface->name); + goto free_rec; + } + } + } + return rec; +free_rec: + free(rec); + return NULL; +} + +struct node_rec *idbm_create_rec_from_boot_context(struct boot_context *context) +{ + struct node_rec *rec; + + /* tpgt hard coded to 1 ??? */ + rec = idbm_create_rec(context->targetname, 1, + context->target_ipaddr, context->target_port, + NULL, 1); + if (!rec) { + log_error("Could not setup rec for fw discovery login."); + return NULL; + } + + iface_setup_defaults(&rec->iface); + strlcpy(rec->session.auth.username, context->chap_name, + sizeof(context->chap_name)); + strlcpy((char *)rec->session.auth.password, context->chap_password, + sizeof(context->chap_password)); + strlcpy(rec->session.auth.username_in, context->chap_name_in, + sizeof(context->chap_name_in)); + strlcpy((char *)rec->session.auth.password_in, + context->chap_password_in, + sizeof(context->chap_password_in)); + rec->session.auth.password_length = + strlen((char *)context->chap_password); + rec->session.auth.password_in_length = + strlen((char *)context->chap_password_in); + strlcpy(rec->session.boot_root, context->boot_root, + sizeof(context->boot_root)); + strlcpy(rec->session.boot_nic, context->boot_nic, + sizeof(context->boot_nic)); + strlcpy(rec->session.boot_target, context->boot_target, + sizeof(context->boot_target)); + + iface_setup_from_boot_context(&rec->iface, context); + + return rec; +} + +void idbm_node_setup_defaults(node_rec_t *rec) +{ + int i; + + memset(rec, 0, sizeof(node_rec_t)); + + INIT_LIST_HEAD(&rec->list); + + rec->tpgt = PORTAL_GROUP_TAG_UNKNOWN; + rec->disc_type = DISCOVERY_TYPE_STATIC; + rec->leading_login = 0; + rec->session.cmds_max = CMDS_MAX; + rec->session.xmit_thread_priority = XMIT_THREAD_PRIORITY; + rec->session.initial_cmdsn = 0; + rec->session.queue_depth = QUEUE_DEPTH; + rec->session.nr_sessions = 1; + rec->session.initial_login_retry_max = DEF_INITIAL_LOGIN_RETRIES_MAX; + rec->session.reopen_max = DEF_SESSION_REOPEN_MAX; + rec->session.auth.authmethod = 0; + rec->session.auth.password_length = 0; + rec->session.auth.password_in_length = 0; + rec->session.err_timeo.abort_timeout = DEF_ABORT_TIMEO; + rec->session.err_timeo.lu_reset_timeout = DEF_LU_RESET_TIMEO; + rec->session.err_timeo.tgt_reset_timeout = DEF_TGT_RESET_TIMEO; + rec->session.err_timeo.host_reset_timeout = DEF_HOST_RESET_TIMEO; + rec->session.timeo.replacement_timeout = DEF_REPLACEMENT_TIMEO; + rec->session.info = NULL; + rec->session.sid = 0; + rec->session.multiple = 0; + rec->session.scan = DEF_INITIAL_SCAN; + idbm_setup_session_defaults(&rec->session.iscsi); + + for (i=0; iconn[i].startup = ISCSI_STARTUP_MANUAL; + rec->conn[i].port = ISCSI_LISTEN_PORT; + rec->conn[i].tcp.window_size = TCP_WINDOW_SIZE; + rec->conn[i].tcp.type_of_service = 0; + rec->conn[i].timeo.login_timeout= DEF_LOGIN_TIMEO; + rec->conn[i].timeo.logout_timeout= DEF_LOGOUT_TIMEO; + rec->conn[i].timeo.auth_timeout = 45; + + rec->conn[i].timeo.noop_out_interval = DEF_NOOP_OUT_INTERVAL; + rec->conn[i].timeo.noop_out_timeout = DEF_NOOP_OUT_TIMEO; + + idbm_setup_conn_defaults(&rec->conn[i].iscsi); + } + + iface_setup_defaults(&rec->iface); +} + +struct node_rec * +idbm_find_rec_in_list(struct list_head *rec_list, char *targetname, char *addr, + int port, struct iface_rec *iface) +{ + struct node_rec *rec; + + list_for_each_entry(rec, rec_list, list) { + if (__iscsi_match_session(rec, targetname, addr, port, iface, + MATCH_ANY_SID)) + return rec; + } + + return NULL; +} diff --git a/usr/idbm.h b/usr/idbm.h new file mode 100644 index 0000000..18c5025 --- /dev/null +++ b/usr/idbm.h @@ -0,0 +1,194 @@ +/* + * iSCSI Discovery Database Library + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * Copyright (C) 2006 Mike Christie + * Copyright (C) 2006 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#ifndef IDBM_H +#define IDBM_H + +#include +#include +#include +#include "initiator.h" +#include "config.h" +#include "list.h" +#include "flashnode.h" + +#define NODE_CONFIG_DIR ISCSI_CONFIG_ROOT"nodes" +#define SLP_CONFIG_DIR ISCSI_CONFIG_ROOT"slp" +#define ISNS_CONFIG_DIR ISCSI_CONFIG_ROOT"isns" +#define STATIC_CONFIG_DIR ISCSI_CONFIG_ROOT"static" +#define FW_CONFIG_DIR ISCSI_CONFIG_ROOT"fw" +#define ST_CONFIG_DIR ISCSI_CONFIG_ROOT"send_targets" +#define ST_CONFIG_NAME "st_config" +#define ISNS_CONFIG_NAME "isns_config" + +#define TYPE_INT 0 +#define TYPE_INT_O 1 +#define TYPE_STR 2 +#define TYPE_UINT8 3 +#define TYPE_UINT16 4 +#define TYPE_UINT32 5 +#define MAX_KEYS 256 /* number of keys total(including CNX_MAX) */ +#define NAME_MAXVAL 128 /* the maximum length of key name */ +#define VALUE_MAXVAL 256 /* the maximum length of 223 bytes in the RFC. */ +#define OPTS_MAXVAL 8 +typedef struct recinfo { + int type; + char name[NAME_MAXVAL]; + char value[VALUE_MAXVAL]; + void *data; + int data_len; + int visible; + char* opts[OPTS_MAXVAL]; + int numopts; + /* + * bool indicating if we can change it or not. + * TODO: make it a enum that can indicate wheter it also requires + * a relogin to pick up if a session is running. + */ + int can_modify; +} recinfo_t; + +typedef char *(idbm_get_config_file_fn)(void); + +typedef struct idbm { + void *discdb; + void *nodedb; + char *configfile; + int refs; + idbm_get_config_file_fn *get_config_file; + node_rec_t nrec; + recinfo_t ninfo[MAX_KEYS]; + discovery_rec_t drec_st; + recinfo_t dinfo_st[MAX_KEYS]; + discovery_rec_t drec_slp; + recinfo_t dinfo_slp[MAX_KEYS]; + discovery_rec_t drec_isns; + recinfo_t dinfo_isns[MAX_KEYS]; +} idbm_t; + +struct user_param { + struct list_head list; + char *name; + char *value; + int param; +}; + +typedef int (idbm_iface_op_fn)(void *data, node_rec_t *rec); +typedef int (idbm_portal_op_fn)(int *found, void *data, char *targetname, + int tpgt, char *ip, int port, bool ruw_lock); +typedef int (idbm_node_op_fn)(int *found, void *data, + char *targetname, bool ruw_lock); + +struct rec_op_data { + void *data; + node_rec_t *match_rec; + idbm_iface_op_fn *fn; +}; +extern int idbm_for_each_portal(int *found, void *data, idbm_portal_op_fn *fn, + char *targetname, bool ruw_lock); +extern int idbm_for_each_node(int *found, void *data, + idbm_node_op_fn *fn, bool ruw_lock); +extern int idbm_for_each_rec(int *found, void *data, + idbm_iface_op_fn *fn, bool ruw_lock); + + +typedef int (idbm_drec_op_fn)(void *data, discovery_rec_t *drec); +extern int idbm_for_each_st_drec(void *data, idbm_drec_op_fn *fn); +extern int idbm_for_each_isns_drec(void *data, idbm_drec_op_fn *fn); + +extern int idbm_init(idbm_get_config_file_fn *fn); + +extern void idbm_node_setup_from_conf(node_rec_t *rec); +extern void idbm_terminate(void); +extern int idbm_print_node_info(void *data, node_rec_t *rec); +extern int idbm_print_node_flat(void *data, node_rec_t *rec); +extern int idbm_print_node_tree(struct node_rec *last_rec, struct node_rec *rec, + char *prefix); +extern int idbm_print_node_and_iface_tree(void *data, node_rec_t *rec); +extern int idbm_print_discovery_info(discovery_rec_t *rec, int show); +extern int idbm_print_all_discovery(int info_level); +extern int idbm_delete_discovery(discovery_rec_t *rec); +extern void idbm_node_setup_defaults(node_rec_t *rec); +extern int idbm_delete_node(node_rec_t *rec); +extern int idbm_add_node(node_rec_t *newrec, discovery_rec_t *drec, + int overwrite); +struct list_head; +typedef int (idbm_disc_nodes_fn)(void *data, struct iface_rec *iface, + struct list_head *recs); +extern int idbm_bind_ifaces_to_nodes(idbm_disc_nodes_fn *disc_node_fn, + void *data, struct list_head *ifaces, + struct list_head *bound_recs); +extern int idbm_add_discovery(discovery_rec_t *newrec); +extern void idbm_sendtargets_defaults(struct iscsi_sendtargets_config *cfg); +extern void idbm_isns_defaults(struct iscsi_isns_config *cfg); +extern void idbm_slp_defaults(struct iscsi_slp_config *cfg); +extern int idbm_session_autoscan(struct iscsi_session *session); +extern int idbm_discovery_read(discovery_rec_t *rec, int type, char *addr, + int port); +extern int idbm_rec_read(node_rec_t *out_rec, char *target_name, + int tpgt, char *addr, int port, + struct iface_rec *iface, bool disable_lock); +extern int idbm_node_set_rec_from_param(struct list_head *params, + node_rec_t *rec, int verify); +extern int idbm_node_set_param(void *data, node_rec_t *rec); +extern int idbm_discovery_set_param(void *data, discovery_rec_t *rec); +struct user_param *idbm_alloc_user_param(char *name, char *value); +extern void idbm_node_setup_defaults(node_rec_t *rec); +extern struct node_rec *idbm_find_rec_in_list(struct list_head *rec_list, + char *targetname, char *addr, + int port, struct iface_rec *iface); + +/* lower level idbm functions for use by iface.c */ +extern void idbm_recinfo_config(recinfo_t *info, FILE *f); +extern void idbm_recinfo_iface(struct iface_rec *r, recinfo_t *ri); +extern int idbm_lock(void); +extern void idbm_unlock(void); +extern recinfo_t *idbm_recinfo_alloc(int max_keys); +extern int idbm_verify_param(recinfo_t *info, char *name); +extern int idbm_rec_update_param(recinfo_t *info, char *name, char *value, + int line_number); +extern void idbm_recinfo_node(node_rec_t *r, recinfo_t *ri); + +enum { + IDBM_PRINT_TYPE_DISCOVERY, + IDBM_PRINT_TYPE_NODE, + IDBM_PRINT_TYPE_IFACE, + IDBM_PRINT_TYPE_HOST_CHAP, + IDBM_PRINT_TYPE_FLASHNODE +}; + +extern void idbm_print(int type, void *rec, int show, FILE *f); + +struct boot_context; +extern struct node_rec *idbm_create_rec(char *targetname, int tpgt, + char *ip, int port, + struct iface_rec *iface, + int verbose); +extern struct node_rec * +idbm_create_rec_from_boot_context(struct boot_context *context); + +extern int idbm_print_host_chap_info(struct iscsi_chap_rec *chap); +extern void idbm_recinfo_host_chap(struct iscsi_chap_rec *r, recinfo_t *ri); + +extern int idbm_print_flashnode_info(struct flashnode_rec *target); +extern void idbm_recinfo_flashnode(struct flashnode_rec *r, recinfo_t *ri); + +#endif /* IDBM_H */ diff --git a/usr/idbm_fields.h b/usr/idbm_fields.h new file mode 100644 index 0000000..142c7ae --- /dev/null +++ b/usr/idbm_fields.h @@ -0,0 +1,244 @@ +#ifndef IDBM_FIELDS_H +#define IDBM_FIELDS_H + +#include "version.h" + +#define ISCSI_BEGIN_REC "# BEGIN RECORD "ISCSI_VERSION_STR +#define ISCSI_END_REC "# END RECORD" + +/* node fields */ +#define NODE_NAME "node.name" +#define NODE_TPGT "node.tpgt" +#define NODE_STARTUP "node.startup" +#define NODE_LEADING_LOGIN "node.leading_login" +#define NODE_DISC_ADDR "node.discovery_address" +#define NODE_DISC_PORT "node.discovery_port" +#define NODE_DISC_TYPE "node.discovery_type" +#define NODE_BOOT_LUN "node.boot_lun" + +/* session fields */ +#define SESSION_INIT_CMDSN "node.session.initial_cmdsn" +#define SESSION_INIT_LOGIN_RETRY "node.session.initial_login_retry_max" +#define SESSION_CMDS_MAX "node.session.cmds_max" +#define SESSION_XMIT_THREAD_PRIORITY "node.session.xmit_thread_priority" +#define SESSION_QDEPTH "node.session.queue_depth" +#define SESSION_NR_SESSIONS "node.session.nr_sessions" +#define SESSION_AUTH_METHOD "node.session.auth.authmethod" +#define SESSION_USERNAME "node.session.auth.username" +#define SESSION_PASSWORD "node.session.auth.password" +#define SESSION_PASSWORD_LEN "node.session.auth.password_length" +#define SESSION_USERNAME_IN "node.session.auth.username_in" +#define SESSION_PASSWORD_IN "node.session.auth.password_in" +#define SESSION_PASSWORD_IN_LEN "node.session.auth.password_in_length" +#define SESSION_REPLACEMENT_TMO "node.session.timeo.replacement_timeout" +#define SESSION_ABORT_TMO "node.session.err_timeo.abort_timeout" +#define SESSION_LU_RESET_TMO "node.session.err_timeo.lu_reset_timeout" +#define SESSION_TGT_RESET_TMO "node.session.err_timeo.tgt_reset_timeout" +#define SESSION_HOST_RESET_TMO "node.session.err_timeo.host_reset_timeout" +#define SESSION_FAST_ABORT "node.session.iscsi.FastAbort" +#define SESSION_INITIAL_R2T "node.session.iscsi.InitialR2T" +#define SESSION_IMM_DATA "node.session.iscsi.ImmediateData" +#define SESSION_FIRST_BURST "node.session.iscsi.FirstBurstLength" +#define SESSION_MAX_BURST "node.session.iscsi.MaxBurstLength" +#define SESSION_DEF_TIME2RETAIN "node.session.iscsi.DefaultTime2Retain" +#define SESSION_DEF_TIME2WAIT "node.session.iscsi.DefaultTime2Wait" +#define SESSION_MAX_CONNS "node.session.iscsi.MaxConnections" +#define SESSION_MAX_R2T "node.session.iscsi.MaxOutstandingR2T" +#define SESSION_ERL "node.session.iscsi.ERL" +#define SESSION_SCAN "node.session.scan" +#define SESSION_REOPEN_MAX "node.session.reopen_max" + +/* connections fields */ +#define CONN_ADDR "node.conn[%d].address" +#define CONN_PORT "node.conn[%d].port" +#define CONN_STARTUP "node.conn[%d].startup" +#define CONN_WINDOW_SIZE "node.conn[%d].tcp.window_size" +#define CONN_SERVICE_TYPE "node.conn[%d].tcp.type_of_service" +#define CONN_LOGOUT_TMO "node.conn[%d].timeo.logout_timeout" +#define CONN_LOGIN_TMO "node.conn[%d].timeo.login_timeout" +#define CONN_AUTH_TMO "node.conn[%d].timeo.auth_timeout" +#define CONN_NOP_INT "node.conn[%d].timeo.noop_out_interval" +#define CONN_NOP_TMO "node.conn[%d].timeo.noop_out_timeout" +#define CONN_MAX_XMIT_DLEN "node.conn[%d].iscsi.MaxXmitDataSegmentLength" +#define CONN_MAX_RECV_DLEN "node.conn[%d].iscsi.MaxRecvDataSegmentLength" +#define CONN_HDR_DIGEST "node.conn[%d].iscsi.HeaderDigest" +#define CONN_DATA_DIGEST "node.conn[%d].iscsi.DataDigest" +#define CONN_IFMARKER "node.conn[%d].iscsi.IFMarker" +#define CONN_OFMARKER "node.conn[%d].iscsi.OFMarker" + +/* iface fields */ +#define IFACE_HWADDR "iface.hwaddress" +#define IFACE_ISCSINAME "iface.iscsi_ifacename" +#define IFACE_NETNAME "iface.net_ifacename" +#define IFACE_TRANSPORTNAME "iface.transport_name" +#define IFACE_INAME "iface.initiatorname" +#define IFACE_ISID "iface.isid" +#define IFACE_BOOT_PROTO "iface.bootproto" +#define IFACE_IPADDR "iface.ipaddress" +#define IFACE_PREFIX_LEN "iface.prefix_len" +#define IFACE_SUBNET_MASK "iface.subnet_mask" +#define IFACE_GATEWAY "iface.gateway" +#define IFACE_PRIMARY_DNS "iface.primary_dns" +#define IFACE_SEC_DNS "iface.secondary_dns" +#define IFACE_VLAN_ID "iface.vlan_id" +#define IFACE_VLAN_PRIORITY "iface.vlan_priority" +#define IFACE_VLAN_STATE "iface.vlan_state" +#define IFACE_LINKLOCAL "iface.ipv6_linklocal" +#define IFACE_ROUTER "iface.ipv6_router" +#define IFACE_IPV6_AUTOCFG "iface.ipv6_autocfg" +#define IFACE_LINKLOCAL_AUTOCFG "iface.linklocal_autocfg" +#define IFACE_ROUTER_AUTOCFG "iface.router_autocfg" +#define IFACE_STATE "iface.state" +#define IFACE_NUM "iface.iface_num" +#define IFACE_MTU "iface.mtu" +#define IFACE_PORT "iface.port" +#define IFACE_DELAYED_ACK "iface.delayed_ack" +#define IFACE_TCP_NAGLE "iface.tcp_nagle" +#define IFACE_TCP_WSF_STATE "iface.tcp_wsf_state" +#define IFACE_TCP_WSF "iface.tcp_wsf" +#define IFACE_TCP_TIMER_SCALE "iface.tcp_timer_scale" +#define IFACE_TCP_TIMESTAMP "iface.tcp_timestamp" +#define IFACE_DHCP_DNS "iface.dhcp_dns" +#define IFACE_DHCP_SLP_DA "iface.dhcp_slp_da" +#define IFACE_TOS_STATE "iface.tos_state" +#define IFACE_TOS "iface.tos" +#define IFACE_GRAT_ARP "iface.gratuitous_arp" +#define IFACE_DHCP_ALT_CID "iface.dhcp_alt_client_id_state" +#define IFACE_DHCP_ALT_CID_STR "iface.dhcp_alt_client_id" +#define IFACE_DHCP_REQ_VID "iface.dhcp_req_vendor_id_state" +#define IFACE_DHCP_VID "iface.dhcp_vendor_id_state" +#define IFACE_DHCP_VID_STR "iface.dhcp_vendor_id" +#define IFACE_DHCP_LEARN_IQN "iface.dhcp_learn_iqn" +#define IFACE_FRAGMENTATION "iface.fragmentation" +#define IFACE_IN_FORWARD "iface.incoming_forwarding" +#define IFACE_TTL "iface.ttl" +#define IFACE_GRAT_NEIGHBOR_ADV "iface.gratuitous_neighbor_adv" +#define IFACE_REDIRECT "iface.redirect" +#define IFACE_IGNORE_ICMP_ECHO_REQ "iface.ignore_icmp_echo_request" +#define IFACE_MLD "iface.mld" +#define IFACE_FLOW_LABEL "iface.flow_label" +#define IFACE_TRAFFIC_CLASS "iface.traffic_class" +#define IFACE_HOP_LIMIT "iface.hop_limit" +#define IFACE_ND_REACHABLE_TMO "iface.nd_reachable_tmo" +#define IFACE_ND_REXMIT_TIME "iface.nd_rexmit_time" +#define IFACE_ND_STALE_TMO "iface.nd_stale_tmo" +#define IFACE_DUP_ADDR_DETECT_CNT "iface.dup_addr_detect_cnt" +#define IFACE_RTR_ADV_LINK_MTU "iface.router_adv_link_mtu" +#define IFACE_DEF_TMF_TMO "iface.def_task_mgmt_timeout" +#define IFACE_HDRDGST "iface.header_digest" +#define IFACE_DATADGST "iface.data_digest" +#define IFACE_IMM_DATA "iface.immediate_data" +#define IFACE_INITIAL_R2T "iface.initial_r2t" +#define IFACE_DSEQ_INORDER "iface.data_seq_inorder" +#define IFACE_DPDU_INORDER "iface.data_pdu_inorder" +#define IFACE_ERL "iface.erl" +#define IFACE_MAX_RECV_DLEN "iface.max_receive_data_len" +#define IFACE_FIRST_BURST "iface.first_burst_len" +#define IFACE_MAX_R2T "iface.max_outstanding_r2t" +#define IFACE_MAX_BURST "iface.max_burst_len" +#define IFACE_CHAP_AUTH "iface.chap_auth" +#define IFACE_BIDI_CHAP "iface.bidi_chap" +#define IFACE_STRICT_LOGIN_COMP "iface.strict_login_compliance" +#define IFACE_DISCOVERY_AUTH "iface.discovery_auth" +#define IFACE_DISCOVERY_LOGOUT "iface.discovery_logout" + +/* discovery fields */ +#define DISC_STARTUP "discovery.startup" +#define DISC_TYPE "discovery.type" +/* sendtargets */ +#define DISC_ST_ADDR "discovery.sendtargets.address" +#define DISC_ST_PORT "discovery.sendtargets.port" +#define DISC_ST_AUTH_METHOD "discovery.sendtargets.auth.authmethod" +#define DISC_ST_USERNAME "discovery.sendtargets.auth.username" +#define DISC_ST_PASSWORD "discovery.sendtargets.auth.password" +#define DISC_ST_PASSWORD_LEN "discovery.sendtargets.auth.password_length" +#define DISC_ST_USERNAME_IN "discovery.sendtargets.auth.username_in" +#define DISC_ST_PASSWORD_IN "discovery.sendtargets.auth.password_in" +#define DISC_ST_PASSWORD_IN_LEN "discovery.sendtargets.auth.password_in_length" +#define DISC_ST_LOGIN_TMO "discovery.sendtargets.timeo.login_timeout" +#define DISC_ST_REOPEN_MAX "discovery.sendtargets.reopen_max" +#define DISC_ST_DISC_DAEMON_POLL_INVAL "discovery.sendtargets.discoveryd_poll_inval" +#define DISC_ST_USE_DISC_DAEMON "discovery.sendtargets.use_discoveryd" +#define DISC_ST_AUTH_TMO "discovery.sendtargets.timeo.auth_timeout" +#define DISC_ST_ACTIVE_TMO "discovery.sendtargets.timeo.active_timeout" +#define DISC_ST_MAX_RECV_DLEN "discovery.sendtargets.iscsi.MaxRecvDataSegmentLength" + +#define DISC_ISNS_DISC_DAEMON_POLL_INVAL "discovery.isns.discoveryd_poll_inval" +#define DISC_ISNS_USE_DISC_DAEMON "discovery.isns.use_discoveryd" +#define DISC_ISNS_ADDR "discovery.sendtargets.address" +#define DISC_ISNS_PORT "discovery.sendtargets.port" + +/* host auth fields */ +#define HOST_AUTH_INDEX "host.auth.tbl_idx" +#define HOST_AUTH_METHOD "host.auth.authmethod" +#define HOST_AUTH_USERNAME "host.auth.username" +#define HOST_AUTH_PASSWORD "host.auth.password" +#define HOST_AUTH_PASSWORD_LEN "host.auth.password_length" +#define HOST_AUTH_USERNAME_IN "host.auth.username_in" +#define HOST_AUTH_PASSWORD_IN "host.auth.password_in" +#define HOST_AUTH_PASSWORD_IN_LEN "host.auth.password_in_length" + +/* flash target session fields */ +#define FLASHNODE_SESS_AUTO_SND_TGT_DISABLE "flashnode.session.auto_snd_tgt_disable" +#define FLASHNODE_SESS_DISCOVERY_SESS "flashnode.session.discovery_session" +#define FLASHNODE_SESS_PORTAL_TYPE "flashnode.session.portal_type" +#define FLASHNODE_SESS_ENTRY_EN "flashnode.session.entry_enable" +#define FLASHNODE_SESS_IMM_DATA_EN "flashnode.session.immediate_data" +#define FLASHNODE_SESS_INITIAL_R2T_EN "flashnode.session.initial_r2t" +#define FLASHNODE_SESS_DATASEQ_INORDER "flashnode.session.data_seq_in_order" +#define FLASHNODE_SESS_PDU_INORDER "flashnode.session.data_pdu_in_order" +#define FLASHNODE_SESS_CHAP_AUTH_EN "flashnode.session.chap_auth_en" +#define FLASHNODE_SESS_DISCOVERY_LOGOUT_EN "flashnode.session.discovery_logout_en" +#define FLASHNODE_SESS_BIDI_CHAP_EN "flashnode.session.bidi_chap_en" +#define FLASHNODE_SESS_DISCOVERY_AUTH_OPTIONAL "flashnode.session.discovery_auth_optional" +#define FLASHNODE_SESS_ERL "flashnode.session.erl" +#define FLASHNODE_SESS_FIRST_BURST "flashnode.session.first_burst_len" +#define FLASHNODE_SESS_DEF_TIME2WAIT "flashnode.session.def_time2wait" +#define FLASHNODE_SESS_DEF_TIME2RETAIN "flashnode.session.def_time2retain" +#define FLASHNODE_SESS_MAX_R2T "flashnode.session.max_outstanding_r2t" +#define FLASHNODE_SESS_ISID "flashnode.session.isid" +#define FLASHNODE_SESS_TSID "flashnode.session.tsid" +#define FLASHNODE_SESS_MAX_BURST "flashnode.session.max_burst_len" +#define FLASHNODE_SESS_DEF_TASKMGMT_TMO "flashnode.session.def_taskmgmt_tmo" +#define FLASHNODE_SESS_ALIAS "flashnode.session.targetalias" +#define FLASHNODE_SESS_NAME "flashnode.session.targetname" +#define FLASHNODE_SESS_TPGT "flashnode.session.tpgt" +#define FLASHNODE_SESS_DISCOVERY_PARENT_IDX "flashnode.session.discovery_parent_idx" +#define FLASHNODE_SESS_DISCOVERY_PARENT_TYPE "flashnode.session.discovery_parent_type" +#define FLASHNODE_SESS_CHAP_OUT_IDX "flashnode.session.chap_out_idx" +#define FLASHNODE_SESS_CHAP_IN_IDX "flashnode.session.chap_in_idx" +#define FLASHNODE_SESS_USERNAME "flashnode.session.username" +#define FLASHNODE_SESS_USERNAME_IN "flashnode.session.username_in" +#define FLASHNODE_SESS_PASSWORD "flashnode.session.password" +#define FLASHNODE_SESS_PASSWORD_IN "flashnode.session.password_in" +#define FLASHNODE_SESS_IS_BOOT_TGT "flashnode.session.is_boot_target" + +/* flash target connection fields */ +#define FLASHNODE_CONN_IS_FW_ASSIGNED_IPV6 "flashnode.conn[%d].is_fw_assigned_ipv6" +#define FLASHNODE_CONN_HDR_DGST_EN "flashnode.conn[%d].header_digest_en" +#define FLASHNODE_CONN_DATA_DGST_EN "flashnode.conn[%d].data_digest_en" +#define FLASHNODE_CONN_SNACK_REQ_EN "flashnode.conn[%d].snack_req_en" +#define FLASHNODE_CONN_TCP_TIMESTAMP_STAT "flashnode.conn[%d].tcp_timestamp_stat" +#define FLASHNODE_CONN_TCP_NAGLE_DISABLE "flashnode.conn[%d].tcp_nagle_disable" +#define FLASHNODE_CONN_TCP_WSF_DISABLE "flashnode.conn[%d].tcp_wsf_disable" +#define FLASHNODE_CONN_TCP_TIMER_SCALE "flashnode.conn[%d].tcp_timer_scale" +#define FLASHNODE_CONN_TCP_TIMESTAMP_EN "flashnode.conn[%d].tcp_timestamp_en" +#define FLASHNODE_CONN_IP_FRAG_DISABLE "flashnode.conn[%d].fragment_disable" +#define FLASHNODE_CONN_MAX_RECV_DLENGTH "flashnode.conn[%d].max_recv_dlength" +#define FLASHNODE_CONN_MAX_XMIT_DLENGTH "flashnode.conn[%d].max_xmit_dlength" +#define FLASHNODE_CONN_KEEPALIVE_TMO "flashnode.conn[%d].keepalive_tmo" +#define FLASHNODE_CONN_PORT "flashnode.conn[%d].port" +#define FLASHNODE_CONN_IPADDR "flashnode.conn[%d].ipaddress" +#define FLASHNODE_CONN_REDIRECT_IPADDR "flashnode.conn[%d].redirect_ipaddr" +#define FLASHNODE_CONN_MAX_SEGMENT_SIZE "flashnode.conn[%d].max_segment_size" +#define FLASHNODE_CONN_LOCAL_PORT "flashnode.conn[%d].local_port" +#define FLASHNODE_CONN_IPV4_TOS "flashnode.conn[%d].ipv4_tos" +#define FLASHNODE_CONN_IPV6_TC "flashnode.conn[%d].ipv6_traffic_class" +#define FLASHNODE_CONN_IPV6_FLOW_LABEL "flashnode.conn[%d].ipv6_flow_label" +#define FLASHNODE_CONN_LINK_LOCAL_IPV6 "flashnode.conn[%d].link_local_ipv6" +#define FLASHNODE_CONN_TCP_XMIT_WSF "flashnode.conn[%d].tcp_xmit_wsf" +#define FLASHNODE_CONN_TCP_RECV_WSF "flashnode.conn[%d].tcp_recv_wsf" +#define FLASHNODE_CONN_STATSN "flashnode.conn[%d].statsn" +#define FLASHNODE_CONN_EXP_STATSN "flashnode.conn[%d].exp_statsn" + +#endif diff --git a/usr/iface.c b/usr/iface.c new file mode 100644 index 0000000..645b0b8 --- /dev/null +++ b/usr/iface.c @@ -0,0 +1,2538 @@ +/* + * iSCSI iface helpers + * + * Copyright (C) 2008 Mike Christie + * Copyright (C) 2008 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "list.h" +#include "iscsi_sysfs.h" +#include "iscsi_settings.h" +#include "config.h" +#include "transport.h" +#include "idbm.h" +#include "iface.h" +#include "session_info.h" +#include "host.h" +#include "fw_context.h" +#include "sysdeps.h" +#include "iscsi_err.h" +#include "iscsi_netlink.h" + +#define _unwrap(x) (x && strlen(x)) ? x : UNKNOWN_VALUE + +/* + * Default ifaces for use with transports that do not bind to hardware + * by defaults (transports that let the interconnect layer to the routing + * by defaults). + */ + +/* + * iSCSI over TCP/IP + */ +static struct iface_rec iface_default = { + .name = "default", + .transport_name = "tcp", +}; + +/* + * iSER + */ +static struct iface_rec iface_iser = { + .name = "iser", + .transport_name = "iser", +}; + +static struct iface_rec *default_ifaces[] = { + &iface_default, + &iface_iser, + NULL, +}; + +static struct iface_rec *iface_match_default(struct iface_rec *iface) +{ + struct iface_rec *def_iface; + int i = 0; + + while ((def_iface = default_ifaces[i++])) { + if (!strcmp(iface->name, def_iface->name)) + return def_iface; + } + return NULL; +} + +static void iface_init(struct iface_rec *iface) +{ + if (!strlen(iface->name)) + sprintf(iface->name, DEFAULT_IFACENAME); +} + +/* + * default is to use tcp through whatever the network layer + * selects for us with the /etc/iscsi/initiatorname.iscsi iname. + */ +void iface_setup_defaults(struct iface_rec *iface) +{ + sprintf(iface->transport_name, DEFAULT_TRANSPORT); + iface_init(iface); +} + +struct iface_rec *iface_alloc(char *ifname, int *err) +{ + struct iface_rec *iface; + + if (!strlen(ifname) || strlen(ifname) + 1 > ISCSI_MAX_IFACE_LEN) { + *err = ISCSI_ERR_INVAL; + return NULL; + } + + iface = calloc(1, sizeof(*iface)); + if (!iface) { + *err = ISCSI_ERR_NOMEM; + return NULL; + } + + strlcpy(iface->name, ifname, ISCSI_MAX_IFACE_LEN); + INIT_LIST_HEAD(&iface->list); + return iface; +} + +static int __iface_conf_read(struct iface_rec *iface) +{ + char *iface_conf; + recinfo_t *info; + FILE *f; + int rc = 0; + + iface_conf = calloc(1, PATH_MAX); + if (!iface_conf) + return ISCSI_ERR_NOMEM; + + info = idbm_recinfo_alloc(MAX_KEYS); + if (!info) { + rc = ISCSI_ERR_NOMEM; + goto free_conf; + } + + snprintf(iface_conf, PATH_MAX, "%s/%s", IFACE_CONFIG_DIR, + iface->name); + + log_debug(5, "looking for iface conf %s", iface_conf); + f = fopen(iface_conf, "r"); + if (!f) { + /* + * if someone passes in default but has not defined + * an iface with default then we do it for them + */ + if (!strcmp(iface->name, DEFAULT_IFACENAME)) { + iface_setup_defaults(iface); + rc = 0; + } else + rc = ISCSI_ERR_IDBM; + goto free_info; + } + + iface_init(iface); + idbm_recinfo_iface(iface, info); + idbm_recinfo_config(info, f); + fclose(f); + +free_info: + free(info); +free_conf: + free(iface_conf); + return rc; +} + +int iface_conf_read(struct iface_rec *iface) +{ + struct iface_rec *def_iface; + int rc, retry = 0; + + def_iface = iface_match_default(iface); + if (def_iface) { + /* + * older tools allowed default to have different + * transport_names so we do not want to overwrite + * it. + */ + if (!strcmp(def_iface->name, DEFAULT_IFACENAME)) { + if (!strlen(iface->name)) + strcpy(iface->name, def_iface->name); + if (!strlen(iface->netdev)) + strcpy(iface->netdev, def_iface->netdev); + if (!strlen(iface->hwaddress)) + strcpy(iface->hwaddress, def_iface->hwaddress); + if (!strlen(iface->transport_name)) + strcpy(iface->transport_name, + def_iface->transport_name); + if (!strlen(iface->iname)) + strcpy(iface->iname, def_iface->iname); + } else { + iface_init(iface); + iface_copy(iface, def_iface); + } + return 0; + } + +retry_read: + rc = idbm_lock(); + if (rc) + return rc; + + rc = __iface_conf_read(iface); + idbm_unlock(); + + /* + * cmd was run before running -m iface, so force def bindings + * creation to see if that was the one requested + */ + if (retry < 1 && rc == ISCSI_ERR_IDBM) { + iface_setup_host_bindings(); + retry++; + goto retry_read; + } + + return rc; +} + +int iface_conf_delete(struct iface_rec *iface) +{ + struct iface_rec *def_iface; + char *iface_conf; + int rc = 0; + + def_iface = iface_match_default(iface); + if (def_iface) { + log_error("iface %s is a special interface and " + "cannot be deleted.", iface->name); + return ISCSI_ERR_INVAL; + } + + iface_conf = calloc(1, PATH_MAX); + if (!iface_conf) + return ISCSI_ERR_NOMEM; + + sprintf(iface_conf, "%s/%s", IFACE_CONFIG_DIR, iface->name); + rc = idbm_lock(); + if (rc) + goto free_conf; + + if (unlink(iface_conf)) + rc = ISCSI_ERR_IDBM; + idbm_unlock(); + +free_conf: + free(iface_conf); + return rc; +} + +int iface_conf_write(struct iface_rec *iface) +{ + struct iface_rec *def_iface; + char *iface_conf; + FILE *f; + int rc = 0; + + def_iface = iface_match_default(iface); + if (def_iface) { + log_error("iface %s is a special interface and " + "is not stored in %s.", iface->name, + IFACE_CONFIG_DIR); + return ISCSI_ERR_INVAL; + } + + iface_conf = calloc(1, PATH_MAX); + if (!iface_conf) + return ISCSI_ERR_NOMEM; + + sprintf(iface_conf, "%s/%s", IFACE_CONFIG_DIR, iface->name); + f = fopen(iface_conf, "w"); + if (!f) { + rc = ISCSI_ERR_IDBM; + goto free_conf; + } + + rc = idbm_lock(); + if (rc) + goto close_f; + + idbm_print(IDBM_PRINT_TYPE_IFACE, iface, 1, f); + idbm_unlock(); + +close_f: + fclose(f); +free_conf: + free(iface_conf); + return rc; +} + +int iface_conf_update(struct list_head *params, struct iface_rec *iface) +{ + struct iface_rec *def_iface; + recinfo_t *info; + struct user_param *param; + int rc = 0; + + def_iface = iface_match_default(iface); + if (def_iface) { + log_error("iface %s is a special interface and " + "cannot be modified.", iface->name); + return ISCSI_ERR_INVAL; + } + + info = idbm_recinfo_alloc(MAX_KEYS); + if (!info) + return ISCSI_ERR_NOMEM; + + idbm_recinfo_iface(iface, info); + + list_for_each_entry(param, params, list) { + rc = idbm_verify_param(info, param->name); + if (rc) + goto free_info; + } + + list_for_each_entry(param, params, list) { + rc = idbm_rec_update_param(info, param->name, param->value, 0); + if (rc) + goto free_info; + } + + rc = iface_conf_write(iface); +free_info: + free(info); + return rc; +} + +#if 0 /* Unused */ +static int iface_get_next_id(void) +{ + struct stat statb; + char *iface_conf; + int i, rc = ENOSPC; + + iface_conf = calloc(1, PATH_MAX); + if (!iface_conf) + return ENOMEM; + + for (i = 0; i < INT_MAX; i++) { + memset(iface_conf, 0, PATH_MAX); + /* check len */ + snprintf(iface_conf, PATH_MAX, "iface%d", i); + if (strlen(iface_conf) > ISCSI_MAX_IFACE_LEN - 1) { + log_error("iface namespace is full. Remove unused " + "iface definitions from %s or send mail " + "to open-iscsi@googlegroups.com to report " + "the problem", IFACE_CONFIG_DIR); + rc = ENOSPC; + break; + } + memset(iface_conf, 0, PATH_MAX); + snprintf(iface_conf, PATH_MAX, "%s/iface%d", IFACE_CONFIG_DIR, + i); + + if (!stat(iface_conf, &statb)) + continue; + if (errno == ENOENT) { + rc = i; + break; + } + } + + free(iface_conf); + return rc; +} +#endif /* Unused */ + +struct iface_search { + struct iface_rec *pattern; + struct iface_rec *found; +}; + +static int __iface_get_by_net_binding(void *data, struct iface_rec *iface) +{ + struct iface_search *search = data; + + if (!strcmp(search->pattern->name, iface->name)) { + iface_copy(search->found, iface); + return 1; + } + + if (iface_is_bound_by_hwaddr(search->pattern)) { + if (!strcasecmp(iface->hwaddress, + search->pattern->hwaddress)) { + iface_copy(search->found, iface); + return 1; + } else + return 0; + } + + if (iface_is_bound_by_netdev(search->pattern)) { + if (!strcmp(iface->netdev, search->pattern->netdev)) { + iface_copy(search->found, iface); + return 1; + } else + return 0; + } + +/* + if (iface_is_bound_by_ipaddr(search->pattern)) { + if (!strcmp(iface->ipaddress, search->pattern->ipaddress)) { + iface_copy(search->found, iface); + return 1; + } else + return 0; + } +*/ + return 0; +} + +/* + * Before 2.0.870, we only could bind by netdeivce or hwaddress, + * so we did a simple reverse lookup to go from sysfs info to + * the iface name. After 2.0.870 we added a lot of options to the + * iface binding so we added the ifacename to the kernel. + * + * This function is for older kernels that do not export the ifacename. + * If the user was doing iscsi_tcp session binding we will find + * the iface by matching net info. + */ +int iface_get_by_net_binding(struct iface_rec *pattern, + struct iface_rec *out_rec) +{ + int num_found = 0, rc; + struct iface_search search; + + if (!iface_is_bound_by_hwaddr(pattern) && + !iface_is_bound_by_netdev(pattern)) { + sprintf(out_rec->name, DEFAULT_IFACENAME); + return 0; + } + + search.pattern = pattern; + search.found = out_rec; + + rc = iface_for_each_iface(&search, 0, &num_found, + __iface_get_by_net_binding); + if (rc == 1) + return 0; + return ISCSI_ERR_NO_OBJS_FOUND; +} + +int iface_get_iptype(struct iface_rec *iface) +{ + /* address might not be set if user config with another tool */ + if (!strlen(iface->ipaddress) || + !strcmp(UNKNOWN_VALUE, iface->ipaddress)) { + /* try to figure out by name */ + if (strstr(iface->name, "ipv4")) + return ISCSI_IFACE_TYPE_IPV4; + else if (strstr(iface->name, "ipv6")) + return ISCSI_IFACE_TYPE_IPV6; + else /* assume ipv4 by default */ + return ISCSI_IFACE_TYPE_IPV4; + } else { + if (strcmp(iface->bootproto, "dhcp") && + !strstr(iface->ipaddress, ".")) + return ISCSI_IFACE_TYPE_IPV6; + else + return ISCSI_IFACE_TYPE_IPV4; + } +} + +static int iface_setup_binding_from_kern_iface(void *data, + struct iface_rec *kern_iface) +{ + struct host_info *hinfo = data; + struct iface_rec iface; + char iface_path[PATH_MAX]; + + if (!strlen(hinfo->iface.hwaddress)) { + log_error("Invalid offload iSCSI host %u. Missing " + "hwaddress. Try upgrading %s driver.", + hinfo->host_no, hinfo->iface.transport_name); + return 0; + } + + memset(&iface, 0, sizeof(struct iface_rec)); + if (kern_iface) { + memcpy(&iface, kern_iface, sizeof(iface)); + + snprintf(iface.name, sizeof(iface.name), "%s.%s.%s.%u", + kern_iface->transport_name, + kern_iface->hwaddress, + iface_get_iptype(kern_iface) == ISCSI_IFACE_TYPE_IPV4 ? + "ipv4" : "ipv6", kern_iface->iface_num); + } else { + snprintf(iface.name, sizeof(iface.name), "%s.%s", + hinfo->iface.transport_name, hinfo->iface.hwaddress); + } + + strcpy(iface.hwaddress, hinfo->iface.hwaddress); + strcpy(iface.transport_name, hinfo->iface.transport_name); + + memset(iface_path, 0, sizeof(iface_path)); + snprintf(iface_path, PATH_MAX, "%s/%s", IFACE_CONFIG_DIR, + iface.name); + + if (access(iface_path, F_OK) != 0) { + /* not found so create it */ + if (iface_conf_write(&iface)) { + log_error("Could not create default iface conf %s.", + iface.name); + /* fall through - will not be persistent */ + } + } + + return 0; +} + +static int __iface_setup_host_bindings(void *data, struct host_info *hinfo) +{ + struct iface_rec *def_iface; + struct iscsi_transport *t; + int i = 0, nr_found; + + t = iscsi_sysfs_get_transport_by_hba(hinfo->host_no); + if (!t) + return 0; + + /* do not setup binding for hosts using non offload drivers */ + while ((def_iface = default_ifaces[i++])) { + if (!strcmp(t->name, def_iface->transport_name)) + return 0; + } + + nr_found = 0; + iscsi_sysfs_for_each_iface_on_host(hinfo, hinfo->host_no, + &nr_found, + iface_setup_binding_from_kern_iface); + if (!nr_found) + iface_setup_binding_from_kern_iface(hinfo, NULL); + return 0; +} + +/* + * Create a default iface for offload cards. We assume that we will + * be able identify each host by MAC. + */ +void iface_setup_host_bindings(void) +{ + int nr_found = 0; + + if (idbm_lock()) + return; + + if (access(IFACE_CONFIG_DIR, F_OK) != 0) { + if (mkdir(IFACE_CONFIG_DIR, 0660) != 0) { + log_error("Could not make %s. HW/OFFLOAD iscsi " + "may not be supported", IFACE_CONFIG_DIR); + idbm_unlock(); + return; + } + } + idbm_unlock(); + + transport_probe_for_offload(); + + if (iscsi_sysfs_for_each_host(NULL, &nr_found, + __iface_setup_host_bindings)) + log_error("Could not scan scsi hosts. HW/OFFLOAD iscsi " + "operations may not be supported, or please " + "see README for instructions on setting up ifaces."); +} + +void iface_copy(struct iface_rec *dst, struct iface_rec *src) +{ + if (strlen(src->name)) + strcpy(dst->name, src->name); + if (src->iface_num) + dst->iface_num = src->iface_num; + if (strlen(src->netdev)) + strcpy(dst->netdev, src->netdev); + if (strlen(src->ipaddress)) + strcpy(dst->ipaddress, src->ipaddress); + if (strlen(src->subnet_mask)) + strcpy(dst->subnet_mask, src->subnet_mask); + if (strlen(src->gateway)) + strcpy(dst->gateway, src->gateway); + if (strlen(src->bootproto)) + strcpy(dst->bootproto, src->bootproto); + if (strlen(src->ipv6_linklocal)) + strcpy(dst->ipv6_linklocal, src->ipv6_linklocal); + if (strlen(src->ipv6_router)) + strcpy(dst->ipv6_router, src->ipv6_router); + if (strlen(src->ipv6_autocfg)) + strcpy(dst->ipv6_autocfg, src->ipv6_autocfg); + if (strlen(src->linklocal_autocfg)) + strcpy(dst->linklocal_autocfg, src->linklocal_autocfg); + if (strlen(src->router_autocfg)) + strcpy(dst->router_autocfg, src->router_autocfg); + if (src->vlan_id) + dst->vlan_id = src->vlan_id; + if (src->vlan_priority) + dst->vlan_priority = src->vlan_priority; + if (strlen(src->vlan_state)) + strcpy(dst->vlan_state, src->vlan_state); + if (strlen(src->state)) + strcpy(dst->state, src->state); + if (src->mtu) + dst->mtu = src->mtu; + if (src->port) + dst->port = src->port; + if (strlen(src->delayed_ack)) + strcpy(dst->delayed_ack, src->delayed_ack); + if (strlen(src->nagle)) + strcpy(dst->nagle, src->nagle); + if (strlen(src->tcp_wsf_state)) + strcpy(dst->tcp_wsf_state, src->tcp_wsf_state); + if (src->tcp_wsf) + dst->tcp_wsf = src->tcp_wsf; + if (src->tcp_timer_scale) + dst->tcp_timer_scale = src->tcp_timer_scale; + if (strlen(src->tcp_timestamp)) + strcpy(dst->tcp_timestamp, src->tcp_timestamp); + if (strlen(src->dhcp_dns)) + strcpy(dst->dhcp_dns, src->dhcp_dns); + if (strlen(src->dhcp_slp_da)) + strcpy(dst->dhcp_slp_da, src->dhcp_slp_da); + if (strlen(src->tos_state)) + strcpy(dst->tos_state, src->tos_state); + if (src->tos) + dst->tos = src->tos; + if (strlen(src->gratuitous_arp)) + strcpy(dst->gratuitous_arp, src->gratuitous_arp); + if (strlen(src->dhcp_alt_client_id_state)) + strcpy(dst->dhcp_alt_client_id_state, + src->dhcp_alt_client_id_state); + if (strlen(src->dhcp_alt_client_id)) + strcpy(dst->dhcp_alt_client_id, src->dhcp_alt_client_id); + if (strlen(src->dhcp_req_vendor_id_state)) + strcpy(dst->dhcp_req_vendor_id_state, + src->dhcp_req_vendor_id_state); + if (strlen(src->dhcp_vendor_id_state)) + strcpy(dst->dhcp_vendor_id_state, src->dhcp_vendor_id_state); + if (strlen(src->dhcp_vendor_id)) + strcpy(dst->dhcp_vendor_id, src->dhcp_vendor_id); + if (strlen(src->dhcp_learn_iqn)) + strcpy(dst->dhcp_learn_iqn, src->dhcp_learn_iqn); + if (strlen(src->fragmentation)) + strcpy(dst->fragmentation, src->fragmentation); + if (strlen(src->incoming_forwarding)) + strcpy(dst->incoming_forwarding, src->incoming_forwarding); + if (src->ttl) + dst->ttl = src->ttl; + if (strlen(src->gratuitous_neighbor_adv)) + strcpy(dst->gratuitous_neighbor_adv, + src->gratuitous_neighbor_adv); + if (strlen(src->redirect)) + strcpy(dst->redirect, src->redirect); + if (strlen(src->mld)) + strcpy(dst->mld, src->mld); + if (src->flow_label) + dst->flow_label = src->flow_label; + if (src->traffic_class) + dst->traffic_class = src->traffic_class; + if (src->hop_limit) + dst->hop_limit = src->hop_limit; + if (src->nd_reachable_tmo) + dst->nd_reachable_tmo = src->nd_reachable_tmo; + if (src->nd_rexmit_time) + dst->nd_rexmit_time = src->nd_rexmit_time; + if (src->nd_stale_tmo) + dst->nd_stale_tmo = src->nd_stale_tmo; + if (src->dup_addr_detect_cnt) + dst->dup_addr_detect_cnt = src->dup_addr_detect_cnt; + if (src->router_adv_link_mtu) + dst->router_adv_link_mtu = src->router_adv_link_mtu; + if (src->def_task_mgmt_tmo) + dst->def_task_mgmt_tmo = src->def_task_mgmt_tmo; + if (strlen(src->header_digest)) + strcpy(dst->header_digest, src->header_digest); + if (strlen(src->data_digest)) + strcpy(dst->data_digest, src->data_digest); + if (strlen(src->immediate_data)) + strcpy(dst->immediate_data, src->immediate_data); + if (strlen(src->initial_r2t)) + strcpy(dst->initial_r2t, src->initial_r2t); + if (strlen(src->data_seq_inorder)) + strcpy(dst->data_seq_inorder, src->data_seq_inorder); + if (strlen(src->data_pdu_inorder)) + strcpy(dst->data_pdu_inorder, src->data_pdu_inorder); + if (src->erl) + dst->erl = src->erl; + if (src->max_recv_dlength) + dst->max_recv_dlength = src->max_recv_dlength; + if (src->first_burst_len) + dst->first_burst_len = src->first_burst_len; + if (src->max_out_r2t) + dst->max_out_r2t = src->max_out_r2t; + if (src->max_burst_len) + dst->max_burst_len = src->max_burst_len; + if (strlen(src->chap_auth)) + strcpy(dst->chap_auth, src->chap_auth); + if (strlen(src->bidi_chap)) + strcpy(dst->bidi_chap, src->bidi_chap); + if (strlen(src->strict_login_comp)) + strcpy(dst->strict_login_comp, src->strict_login_comp); + if (strlen(src->discovery_auth)) + strcpy(dst->discovery_auth, src->discovery_auth); + if (strlen(src->discovery_logout)) + strcpy(dst->discovery_logout, src->discovery_logout); + if (strlen(src->hwaddress)) + strcpy(dst->hwaddress, src->hwaddress); + if (strlen(src->transport_name)) + strcpy(dst->transport_name, src->transport_name); + if (strlen(src->iname)) + strcpy(dst->iname, src->iname); +} + +int iface_is_valid(struct iface_rec *iface) +{ + if (!iface) + return 0; + + if (!strlen(iface->name)) + return 0; + + if (!strlen(iface->transport_name)) + return 0; + + if (iface_is_bound_by_hwaddr(iface)) + return 1; + + if (iface_is_bound_by_netdev(iface)) + return 1; +// if (iface_is_bound_by_ipaddr(iface)) +// return 1; + + /* bound by transport name */ + return 1; +} + +int iface_match(struct iface_rec *pattern, struct iface_rec *iface) +{ + if (!pattern || !iface) + return 1; + + if (!strlen(pattern->name)) + return 1; + + if (!strcmp(pattern->name, iface->name)) { + if (!strcmp(pattern->name, DEFAULT_IFACENAME)) + return 1; + /* + * For default we allow the same name, but different + * transports. + */ + if (!strlen(pattern->transport_name)) + return 1; + + if (!strcmp(pattern->transport_name, iface->transport_name)) + return 1; + /* fall through */ + } + return 0; +} + +int iface_is_bound_by_hwaddr(struct iface_rec *iface) +{ + if (iface && strlen(iface->hwaddress) && + strcmp(iface->hwaddress, DEFAULT_HWADDRESS)) + return 1; + return 0; +} + +int iface_is_bound_by_netdev(struct iface_rec *iface) +{ + if (iface && strlen(iface->netdev) && + strcmp(iface->netdev, DEFAULT_NETDEV)) + return 1; + return 0; +} + +int iface_is_bound_by_ipaddr(struct iface_rec *iface) +{ + if (iface && strlen(iface->ipaddress) && + strcmp(iface->ipaddress, DEFAULT_IPADDRESS)) + return 1; + return 0; +} + +void iface_print(struct iscsi_iface *iface, char *prefix) +{ + const char *ipaddress = iscsi_iface_ipaddress_get(iface); + + printf("%sIface Name: %s\n", prefix, + (strlen(iscsi_iface_name_get(iface)) > 0) ? + iscsi_iface_name_get(iface) : UNKNOWN_VALUE); + + printf("%sIface Transport: %s\n", prefix, + (strlen(iscsi_iface_transport_name_get(iface)) > 0) ? + iscsi_iface_transport_name_get(iface) : UNKNOWN_VALUE); + + printf("%sIface Initiatorname: %s\n", prefix, + (strlen(iscsi_iface_iname_get(iface)) > 0) ? + iscsi_iface_iname_get(iface) : UNKNOWN_VALUE); + + if (!strlen(ipaddress)) + printf("%sIface IPaddress: %s\n", prefix, UNKNOWN_VALUE); + else if (strchr(ipaddress, '.')) + printf("%sIface IPaddress: %s\n", prefix, ipaddress); + else + printf("%sIface IPaddress: [%s]\n", prefix, ipaddress); + + printf("%sIface HWaddress: %s\n", prefix, + (strlen(iscsi_iface_hwaddress_get(iface)) > 0) ? + iscsi_iface_hwaddress_get(iface) : UNKNOWN_VALUE); + + printf("%sIface Netdev: %s\n", prefix, + (strlen(iscsi_iface_netdev_get(iface)) > 0) ? + iscsi_iface_netdev_get(iface) : UNKNOWN_VALUE); +} + +struct iface_print_node_data { + struct node_rec *last_rec; + struct iface_rec *match_iface; +}; + +static int iface_print_nodes(void *data, node_rec_t *rec) +{ + struct iface_print_node_data *print_data = data; + + if (!iface_match(print_data->match_iface, &rec->iface)) + return -1; + + idbm_print_node_tree(print_data->last_rec, rec, "\t"); + return 0; +} + +/** + * iface_print_tree - print out binding info + * @iface: iface to print out + * + * Currently this looks like the iface conf print, because we only + * have the binding info. When we store the iface specific node settings + * in the iface record then it will look different. + */ +int iface_print_tree(void *data, struct iface_rec *iface) +{ + struct node_rec last_rec; + struct iface_print_node_data print_data; + int num_found = 0; + + printf("Iface: %s\n", iface->name); + + memset(&last_rec, 0, sizeof(struct node_rec )); + + print_data.match_iface = iface; + print_data.last_rec = &last_rec; + + idbm_for_each_rec(&num_found, &print_data, iface_print_nodes, false); + return 0; +} + +void iface_print_flat(struct iscsi_iface *iface) +{ + printf("%s %s,%s,%s,%s,%s\n", + _unwrap(iscsi_iface_name_get(iface)), + _unwrap(iscsi_iface_transport_name_get(iface)), + _unwrap(iscsi_iface_hwaddress_get(iface)), + _unwrap(iscsi_iface_ipaddress_get(iface)), + _unwrap(iscsi_iface_netdev_get(iface)), + _unwrap(iscsi_iface_iname_get(iface))); +} + +int iface_for_each_iface(void *data, int skip_def, int *nr_found, + iface_op_fn *fn) +{ + DIR *iface_dirfd; + struct dirent *iface_dent; + struct iface_rec *iface, *def_iface; + int err = 0, i = 0; + + if (!skip_def) { + while ((def_iface = default_ifaces[i++])) { + iface = iface_alloc(def_iface->name, &err); + if (!iface) { + log_error("Could not add iface %s.", + def_iface->name); + continue; + } + iface_copy(iface, def_iface); + err = fn(data, iface); + free(iface); + if (err) + return err; + (*nr_found)++; + } + } + + iface_dirfd = opendir(IFACE_CONFIG_DIR); + if (!iface_dirfd) + return errno; + + while ((iface_dent = readdir(iface_dirfd))) { + if (!strcmp(iface_dent->d_name, ".") || + !strcmp(iface_dent->d_name, "..")) + continue; + + log_debug(5, "iface_for_each_iface found %s", + iface_dent->d_name); + iface = iface_alloc(iface_dent->d_name, &err); + if (!iface || err) { + if (err == ISCSI_ERR_INVAL) + log_error("Invalid iface name %s. Must be " + "from 1 to %d characters.", + iface_dent->d_name, + ISCSI_MAX_IFACE_LEN - 1); + else + log_error("Could not add iface %s.", + iface_dent->d_name); + continue; + } + + err = idbm_lock(); + if (err) { + free(iface); + continue; + } + + err = __iface_conf_read(iface); + idbm_unlock(); + if (err) { + log_error("Could not read def iface %s (err %d)", + iface->name, err); + free(iface); + continue; + } + + if (!iface_is_valid(iface)) { + log_debug(5, "iface is not valid " + "Iface settings " iface_fmt, + iface_str(iface)); + free(iface); + continue; + } + + err = fn(data, iface); + free(iface); + if (err) + break; + (*nr_found)++; + } + + closedir(iface_dirfd); + return err; +} + +static int iface_link(void *data, struct iface_rec *iface) +{ + struct list_head *ifaces = data; + struct iface_rec *iface_copy; + + iface_copy = calloc(1, sizeof(*iface_copy)); + if (!iface_copy) + return ISCSI_ERR_NOMEM; + + memcpy(iface_copy, iface, sizeof(*iface_copy)); + INIT_LIST_HEAD(&iface_copy->list); + list_add_tail(&iface_copy->list, ifaces); + return 0; +} + +/** + * iface_link_ifaces - link non default ifaces + * @ifaces: list to add ifaces to + * + * This will return a list of the ifaces created by iscsiadm + * or the user. It does not return the static default ones. + */ +void iface_link_ifaces(struct list_head *ifaces) +{ + int nr_found = 0; + + iface_for_each_iface(ifaces, 1, &nr_found, iface_link); +} + +/** + * iface_setup_from_boot_context - setup iface from boot context info + * @iface: iface t setup + * @context: boot context info + * + * Returns 1 if setup for offload. + */ +int iface_setup_from_boot_context(struct iface_rec *iface, + struct boot_context *context) +{ + struct iscsi_transport *t = NULL; + uint32_t hostno; + + if (strlen(context->initiatorname)) + strlcpy(iface->iname, context->initiatorname, + sizeof(iface->iname)); + + if (strlen(context->scsi_host_name)) { + if (sscanf(context->scsi_host_name, + "iscsi_boot%u", &hostno) != 1) { + log_error("Could not parse %s's host no.", + context->scsi_host_name); + return 0; + } + } else if (strlen(context->iface)) { +/* this ifdef is only temp until distros and firmwares are updated */ +#ifdef OFFLOAD_BOOT_SUPPORTED + char transport_name[ISCSI_TRANSPORT_NAME_MAXLEN]; + int rc; + + memset(transport_name, 0, ISCSI_TRANSPORT_NAME_MAXLEN); + /* make sure offload driver is loaded */ + if (!net_get_transport_name_from_netdev(context->iface, + transport_name)) + t = iscsi_sysfs_get_transport_by_name(transport_name); + + if (net_ifup_netdev(context->iface)) + log_warning("Could not bring up netdev %s for boot", + context->iface); + + hostno = iscsi_sysfs_get_host_no_from_hwaddress(context->mac, + &rc); + if (rc) { + /* + * If the MAC in the boot info does not match an iscsi + * host then the MAC must be for network card, so boot + * is not going to be offloaded. + */ + log_debug(3, "Could not match %s to host", + context->mac); + return 0; + } + + strlcpy(iface->netdev, context->iface, sizeof(iface->netdev)); +#else + return 0; +#endif + } else + return 0; + + /* + * set up for access through a offload card. + */ + if (!t) + t = iscsi_sysfs_get_transport_by_hba(hostno); + if (!t) { + log_error("Could not get transport for host%u. " + "Make sure the iSCSI driver is loaded.", + hostno); + return 0; + } + strcpy(iface->transport_name, t->name); + + memset(iface->name, 0, sizeof(iface->name)); + snprintf(iface->name, sizeof(iface->name), "%s.%s", + iface->transport_name, context->mac); + strlcpy(iface->hwaddress, context->mac, + sizeof(iface->hwaddress)); + strlcpy(iface->ipaddress, context->ipaddr, + sizeof(iface->ipaddress)); + iface->vlan_id = atoi(context->vlan); + strlcpy(iface->subnet_mask, context->mask, + sizeof(iface->subnet_mask)); + strlcpy(iface->gateway, context->gateway, + sizeof(iface->gateway)); + log_debug(1, "iface " iface_fmt "", iface_str(iface)); + return 1; +} + +/** + * iface_create_ifaces_from_boot_contexts - create ifaces based on boot info + * @ifaces: list to store ifaces in + * @targets: list of targets to create ifaces from + * + * This function will create an iface struct based on the boot info + * and it will create (or update if existing already) an iface rec in + * the ifaces dir based on the info. + */ +int iface_create_ifaces_from_boot_contexts(struct list_head *ifaces, + struct list_head *targets) +{ + struct boot_context *context; + struct iface_rec *iface, *tmp_iface; + int rc = 0; + + list_for_each_entry(context, targets, list) { + rc = 0; + /* use dummy name. If valid it will get overwritten below */ + iface = iface_alloc(DEFAULT_IFACENAME, &rc); + if (!iface) { + log_error("Could not setup iface %s for boot", + context->iface); + goto fail; + } + if (!iface_setup_from_boot_context(iface, context)) { + /* no offload so forget it */ + free(iface); + continue; + } + + rc = iface_conf_write(iface); + if (rc) { + log_error("Could not setup default iface conf " + "for %s.", iface->name); + free(iface); + goto fail; + } + list_add_tail(&iface->list, ifaces); + } + + return 0; +fail: + list_for_each_entry_safe(iface, tmp_iface, ifaces, list) { + list_del(&iface->list); + free(iface); + } + return rc; +} + +struct iface_param_count { + struct iface_rec *primary; + int count; +}; + +#define IFACE_NET_PARAM_EN_CNT(param_val, cnt) { \ + if (!strcmp(param_val, "disable") || \ + !strcmp(param_val, "enable")) \ + (*cnt)++; \ +} + +/** + * iface_get_common_param_count - Gets common parameters count for given iface + * @iface: iface to setup + * @count: number of parameters to set + */ +static void iface_get_common_param_count(struct iface_rec *iface, int *count) +{ + if (strcmp(iface->vlan_state, "disable")) { + /* vlan_state enabled */ + (*count)++; + + if (iface->vlan_id) + /* For vlan value */ + (*count)++; + } else { + /* vlan_state disabled */ + (*count)++; + } + + if (iface->mtu) + (*count)++; + + if (iface->port) + (*count)++; + + IFACE_NET_PARAM_EN_CNT(iface->delayed_ack, count); + IFACE_NET_PARAM_EN_CNT(iface->nagle, count); + IFACE_NET_PARAM_EN_CNT(iface->tcp_wsf_state, count); + IFACE_NET_PARAM_EN_CNT(iface->tcp_timestamp, count); + IFACE_NET_PARAM_EN_CNT(iface->redirect, count); + IFACE_NET_PARAM_EN_CNT(iface->header_digest, count); + IFACE_NET_PARAM_EN_CNT(iface->data_digest, count); + IFACE_NET_PARAM_EN_CNT(iface->immediate_data, count); + IFACE_NET_PARAM_EN_CNT(iface->initial_r2t, count); + IFACE_NET_PARAM_EN_CNT(iface->data_seq_inorder, count); + IFACE_NET_PARAM_EN_CNT(iface->data_pdu_inorder, count); + IFACE_NET_PARAM_EN_CNT(iface->chap_auth, count); + IFACE_NET_PARAM_EN_CNT(iface->bidi_chap, count); + IFACE_NET_PARAM_EN_CNT(iface->strict_login_comp, count); + IFACE_NET_PARAM_EN_CNT(iface->discovery_auth, count); + IFACE_NET_PARAM_EN_CNT(iface->discovery_logout, count); + + if (iface->tcp_wsf) + (*count)++; + + if (iface->tcp_timer_scale) + (*count)++; + + if (iface->def_task_mgmt_tmo) + (*count)++; + + if (iface->erl) + (*count)++; + + if (iface->max_recv_dlength) + (*count)++; + + if (iface->first_burst_len) + (*count)++; + + if (iface->max_burst_len) + (*count)++; + + if (iface->max_out_r2t) + (*count)++; +} + +/** + * __iface_get_param_count - Gets netconfig parameter count for given iface + * @data: iface_param_count structure + * @iface: iface to setup + */ +static int __iface_get_param_count(void *data, struct iface_rec *iface) +{ + struct iface_param_count *iface_params = data; + int iptype = ISCSI_IFACE_TYPE_IPV4; + int count = 0; + + if (strcmp(iface_params->primary->hwaddress, iface->hwaddress)) + return 0; + + iptype = iface_get_iptype(iface); + if (iptype == ISCSI_IFACE_TYPE_IPV4) { + + if (strcmp(iface->state, "disable")) { + if (strstr(iface->bootproto, "dhcp")) { + /* DHCP enabled */ + count++; + } else { + /* DHCP disabled */ + count++; + + if (strstr(iface->ipaddress, ".")) { + /* User configured IPv4 Address */ + count++; + + if (strstr(iface->subnet_mask, ".")) + /* User configured Subnet */ + count++; + + if (strstr(iface->gateway, ".")) + /* User configured Gateway */ + count++; + } else { + /* + * IPv4 Address not valid, decrement + * count of DHCP + */ + count--; + } + } + + /* + * If IPv4 configuration in iface file is valid, + * enable state and other parameters (if any) + */ + if (count) { + /* iface state */ + count++; + + IFACE_NET_PARAM_EN_CNT(iface->dhcp_dns, + &count); + + IFACE_NET_PARAM_EN_CNT(iface->dhcp_slp_da, + &count); + + IFACE_NET_PARAM_EN_CNT(iface->tos_state, + &count); + + IFACE_NET_PARAM_EN_CNT(iface->gratuitous_arp, + &count); + + IFACE_NET_PARAM_EN_CNT( + iface->dhcp_alt_client_id_state, + &count); + + if (iface->dhcp_alt_client_id[0]) + count++; + + IFACE_NET_PARAM_EN_CNT( + iface->dhcp_req_vendor_id_state, + &count); + + IFACE_NET_PARAM_EN_CNT( + iface->dhcp_vendor_id_state, + &count); + + if (iface->dhcp_vendor_id[0]) + count++; + + IFACE_NET_PARAM_EN_CNT(iface->dhcp_learn_iqn, + &count); + + IFACE_NET_PARAM_EN_CNT(iface->fragmentation, + &count); + + IFACE_NET_PARAM_EN_CNT( + iface->incoming_forwarding, + &count); + + if (iface->tos) + count++; + + if (iface->ttl) + count++; + + iface_get_common_param_count(iface, &count); + } + } else { + /* IPv4 is disabled, iface state */ + count++; + } + } else if (iptype == ISCSI_IFACE_TYPE_IPV6) { + + if (strcmp(iface->state, "disable")) { + + /* IPv6 Address */ + if (strstr(iface->ipv6_autocfg, "nd") || + strstr(iface->ipv6_autocfg, "dhcpv6")) { + /* Autocfg enabled */ + count++; + } else { + /* Autocfg disabled */ + count++; + + if (strstr(iface->ipaddress, ":")) + /* User configured IPv6 Address */ + count++; + else + /* + * IPv6 Address not valid, decrement + * count of IPv6 Autocfg + */ + count--; + } + + /* IPv6 LinkLocal Address */ + if (strstr(iface->linklocal_autocfg, "auto")) + /* Autocfg enabled */ + count++; + else { + /* Autocfg disabled */ + count++; + + if (strstr(iface->ipv6_linklocal, ":")) + /* User configured LinkLocal Address */ + count++; + else + /* + * LinkLocal Address not valid, + * decrement count of LinkLocal Autocfg + */ + count--; + } + + /* IPv6 Router Address */ + if (strstr(iface->router_autocfg, "auto")) + /* Autocfg enabled */ + count++; + else { + /* Autocfg disabled */ + count++; + + if (strstr(iface->ipv6_router, ":")) + /* User configured Router Address */ + count++; + else + /* + * Router Address not valid, + * decrement count of Router Autocfg + */ + count--; + } + + /* + * If IPv6 configuration in iface file is valid, + * enable state and other parameters (if any) + */ + if (count) { + /* iface state */ + count++; + + IFACE_NET_PARAM_EN_CNT( + iface->gratuitous_neighbor_adv, + &count); + + IFACE_NET_PARAM_EN_CNT(iface->mld, &count); + + if (iface->flow_label) + count++; + + if (iface->traffic_class) + count++; + + if (iface->hop_limit) + count++; + + if (iface->nd_reachable_tmo) + count++; + + if (iface->nd_rexmit_time) + count++; + + if (iface->nd_stale_tmo) + count++; + + if (iface->dup_addr_detect_cnt) + count++; + + if (iface->router_adv_link_mtu) + count++; + + iface_get_common_param_count(iface, &count); + } + } else { + /* IPv6 is disabled, iface state */ + count++; + } + } + + iface_params->count += count; + return 0; +} + +/** + * iface_get_param_count - Gets netconfig parameter count from iface + * @iface: iface to setup + * @iface_all: Flag for number of ifaces to traverse (1 for all) + * + * Returns netconfig parameter count. + */ +int iface_get_param_count(struct iface_rec *iface, int iface_all) +{ + int num_found = 0, rc; + struct iface_param_count iface_params; + + log_debug(8, "In iface_get_param_count"); + + iface_params.primary = iface; + iface_params.count = 0; + + if (iface_all) + rc = iface_for_each_iface(&iface_params, 0, &num_found, + __iface_get_param_count); + else + rc = __iface_get_param_count(&iface_params, iface); + + log_debug(8, "iface_get_param_count: rc = %d, count = %d", + rc, iface_params.count); + return iface_params.count; +} + +/* write integer parameter value */ +static int iface_fill_int_param_val(struct iovec *iov, uint32_t iface_num, + uint8_t iface_type, uint16_t param, + uint8_t param_type, uint32_t param_len, + uint32_t param_val) +{ + int len; + struct iscsi_iface_param_info *net_param; + struct nlattr *attr; + uint8_t val8 = 0; + uint16_t val16 = 0; + uint32_t val32 = 0; + char *val = NULL; + + len = sizeof(struct iscsi_iface_param_info) + param_len; + iov->iov_base = iscsi_nla_alloc(param, len); + if (!(iov->iov_base)) + return 1; + + attr = iov->iov_base; + iov->iov_len = NLA_ALIGN(attr->nla_len); + net_param = (struct iscsi_iface_param_info *)ISCSI_NLA_DATA(attr); + net_param->iface_num = iface_num; + net_param->len = param_len; + net_param->param = param; + net_param->iface_type = iface_type; + net_param->param_type = param_type; + switch (param_len) { + case 1: + val8 = (uint8_t)param_val; + val = (char *)&val8; + break; + + case 2: + val16 = (uint16_t)param_val; + val = (char *)&val16; + break; + + case 4: + val32 = (uint32_t)param_val; + val = (char *)&val32; + break; + + default: + goto free; + } + memcpy(net_param->value, val, param_len); + return 0; +free: + free(iov->iov_base); + iov->iov_base = NULL; + iov->iov_len = 0; + return 1; +} + +#define IFACE_SET_PARAM_INTVAL(iov, inum, itype, param, ptype, plen, \ + ival, gcnt, lcnt) { \ + if (ival && !iface_fill_int_param_val(iov, inum, itype, param, \ + ptype, plen, ival)) { \ + (*gcnt)++; \ + (*lcnt)++; \ + } \ +} + +/* IPv4/IPv6 VLAN_ID: decimal value <= 4095 */ +static int iface_fill_vlan_id(struct iovec *iov, struct iface_rec *iface, + uint32_t iface_type) +{ + int len; + struct iscsi_iface_param_info *net_param; + uint16_t vlan = 0; + struct nlattr *attr; + + len = sizeof(struct iscsi_iface_param_info) + 2; + iov->iov_base = iscsi_nla_alloc(ISCSI_NET_PARAM_VLAN_TAG, len); + if (!(iov->iov_base)) + return 1; + + attr = iov->iov_base; + iov->iov_len = NLA_ALIGN(attr->nla_len); + net_param = (struct iscsi_iface_param_info *)ISCSI_NLA_DATA(attr); + net_param->param = ISCSI_NET_PARAM_VLAN_TAG; + net_param->iface_type = iface_type; + net_param->iface_num = iface->iface_num; + net_param->param_type = ISCSI_NET_PARAM; + net_param->len = 2; + if (iface->vlan_id <= ISCSI_MAX_VLAN_ID && + iface->vlan_priority <= ISCSI_MAX_VLAN_PRIORITY) + /* + * Bit 15-13: User Priority of VLAN + * Bit 11-00: VLAN ID + */ + vlan = (iface->vlan_priority << 13) | + (iface->vlan_id & ISCSI_MAX_VLAN_ID); + memcpy(net_param->value, &vlan, net_param->len); + return 0; +} + +/* disable/enable parameters */ +static int iface_fill_param_state(struct iovec *iov, uint32_t iface_num, + uint8_t iface_type, uint16_t param, + uint8_t param_type, char *param_val) +{ + int len; + struct iscsi_iface_param_info *net_param; + struct nlattr *attr; + + if (!param_val[0]) + return 1; + + len = sizeof(struct iscsi_iface_param_info) + 1; + iov->iov_base = iscsi_nla_alloc(param, len); + if (!(iov->iov_base)) + return 1; + + attr = iov->iov_base; + iov->iov_len = NLA_ALIGN(attr->nla_len); + net_param = (struct iscsi_iface_param_info *)ISCSI_NLA_DATA(attr); + net_param->iface_num = iface_num; + net_param->len = 1; + net_param->param = param; + net_param->iface_type = iface_type; + net_param->param_type = param_type; + if (!strcmp(param_val, "disable")) + net_param->value[0] = ISCSI_NET_PARAM_DISABLE; + else if (!strcmp(param_val, "enable")) + net_param->value[0] = ISCSI_NET_PARAM_ENABLE; + else + goto free; + return 0; +free: + free(iov->iov_base); + iov->iov_base = NULL; + iov->iov_len = 0; + return 1; +} + +#define IFACE_SET_PARAM_STATE(iov, inum, itype, param, ptype, ival, \ + gcnt, lcnt) { \ + if (!iface_fill_param_state(iov, inum, itype, param, ptype, \ + ival)) { \ + (*gcnt)++; \ + (*lcnt)++; \ + } \ +} + +/* IPv4 Bootproto: DHCP/static */ +static int iface_fill_net_bootproto(struct iovec *iov, struct iface_rec *iface) +{ + int len; + struct iscsi_iface_param_info *net_param; + struct nlattr *attr; + + len = sizeof(struct iscsi_iface_param_info) + 1; + iov->iov_base = iscsi_nla_alloc(ISCSI_NET_PARAM_IPV4_BOOTPROTO, len); + if (!(iov->iov_base)) + return 1; + + attr = iov->iov_base; + iov->iov_len = NLA_ALIGN(attr->nla_len); + net_param = (struct iscsi_iface_param_info *)ISCSI_NLA_DATA(attr); + net_param->param = ISCSI_NET_PARAM_IPV4_BOOTPROTO; + net_param->iface_type = ISCSI_IFACE_TYPE_IPV4; + net_param->iface_num = iface->iface_num; + net_param->param_type = ISCSI_NET_PARAM; + net_param->len = 1; + if (!strcmp(iface->bootproto, "dhcp")) + net_param->value[0] = ISCSI_BOOTPROTO_DHCP; + else + net_param->value[0] = ISCSI_BOOTPROTO_STATIC; + return 0; +} + +/* IPv6 IPAddress Autocfg: nd/dhcpv6/disable */ +static int iface_fill_net_autocfg(struct iovec *iov, struct iface_rec *iface) +{ + int len; + struct iscsi_iface_param_info *net_param; + struct nlattr *attr; + + len = sizeof(struct iscsi_iface_param_info) + 1; + iov->iov_base = iscsi_nla_alloc(ISCSI_NET_PARAM_IPV6_ADDR_AUTOCFG, len); + if (!(iov->iov_base)) + return 1; + + attr = iov->iov_base; + iov->iov_len = NLA_ALIGN(attr->nla_len); + net_param = (struct iscsi_iface_param_info *)ISCSI_NLA_DATA(attr); + net_param->param = ISCSI_NET_PARAM_IPV6_ADDR_AUTOCFG; + net_param->iface_type = ISCSI_IFACE_TYPE_IPV6; + net_param->param_type = ISCSI_NET_PARAM; + net_param->len = 1; + + if (!strcmp(iface->ipv6_autocfg, "nd")) + net_param->value[0] = ISCSI_IPV6_AUTOCFG_ND_ENABLE; + else if (!strcmp(iface->ipv6_autocfg, "dhcpv6")) + net_param->value[0] = ISCSI_IPV6_AUTOCFG_DHCPV6_ENABLE; + else + net_param->value[0] = ISCSI_IPV6_AUTOCFG_DISABLE; + + return 0; +} + +/* IPv6 LinkLocal Autocfg: enable/disable */ +static int iface_fill_linklocal_autocfg(struct iovec *iov, + struct iface_rec *iface) +{ + int len; + struct iscsi_iface_param_info *net_param; + struct nlattr *attr; + + len = sizeof(struct iscsi_iface_param_info) + 1; + iov->iov_base = iscsi_nla_alloc(ISCSI_NET_PARAM_IPV6_LINKLOCAL_AUTOCFG, + len); + if (!(iov->iov_base)) + return 1; + + attr = iov->iov_base; + iov->iov_len = NLA_ALIGN(attr->nla_len); + net_param = (struct iscsi_iface_param_info *)ISCSI_NLA_DATA(attr); + net_param->param = ISCSI_NET_PARAM_IPV6_LINKLOCAL_AUTOCFG; + net_param->iface_type = ISCSI_IFACE_TYPE_IPV6; + net_param->param_type = ISCSI_NET_PARAM; + net_param->len = 1; + + if (strstr(iface->linklocal_autocfg, "auto")) + net_param->value[0] = ISCSI_IPV6_LINKLOCAL_AUTOCFG_ENABLE; + else + net_param->value[0] = ISCSI_IPV6_LINKLOCAL_AUTOCFG_DISABLE; + + return 0; +} + +/* IPv6 Router Autocfg: enable/disable */ +static int iface_fill_router_autocfg(struct iovec *iov, struct iface_rec *iface) +{ + int len; + struct iscsi_iface_param_info *net_param; + struct nlattr *attr; + + len = sizeof(struct iscsi_iface_param_info) + 1; + iov->iov_base = iscsi_nla_alloc(ISCSI_NET_PARAM_IPV6_ROUTER_AUTOCFG, + len); + if (!(iov->iov_base)) + return 1; + + attr = iov->iov_base; + iov->iov_len = NLA_ALIGN(attr->nla_len); + net_param = (struct iscsi_iface_param_info *)ISCSI_NLA_DATA(attr); + net_param->param = ISCSI_NET_PARAM_IPV6_ROUTER_AUTOCFG; + net_param->iface_type = ISCSI_IFACE_TYPE_IPV6; + net_param->param_type = ISCSI_NET_PARAM; + net_param->len = 1; + + if (strstr(iface->router_autocfg, "auto")) + net_param->value[0] = ISCSI_IPV6_ROUTER_AUTOCFG_ENABLE; + else + net_param->value[0] = ISCSI_IPV6_ROUTER_AUTOCFG_DISABLE; + + return 0; +} + +/* IPv4 IPAddress/Subnet Mask/Gateway: 4 bytes */ +static int iface_fill_net_ipv4_addr(struct iovec *iov, uint32_t iface_num, + uint16_t param, char *param_val) +{ + int rc = 1; + int len; + struct iscsi_iface_param_info *net_param; + struct nlattr *attr; + + len = sizeof(struct iscsi_iface_param_info) + 4; + iov->iov_base = iscsi_nla_alloc(param, len); + if (!(iov->iov_base)) + return 1; + + attr = iov->iov_base; + iov->iov_len = NLA_ALIGN(attr->nla_len); + net_param = (struct iscsi_iface_param_info *)ISCSI_NLA_DATA(attr); + net_param->param = param; + net_param->iface_type = ISCSI_IFACE_TYPE_IPV4; + net_param->iface_num = iface_num; + net_param->len = 4; + net_param->param_type = ISCSI_NET_PARAM; + rc = inet_pton(AF_INET, param_val, net_param->value); + if (rc <= 0) + goto free; + + /* validate */ + if (!net_param->value[0] && !net_param->value[1] && + !net_param->value[2] && !net_param->value[3]) + goto free; + + return 0; +free: + free(iov->iov_base); + iov->iov_base = NULL; + iov->iov_len = 0; + return 1; +} + +#define IFACE_SET_NET_PARAM_IPV4_ADDR(iov, inum, param, ival, gcnt, \ + lcnt) { \ + if (strstr(ival, ".")) { \ + if (!iface_fill_net_ipv4_addr(iov, inum, param, ival)) {\ + (*gcnt)++; \ + (*lcnt)++; \ + } \ + } \ +} + +/* IPv6 IPAddress/LinkLocal/Router: 16 bytes */ +static int iface_fill_net_ipv6_addr(struct iovec *iov, uint32_t iface_num, + uint16_t param, char *param_val) +{ + int rc; + int len; + struct iscsi_iface_param_info *net_param; + struct nlattr *attr; + + len = sizeof(struct iscsi_iface_param_info) + 16; + iov->iov_base = iscsi_nla_alloc(param, len); + if (!(iov->iov_base)) + return 1; + + attr = iov->iov_base; + iov->iov_len = NLA_ALIGN(attr->nla_len); + net_param = (struct iscsi_iface_param_info *)ISCSI_NLA_DATA(attr); + net_param->param = param; + net_param->iface_type = ISCSI_IFACE_TYPE_IPV6; + net_param->iface_num = iface_num; + net_param->param_type = ISCSI_NET_PARAM; + net_param->len = 16; + rc = inet_pton(AF_INET6, param_val, net_param->value); + if (rc <= 0) + goto free; + + return 0; +free: + free(iov->iov_base); + iov->iov_base = NULL; + iov->iov_len = 0; + return 1; +} + +#define IFACE_SET_NET_PARAM_IPV6_ADDR(iov, inum, param, ival, gcnt, \ + lcnt) { \ + if (strstr(ival, ":")) { \ + if (!iface_fill_net_ipv6_addr(iov, inum, param, ival)) {\ + (*gcnt)++; \ + (*lcnt)++; \ + } \ + } \ +} + +/* write string parameter value */ +static int iface_fill_str_param_val(struct iovec *iov, uint32_t iface_num, + uint8_t iface_type, uint16_t param, + uint32_t param_len, char *param_val) +{ + int len; + struct iscsi_iface_param_info *net_param; + struct nlattr *attr; + + if (!param_val[0]) + return 1; + + len = sizeof(struct iscsi_iface_param_info) + param_len; + iov->iov_base = iscsi_nla_alloc(param, len); + if (!(iov->iov_base)) + return 1; + + attr = iov->iov_base; + iov->iov_len = NLA_ALIGN(attr->nla_len); + net_param = (struct iscsi_iface_param_info *)ISCSI_NLA_DATA(attr); + net_param->iface_num = iface_num; + net_param->len = param_len; + net_param->param = param; + net_param->iface_type = iface_type; + net_param->param_type = ISCSI_NET_PARAM; + memcpy(net_param->value, param_val, param_len); + return 0; +} + +#define IFACE_SET_NET_PARAM_STRVAL(iov, inum, itype, param, plen, \ + ival, gcnt, lcnt) { \ + if (!iface_fill_str_param_val(iov, inum, itype, param, plen, \ + ival)) { \ + (*gcnt)++; \ + (*lcnt)++; \ + } \ +} + +struct iface_net_config { + struct iface_rec *primary; + struct iovec *iovs; + int count; +}; + +static int __iface_build_net_config(void *data, struct iface_rec *iface) +{ + struct iface_net_config *net_config = data; + struct iovec *iov; + int iptype = ISCSI_IFACE_TYPE_IPV4; + int count = 0; + + if (strcmp(net_config->primary->hwaddress, iface->hwaddress)) + return 0; + + /* start at 2, because 0 is for nlmsghdr and 1 for event */ + iov = net_config->iovs + 2; + + if (!iface->port) + iface->port = 3260; + + iptype = iface_get_iptype(iface); + switch (iptype) { + case ISCSI_IFACE_TYPE_IPV4: + if (!strcmp(iface->state, "disable")) { + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV4, + ISCSI_NET_PARAM_IFACE_ENABLE, + ISCSI_NET_PARAM, + iface->state, + &net_config->count, + &count); + return 0; + } + + if (strstr(iface->bootproto, "dhcp")) { + if (!iface_fill_net_bootproto(&iov[net_config->count], + iface)) { + net_config->count++; + count++; + } + } else if (strstr(iface->ipaddress, ".")) { + if (!iface_fill_net_bootproto(&iov[net_config->count], + iface)) { + net_config->count++; + count++; + } + + IFACE_SET_NET_PARAM_IPV4_ADDR(&iov[net_config->count], + iface->iface_num, + ISCSI_NET_PARAM_IPV4_ADDR, + iface->ipaddress, + &net_config->count, + &count); + + IFACE_SET_NET_PARAM_IPV4_ADDR(&iov[net_config->count], + iface->iface_num, + ISCSI_NET_PARAM_IPV4_SUBNET, + iface->subnet_mask, + &net_config->count, + &count); + + IFACE_SET_NET_PARAM_IPV4_ADDR(&iov[net_config->count], + iface->iface_num, + ISCSI_NET_PARAM_IPV4_GW, + iface->gateway, + &net_config->count, + &count); + } + + /* + * If IPv4 configuration in iface file is valid, + * fill state and other parameters (if any) + */ + if (count) { + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV4, + ISCSI_NET_PARAM_IPV4_DHCP_DNS_ADDR_EN, + ISCSI_NET_PARAM, + iface->dhcp_dns, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV4, + ISCSI_NET_PARAM_IPV4_DHCP_SLP_DA_EN, + ISCSI_NET_PARAM, + iface->dhcp_slp_da, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV4, + ISCSI_NET_PARAM_IPV4_TOS_EN, + ISCSI_NET_PARAM, + iface->tos_state, + &net_config->count, + &count); + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV4, + ISCSI_NET_PARAM_IPV4_TOS, + ISCSI_NET_PARAM, + 1, + iface->tos, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV4, + ISCSI_NET_PARAM_IPV4_GRAT_ARP_EN, + ISCSI_NET_PARAM, + iface->gratuitous_arp, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV4, + ISCSI_NET_PARAM_IPV4_DHCP_ALT_CLIENT_ID_EN, + ISCSI_NET_PARAM, + iface->dhcp_alt_client_id_state, + &net_config->count, + &count); + + IFACE_SET_NET_PARAM_STRVAL(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV4, + ISCSI_NET_PARAM_IPV4_DHCP_ALT_CLIENT_ID, + strlen(iface->dhcp_alt_client_id), + iface->dhcp_alt_client_id, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV4, + ISCSI_NET_PARAM_IPV4_DHCP_REQ_VENDOR_ID_EN, + ISCSI_NET_PARAM, + iface->dhcp_req_vendor_id_state, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV4, + ISCSI_NET_PARAM_IPV4_DHCP_USE_VENDOR_ID_EN, + ISCSI_NET_PARAM, + iface->dhcp_vendor_id_state, + &net_config->count, + &count); + + IFACE_SET_NET_PARAM_STRVAL(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV4, + ISCSI_NET_PARAM_IPV4_DHCP_VENDOR_ID, + strlen(iface->dhcp_vendor_id), + iface->dhcp_vendor_id, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV4, + ISCSI_NET_PARAM_IPV4_DHCP_LEARN_IQN_EN, + ISCSI_NET_PARAM, + iface->dhcp_learn_iqn, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV4, + ISCSI_NET_PARAM_IPV4_FRAGMENT_DISABLE, + ISCSI_NET_PARAM, + iface->fragmentation, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV4, + ISCSI_NET_PARAM_IPV4_IN_FORWARD_EN, + ISCSI_NET_PARAM, + iface->incoming_forwarding, + &net_config->count, + &count); + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV4, + ISCSI_NET_PARAM_IPV4_TTL, + ISCSI_NET_PARAM, + 1, + iface->ttl, + &net_config->count, + &count); + } + break; + + case ISCSI_IFACE_TYPE_IPV6: + if (!strcmp(iface->state, "disable")) { + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV6, + ISCSI_NET_PARAM_IFACE_ENABLE, + ISCSI_NET_PARAM, + iface->state, + &net_config->count, + &count); + return 0; + } + + /* For IPv6 Address */ + if (strstr(iface->ipv6_autocfg, "nd") || + strstr(iface->ipv6_autocfg, "dhcpv6")) { + if (!iface_fill_net_autocfg(&iov[net_config->count], + iface)) { + net_config->count++; + count++; + } + } else if (strstr(iface->ipaddress, ":")) { + if (!iface_fill_net_autocfg(&iov[net_config->count], + iface)) { + net_config->count++; + count++; + } + /* User provided IPv6 Address */ + IFACE_SET_NET_PARAM_IPV6_ADDR(&iov[net_config->count], + iface->iface_num, + ISCSI_NET_PARAM_IPV6_ADDR, + iface->ipaddress, + &net_config->count, + &count); + } + + /* For LinkLocal Address */ + if (strstr(iface->linklocal_autocfg, "auto")) { + if (!iface_fill_linklocal_autocfg( + &iov[net_config->count], + iface)) { + net_config->count++; + count++; + } + } else if (strstr(iface->ipv6_linklocal, ":")) { + if (!iface_fill_linklocal_autocfg( + &iov[net_config->count], + iface)) { + net_config->count++; + count++; + } + /* User provided Link Local Address */ + IFACE_SET_NET_PARAM_IPV6_ADDR(&iov[net_config->count], + iface->iface_num, + ISCSI_NET_PARAM_IPV6_LINKLOCAL, + iface->ipv6_linklocal, + &net_config->count, + &count); + } + + /* For Router Address */ + if (strstr(iface->router_autocfg, "auto")) { + if (!iface_fill_router_autocfg(&iov[net_config->count], + iface)) { + net_config->count++; + count++; + } + } else if (strstr(iface->ipv6_router, ":")) { + if (!iface_fill_router_autocfg(&iov[net_config->count], + iface)) { + net_config->count++; + count++; + } + /* User provided Router Address */ + IFACE_SET_NET_PARAM_IPV6_ADDR(&iov[net_config->count], + iface->iface_num, + ISCSI_NET_PARAM_IPV6_ROUTER, + iface->ipv6_router, + &net_config->count, + &count); + } + + /* + * If IPv6 configuration in iface file is valid, + * fill state and other parameters + */ + if (count) { + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV6, + ISCSI_NET_PARAM_IPV6_GRAT_NEIGHBOR_ADV_EN, + ISCSI_NET_PARAM, + iface->gratuitous_neighbor_adv, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV6, + ISCSI_NET_PARAM_IPV6_MLD_EN, + ISCSI_NET_PARAM, + iface->mld, + &net_config->count, + &count); + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV6, + ISCSI_NET_PARAM_IPV6_FLOW_LABEL, + ISCSI_NET_PARAM, + 4, + iface->flow_label, + &net_config->count, + &count); + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV6, + ISCSI_NET_PARAM_IPV6_TRAFFIC_CLASS, + ISCSI_NET_PARAM, + 1, + iface->traffic_class, + &net_config->count, + &count); + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV6, + ISCSI_NET_PARAM_IPV6_HOP_LIMIT, + ISCSI_NET_PARAM, + 1, + iface->hop_limit, + &net_config->count, + &count); + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV6, + ISCSI_NET_PARAM_IPV6_ND_REACHABLE_TMO, + ISCSI_NET_PARAM, + 4, + iface->nd_reachable_tmo, + &net_config->count, + &count); + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV6, + ISCSI_NET_PARAM_IPV6_ND_REXMIT_TIME, + ISCSI_NET_PARAM, + 4, + iface->nd_rexmit_time, + &net_config->count, + &count); + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV6, + ISCSI_NET_PARAM_IPV6_ND_STALE_TMO, + ISCSI_NET_PARAM, + 4, + iface->nd_stale_tmo, + &net_config->count, + &count); + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV6, + ISCSI_NET_PARAM_IPV6_DUP_ADDR_DETECT_CNT, + ISCSI_NET_PARAM, + 1, + iface->dup_addr_detect_cnt, + &net_config->count, + &count); + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + ISCSI_IFACE_TYPE_IPV6, + ISCSI_NET_PARAM_IPV6_RTR_ADV_LINK_MTU, + ISCSI_NET_PARAM, + 4, + iface->router_adv_link_mtu, + &net_config->count, + &count); + } + break; + } + + /* Fill parameters common to IPv4 and IPv6 ifaces */ + if (count) { + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_NET_PARAM_IFACE_ENABLE, + ISCSI_NET_PARAM, + iface->state, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_NET_PARAM_VLAN_ENABLED, + ISCSI_NET_PARAM, + iface->vlan_state, + &net_config->count, + &count); + + if (strcmp(iface->vlan_state, "disable") && iface->vlan_id) { + if (!iface_fill_vlan_id(&iov[net_config->count], iface, + iptype)) { + net_config->count++; + count++; + } + } + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_NET_PARAM_MTU, + ISCSI_NET_PARAM, + 2, + iface->mtu, + &net_config->count, + &count); + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_NET_PARAM_PORT, + ISCSI_NET_PARAM, + 2, + iface->port, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_NET_PARAM_DELAYED_ACK_EN, + ISCSI_NET_PARAM, + iface->delayed_ack, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_NET_PARAM_TCP_NAGLE_DISABLE, + ISCSI_NET_PARAM, + iface->nagle, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_NET_PARAM_TCP_WSF_DISABLE, + ISCSI_NET_PARAM, + iface->tcp_wsf_state, + &net_config->count, + &count); + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_NET_PARAM_TCP_WSF, + ISCSI_NET_PARAM, + 1, + iface->tcp_wsf, + &net_config->count, + &count); + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_NET_PARAM_TCP_TIMER_SCALE, + ISCSI_NET_PARAM, + 1, + iface->tcp_timer_scale, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_NET_PARAM_TCP_TIMESTAMP_EN, + ISCSI_NET_PARAM, + iface->tcp_timestamp, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_NET_PARAM_REDIRECT_EN, + ISCSI_NET_PARAM, + iface->redirect, + &net_config->count, + &count); + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_IFACE_PARAM_DEF_TASKMGMT_TMO, + ISCSI_IFACE_PARAM, + 2, + iface->def_task_mgmt_tmo, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_IFACE_PARAM_HDRDGST_EN, + ISCSI_IFACE_PARAM, + iface->header_digest, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_IFACE_PARAM_DATADGST_EN, + ISCSI_IFACE_PARAM, + iface->data_digest, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_IFACE_PARAM_IMM_DATA_EN, + ISCSI_IFACE_PARAM, + iface->immediate_data, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_IFACE_PARAM_INITIAL_R2T_EN, + ISCSI_IFACE_PARAM, + iface->initial_r2t, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_IFACE_PARAM_DATASEQ_INORDER_EN, + ISCSI_IFACE_PARAM, + iface->data_seq_inorder, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_IFACE_PARAM_PDU_INORDER_EN, + ISCSI_IFACE_PARAM, + iface->data_pdu_inorder, + &net_config->count, + &count); + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_IFACE_PARAM_ERL, + ISCSI_IFACE_PARAM, + 1, + iface->erl, + &net_config->count, + &count); + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_IFACE_PARAM_MAX_RECV_DLENGTH, + ISCSI_IFACE_PARAM, + 4, + iface->max_recv_dlength, + &net_config->count, + &count); + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_IFACE_PARAM_FIRST_BURST, + ISCSI_IFACE_PARAM, + 4, + iface->first_burst_len, + &net_config->count, + &count); + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_IFACE_PARAM_MAX_R2T, + ISCSI_IFACE_PARAM, + 2, + iface->max_out_r2t, + &net_config->count, + &count); + + IFACE_SET_PARAM_INTVAL(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_IFACE_PARAM_MAX_BURST, + ISCSI_IFACE_PARAM, + 4, + iface->max_burst_len, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_IFACE_PARAM_CHAP_AUTH_EN, + ISCSI_IFACE_PARAM, + iface->chap_auth, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_IFACE_PARAM_BIDI_CHAP_EN, + ISCSI_IFACE_PARAM, + iface->bidi_chap, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_IFACE_PARAM_STRICT_LOGIN_COMP_EN, + ISCSI_IFACE_PARAM, + iface->strict_login_comp, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_IFACE_PARAM_DISCOVERY_AUTH_OPTIONAL, + ISCSI_IFACE_PARAM, + iface->discovery_auth, + &net_config->count, + &count); + + IFACE_SET_PARAM_STATE(&iov[net_config->count], + iface->iface_num, + iptype, + ISCSI_IFACE_PARAM_DISCOVERY_LOGOUT_EN, + ISCSI_IFACE_PARAM, + iface->discovery_logout, + &net_config->count, + &count); + } + return 0; +} + +/** + * iface_build_net_config - Setup neconfig parameter buffers + * @iface: iface to setup + * @iface_all: Flag for number of ifaces to traverse (1 for all) + * @iovs: iovec buffer for netconfig parameters + * + * Returns total number of netconfig parameter buffers used. + */ +int iface_build_net_config(struct iface_rec *iface, int iface_all, + struct iovec *iovs) +{ + int num_found = 0, rc; + struct iface_net_config net_config; + + log_debug(8, "In iface_build_net_config"); + + net_config.primary = iface; + net_config.iovs = iovs; + net_config.count = 0; + + if (iface_all) + rc = iface_for_each_iface(&net_config, 0, &num_found, + __iface_build_net_config); + else + rc = __iface_build_net_config(&net_config, iface); + + log_debug(8, "iface_build_net_config: rc = %d, count = %d", + rc, net_config.count); + return net_config.count; +} diff --git a/usr/iface.h b/usr/iface.h new file mode 100644 index 0000000..6c06f7f --- /dev/null +++ b/usr/iface.h @@ -0,0 +1,69 @@ +/* + * iSCSI iface helpers + * + * Copyright (C) 2008 Mike Christie + * Copyright (C) 2008 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#ifndef ISCSI_IFACE_H +#define ISCSI_IFACE_H + +#include + +#define IFACE_CONFIG_DIR ISCSI_CONFIG_ROOT"ifaces" + +struct iface_rec; +struct list_head; +struct boot_context; + +extern void iface_copy(struct iface_rec *dst, struct iface_rec *src); +extern int iface_match(struct iface_rec *pattern, struct iface_rec *iface); +extern struct iface_rec *iface_alloc(char *ifname, int *err); +extern int iface_conf_read(struct iface_rec *iface); +extern void iface_setup_defaults(struct iface_rec *iface); +extern int iface_is_bound_by_hwaddr(struct iface_rec *iface); +extern int iface_is_bound_by_netdev(struct iface_rec *iface); +extern int iface_is_bound_by_ipaddr(struct iface_rec *iface); +typedef int (iface_op_fn)(void *data, struct iface_rec *iface); +extern int iface_for_each_iface(void *data, int skip_def, int *nr_found, + iface_op_fn *fn); +extern void iface_print(struct iscsi_iface *iface, char *prefix); +extern void iface_print_flat(struct iscsi_iface *iface); +extern int iface_print_tree(void *data, struct iface_rec *iface); +extern void iface_setup_host_bindings(void); +extern int iface_get_by_net_binding(struct iface_rec *pattern, + struct iface_rec *out_rec); +extern int iface_conf_update(struct list_head *params, + struct iface_rec *iface); +extern int iface_conf_write(struct iface_rec *iface); +extern int iface_conf_delete(struct iface_rec *iface); +extern int iface_is_valid(struct iface_rec *iface); +extern void iface_link_ifaces(struct list_head *ifaces); +extern int iface_setup_from_boot_context(struct iface_rec *iface, + struct boot_context *context); +extern int iface_create_ifaces_from_boot_contexts(struct list_head *ifaces, + struct list_head *targets); +extern int iface_get_param_count(struct iface_rec *iface_primary, + int iface_all); +extern int iface_build_net_config(struct iface_rec *iface_primary, + int iface_all, struct iovec *iovs); +extern int iface_get_iptype(struct iface_rec *iface); + +#define iface_fmt "[hw=%s,ip=%s,net_if=%s,iscsi_if=%s]" +#define iface_str(_iface) \ + (_iface)->hwaddress, (_iface)->ipaddress, (_iface)->netdev, \ + (_iface)->name + +#endif diff --git a/usr/initiator.c b/usr/initiator.c new file mode 100644 index 0000000..f4c9e02 --- /dev/null +++ b/usr/initiator.c @@ -0,0 +1,2198 @@ +/* + * iSCSI Session Management and Slow-path Control + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * Copyright (C) 2006 Mike Christie + * Copyright (C) 2006 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "initiator.h" +#include "transport.h" +#include "iscsid.h" +#include "iscsi_if.h" +#include "mgmt_ipc.h" +#include "event_poll.h" +#include "iscsi_ipc.h" +#include "idbm.h" +#include "log.h" +#include "iscsi_util.h" +#include "scsi.h" +#include "iscsi_sysfs.h" +#include "iscsi_settings.h" +#include "iface.h" +#include "host.h" +#include "sysdeps.h" +#include "iscsi_err.h" +#include "kern_err_table.h" + +#define ISCSI_CONN_ERR_REOPEN_DELAY 3 +#define ISCSI_INTERNAL_ERR_REOPEN_DELAY 5 + +#define PROC_DIR "/proc" + +struct login_task_retry_info { + actor_t retry_actor; + queue_task_t *qtask; + node_rec_t *rec; + int retry_count; +}; + +static void iscsi_login_timedout(void *data); +static int iscsi_sched_ev_context(struct iscsi_ev_context *ev_context, + struct iscsi_conn *conn, unsigned long tmo, + int event); +static int queue_session_login_task_retry(struct login_task_retry_info *info, + node_rec_t *rec, queue_task_t *qtask); + +static int iscsi_ev_context_alloc(iscsi_conn_t *conn) +{ + int i; + + for (i = 0; i < CONTEXT_POOL_MAX; i++) { + conn->context_pool[i] = calloc(1, + sizeof(struct iscsi_ev_context) + + ipc->ctldev_bufmax); + if (!conn->context_pool[i]) { + int j; + for (j = 0; j < i; j++) + free(conn->context_pool[j]); + return ENOMEM; + } + conn->context_pool[i]->conn = conn; + } + + return 0; +} + +static void iscsi_ev_context_free(iscsi_conn_t *conn) +{ + int i; + + for (i = 0; i < CONTEXT_POOL_MAX; i++) { + if (!conn->context_pool[i]) + continue; + + if (conn->context_pool[i]->allocated) + /* missing flush on shutdown */ + log_error("BUG: context_pool leak %p", + conn->context_pool[i]); + free(conn->context_pool[i]); + } +} + +static struct iscsi_ev_context * +iscsi_ev_context_get(iscsi_conn_t *conn, int ev_size) +{ + struct iscsi_ev_context *ev_context; + int i; + + if (ev_size > ipc->ctldev_bufmax) + return NULL; + + for (i = 0; i < CONTEXT_POOL_MAX; i++) { + if (!conn->context_pool[i]) + continue; + + if (!conn->context_pool[i]->allocated) { + ev_context = conn->context_pool[i]; + + memset(&ev_context->actor, 0, + sizeof(struct actor)); + ev_context->allocated = 1; + /* some callers abuse this pointer */ + ev_context->data = (void *)ev_context + + sizeof(struct iscsi_ev_context); + log_debug(7, "get ev context %p", + &ev_context->actor); + return ev_context; + } + } + return NULL; +} + +static void iscsi_ev_context_put(struct iscsi_ev_context *ev_context) +{ + log_debug(7, "put ev context %p", &ev_context->actor); + ev_context->allocated = 0; +} + +static void session_online_devs(int host_no, int sid) +{ + iscsi_sysfs_for_each_device(NULL, host_no, sid, + iscsi_sysfs_set_device_online); +} + +static conn_login_status_e +__login_response_status(iscsi_conn_t *conn, + enum iscsi_login_status login_status) +{ + switch (login_status) { + case LOGIN_OK: + /* check the status class and detail */ + return CONN_LOGIN_SUCCESS; + case LOGIN_REDIRECT: + return CONN_LOGIN_IMM_REDIRECT_RETRY; + case LOGIN_IO_ERROR: + case LOGIN_REDIRECTION_FAILED: + return CONN_LOGIN_RETRY; + default: + log_error("Login error (Login status %d) on conn %d", conn->id, + login_status); + break; + } + + return CONN_LOGIN_FAILED; +} + +static conn_login_status_e +__check_iscsi_status_class(iscsi_session_t *session, int cid, + uint8_t status_class, uint8_t status_detail) +{ + iscsi_conn_t *conn = &session->conn[cid]; + + switch (status_class) { + case ISCSI_STATUS_CLS_SUCCESS: + return CONN_LOGIN_SUCCESS; + case ISCSI_STATUS_CLS_REDIRECT: + switch (status_detail) { + case ISCSI_LOGIN_STATUS_TGT_MOVED_TEMP: + return CONN_LOGIN_IMM_RETRY; + case ISCSI_LOGIN_STATUS_TGT_MOVED_PERM: + /* + * for a permanent redirect, we need to update the + * failback address + */ + memset(&conn->failback_saddr, 0, + sizeof(struct sockaddr_storage)); + conn->failback_saddr = conn->saddr; + return CONN_LOGIN_IMM_REDIRECT_RETRY; + default: + log_error("conn %d login rejected: redirection " + "type 0x%x not supported", + conn->id, status_detail); + return CONN_LOGIN_RETRY; + } + case ISCSI_STATUS_CLS_INITIATOR_ERR: + switch (status_detail) { + case ISCSI_LOGIN_STATUS_AUTH_FAILED: + log_error("session %d login rejected: Initiator " + "failed authentication with target", + session->id); + return CONN_LOGIN_AUTH_FAILED; + case ISCSI_LOGIN_STATUS_TGT_FORBIDDEN: + log_error("conn %d login rejected: initiator " + "failed authorization with target", conn->id); + return CONN_LOGIN_AUTH_FAILED; + case ISCSI_LOGIN_STATUS_TGT_NOT_FOUND: + log_error("conn %d login rejected: initiator " + "error - target not found (%02x/%02x)", + conn->id, status_class, status_detail); + return CONN_LOGIN_FAILED; + case ISCSI_LOGIN_STATUS_NO_VERSION: + /* + * FIXME: if we handle multiple protocol versions, + * before we log an error, try the other supported + * versions. + */ + log_error("conn %d login rejected: incompatible " + "version (%02x/%02x), non-retryable, " + "giving up", conn->id, status_class, + status_detail); + return CONN_LOGIN_FAILED; + default: + log_error("conn %d login rejected: initiator " + "error (%02x/%02x)", conn->id, status_class, + status_detail); + return CONN_LOGIN_FAILED; + } + case ISCSI_STATUS_CLS_TARGET_ERR: + log_error("conn %d login rejected: target error " + "(%02x/%02x)", conn->id, status_class, status_detail); + /* + * We have no idea what the problem is. But spec says initiator + * may retry later. + */ + return CONN_LOGIN_RETRY; + default: + log_error("conn %d login response with unknown status " + "class 0x%x, detail 0x%x", conn->id, status_class, + status_detail); + break; + } + + return CONN_LOGIN_FAILED; +} + +static int +__session_conn_create(iscsi_session_t *session, int cid) +{ + iscsi_conn_t *conn = &session->conn[cid]; + conn_rec_t *conn_rec = &session->nrec.conn[cid]; + node_rec_t *rec = &session->nrec; + int err; + + if (iscsi_ev_context_alloc(conn)) { + log_error("cannot allocate context_pool for conn cid %d", cid); + return ISCSI_ERR_NOMEM; + } + + /* set session reconnection retry max */ + session->reopen_max = rec->session.reopen_max; + + conn->state = ISCSI_CONN_STATE_FREE; + conn->session = session; + actor_init(&conn->login_timer, iscsi_login_timedout, NULL); + /* + * TODO: we must export the socket_fd/transport_eph from sysfs + * so if iscsid is resyncing up we can pick that up and cleanup up + * the old connection. Right now we leak a connection. + * We can also probably merge these two fields. + */ + conn->socket_fd = -1; + conn->transport_ep_handle = -1; + /* connection's timeouts */ + conn->id = cid; + conn->logout_timeout = conn_rec->timeo.logout_timeout; + if (!conn->logout_timeout) { + log_error("Invalid timeo.logout_timeout. Must be greater " + "than zero. Using default %d.", + DEF_LOGOUT_TIMEO); + conn->logout_timeout = DEF_LOGOUT_TIMEO; + } + + conn->login_timeout = conn_rec->timeo.login_timeout; + if (!conn->login_timeout) { + log_error("Invalid timeo.login_timeout. Must be greater " + "than zero. Using default %d.", + DEF_LOGIN_TIMEO); + conn->login_timeout = DEF_LOGIN_TIMEO; + } + + conn->auth_timeout = conn_rec->timeo.auth_timeout; + + /* noop-out setting */ + conn->noop_out_interval = conn_rec->timeo.noop_out_interval; + conn->noop_out_timeout = conn_rec->timeo.noop_out_timeout; + if (conn->noop_out_interval && !conn->noop_out_timeout) { + log_error("Invalid timeo.noop_out_timeout. Must be greater " + "than zero. Using default %d.", + DEF_NOOP_OUT_TIMEO); + conn->noop_out_timeout = DEF_NOOP_OUT_TIMEO; + } + + if (conn->noop_out_timeout && !conn->noop_out_interval) { + log_error("Invalid timeo.noop_out_interval. Must be greater " + "than zero. Using default %d.", + DEF_NOOP_OUT_INTERVAL); + conn->noop_out_interval = DEF_NOOP_OUT_INTERVAL; + } + + iscsi_copy_operational_params(conn, &session->nrec.session.iscsi, + &conn_rec->iscsi); + + /* TCP options */ + conn->tcp_window_size = conn_rec->tcp.window_size; + /* FIXME: type_of_service */ + + /* resolve the string address to an IP address */ + err = iscsi_setup_portal(conn, conn_rec->address, conn_rec->port); + if (err) + return err; + return 0; +} + +static void +session_release(iscsi_session_t *session) +{ + log_debug(2, "Releasing session %p", session); + + if (session->target_alias) + free(session->target_alias); + iscsi_ev_context_free(&session->conn[0]); + free(session); +} + +static iscsi_session_t* +__session_create(node_rec_t *rec, struct iscsi_transport *t, int *rc) +{ + iscsi_session_t *session; + int hostno; + + *rc = 0; + + session = calloc(1, sizeof (*session)); + if (session == NULL) { + log_debug(1, "can not allocate memory for session"); + *rc = ISCSI_ERR_NOMEM; + return NULL; + } + log_debug(2, "Allocted session %p", session); + + INIT_LIST_HEAD(&session->list); + session->t = t; + session->reopen_qtask.mgmt_ipc_fd = -1; + session->id = -1; + session->use_ipc = 1; + + /* save node record. we might need it for redirection */ + memcpy(&session->nrec, rec, sizeof(node_rec_t)); + + session->portal_group_tag = rec->tpgt; + session->type = ISCSI_SESSION_TYPE_NORMAL; + session->r_stage = R_STAGE_NO_CHANGE; + strlcpy(session->target_name, rec->name, TARGET_NAME_MAXLEN); + + if (strlen(session->nrec.iface.iname)) + session->initiator_name = session->nrec.iface.iname; + else if (dconfig->initiator_name) + session->initiator_name = dconfig->initiator_name; + else { + log_error("No initiator name set. Cannot create session."); + *rc = ISCSI_ERR_INVAL; + goto free_session; + } + + if (strlen(session->nrec.iface.alias)) + session->initiator_alias = session->nrec.iface.alias; + else + session->initiator_alias = dconfig->initiator_alias; + + /* session's eh parameters */ + session->replacement_timeout = rec->session.timeo.replacement_timeout; + session->fast_abort = rec->session.iscsi.FastAbort; + session->abort_timeout = rec->session.err_timeo.abort_timeout; + session->lu_reset_timeout = rec->session.err_timeo.lu_reset_timeout; + session->tgt_reset_timeout = rec->session.err_timeo.tgt_reset_timeout; + session->host_reset_timeout = rec->session.err_timeo.host_reset_timeout; + + /* OUI and uniqifying number */ + session->isid[0] = DRIVER_ISID_0; + session->isid[1] = DRIVER_ISID_1; + session->isid[2] = DRIVER_ISID_2; + session->isid[3] = 0; + session->isid[4] = 0; + session->isid[5] = 0; + + /* setup authentication variables for the session*/ + iscsi_setup_authentication(session, &rec->session.auth); + + iscsi_session_init_params(session); + + if (t->template->bind_ep_required) { + hostno = iscsi_sysfs_get_host_no_from_hwinfo(&rec->iface, rc); + if (!*rc) { + /* + * if the netdev or mac was set, then we are going to want + * to want to bind the all the conns/eps to a specific host + * if offload is used. + */ + session->conn[0].bind_ep = 1; + session->hostno = hostno; + } else if (*rc == ISCSI_ERR_HOST_NOT_FOUND) { + goto free_session; + } else { + *rc = 0; + } + } + + /* reset session reopen count */ + session->reopen_cnt = 0; + + list_add_tail(&session->list, &t->sessions); + return session; + +free_session: + free(session); + return NULL; +} + +static void iscsi_flush_context_pool(struct iscsi_session *session) +{ + struct iscsi_ev_context *ev_context; + struct iscsi_conn *conn = &session->conn[0]; + int i; + + for (i = 0; i < CONTEXT_POOL_MAX; i++) { + ev_context = conn->context_pool[i]; + if (!ev_context) + continue; + + if (ev_context->allocated) { + actor_delete(&(conn->context_pool[i]->actor)); + iscsi_ev_context_put(ev_context); + } + } +} + +static void +__session_destroy(iscsi_session_t *session) +{ + log_debug(1, "destroying session"); + list_del(&session->list); + iscsi_flush_context_pool(session); + session_release(session); +} + +static void +conn_delete_timers(iscsi_conn_t *conn) +{ + actor_delete(&conn->login_timer); + actor_delete(&conn->nop_out_timer); +} + +static int +session_conn_shutdown(iscsi_conn_t *conn, queue_task_t *qtask, + int err) +{ + iscsi_session_t *session = conn->session; + + log_debug(2, "disconnect conn"); + /* this will check for a valid interconnect connection */ + if (session->t->template->ep_disconnect) + session->t->template->ep_disconnect(conn); + + if (session->id == -1) + goto cleanup; + + if (!iscsi_sysfs_session_has_leadconn(session->id)) + goto cleanup; + + if (conn->state == ISCSI_CONN_STATE_IN_LOGIN || + conn->state == ISCSI_CONN_STATE_IN_LOGOUT || + conn->state == ISCSI_CONN_STATE_LOGGED_IN) { + log_debug(2, "stop conn (conn state %d)", conn->state); + if (ipc->stop_conn(session->t->handle, session->id, + conn->id, STOP_CONN_TERM)) { + log_error("can't stop connection %d:%d (%d)", + session->id, conn->id, errno); + return ISCSI_ERR_INTERNAL; + } + } + + log_debug(2, "kdestroy conn"); + if (ipc->destroy_conn(session->t->handle, session->id, + conn->id)) { + log_error("can not safely destroy connection %d", conn->id); + return ISCSI_ERR_INTERNAL; + } + +cleanup: + if (session->id != -1) { + log_debug(2, "kdestroy session %u", session->id); + session->r_stage = R_STAGE_SESSION_DESTOYED; + if (ipc->destroy_session(session->t->handle, session->id)) { + log_error("can not safely destroy session %d", + session->id); + return ISCSI_ERR_INTERNAL; + } + } + + log_warning("Connection%d:%d to [target: %s, portal: %s,%d] " + "through [iface: %s] is shutdown.", + session->id, conn->id, session->nrec.name, + session->nrec.conn[conn->id].address, + session->nrec.conn[conn->id].port, + session->nrec.iface.name); + + mgmt_ipc_write_rsp(qtask, err); + conn_delete_timers(conn); + __session_destroy(session); + return ISCSI_SUCCESS; +} + +static void +queue_delayed_reopen(queue_task_t *qtask, int delay) +{ + iscsi_conn_t *conn = qtask->conn; + + log_debug(4, "Requeue reopen attempt in %d secs", delay); + + /* + * iscsi_login_eh can handle the login resched as + * if it were login time out + */ + actor_timer_mod(&conn->login_timer, delay, qtask); +} + +static int iscsi_conn_connect(struct iscsi_conn *conn, queue_task_t *qtask) +{ + struct iscsi_ev_context *ev_context; + int rc; + + ev_context = iscsi_ev_context_get(conn, 0); + if (!ev_context) { + /* while reopening the recv pool should be full */ + log_error("BUG: __session_conn_reopen could not get conn " + "context for recv."); + return ENOMEM; + } + ev_context->data = qtask; + + rc = conn->session->t->template->ep_connect(conn, 1); + if (rc < 0 && errno != EINPROGRESS) { + char serv[NI_MAXSERV]; + + getnameinfo((struct sockaddr *) &conn->saddr, + sizeof(conn->saddr), + conn->host, sizeof(conn->host), serv, sizeof(serv), + NI_NUMERICHOST|NI_NUMERICSERV); + + log_error("cannot make a connection to %s:%s (%d,%d)", + conn->host, serv, rc, errno); + iscsi_ev_context_put(ev_context); + return ENOTCONN; + } + + iscsi_sched_ev_context(ev_context, conn, 0, EV_CONN_POLL); + log_debug(3, "Setting login timer %p timeout %d", &conn->login_timer, + conn->login_timeout); + actor_timer_mod(&conn->login_timer, conn->login_timeout, qtask); + return 0; +} + +static void +__session_conn_reopen(iscsi_conn_t *conn, queue_task_t *qtask, int do_stop, + int redirected) +{ + iscsi_session_t *session = conn->session; + uint32_t delay; + + log_debug(1, "re-opening session %d (reopen_cnt %d)", session->id, + session->reopen_cnt); + + qtask->conn = conn; + + /* flush stale polls or errors queued */ + iscsi_flush_context_pool(session); + conn_delete_timers(conn); + conn->state = ISCSI_CONN_STATE_XPT_WAIT; + + conn->session->t->template->ep_disconnect(conn); + if (do_stop) { + /* state: ISCSI_CONN_STATE_CLEANUP_WAIT */ + if (ipc->stop_conn(session->t->handle, session->id, + conn->id, do_stop)) { + log_error("can't stop connection %d:%d (%d)", + session->id, conn->id, errno); + delay = ISCSI_INTERNAL_ERR_REOPEN_DELAY; + goto queue_reopen; + } + log_debug(3, "connection %d:%d is stopped for recovery", + session->id, conn->id); + } + + if (!redirected) { + delay = session->def_time2wait; + session->def_time2wait = 0; + if (delay) + goto queue_reopen; + } + + if (!redirected) + session->reopen_cnt++; + + /* uIP will needs to be re-triggered on the connection re-open */ + if (iscsi_set_net_config(conn->session->t, conn->session, + &conn->session->nrec.iface) != 0) + goto queue_reopen; + + if (iscsi_conn_connect(conn, qtask)) { + delay = ISCSI_CONN_ERR_REOPEN_DELAY; + goto queue_reopen; + } + return; + +queue_reopen: + log_debug(4, "Waiting %u seconds before trying to reconnect.", delay); + queue_delayed_reopen(qtask, delay); +} + +static void +session_conn_reopen(iscsi_conn_t *conn, queue_task_t *qtask, int do_stop) +{ + /* + * If we were temporarily redirected, we need to fall back to + * the original address to see where the target will send us + * for the retry + */ + memset(&conn->saddr, 0, sizeof(struct sockaddr_storage)); + conn->saddr = conn->failback_saddr; + + __session_conn_reopen(conn, qtask, do_stop, 0); +} + +static int iscsi_retry_initial_login(struct iscsi_conn *conn) +{ + int initial_login_retry_max; + struct timeval now, timeout, fail_time; + + initial_login_retry_max = + conn->session->nrec.session.initial_login_retry_max; + + memset(&now, 0, sizeof(now)); + memset(&timeout, 0, sizeof(timeout)); + memset(&fail_time, 0, sizeof(fail_time)); + + timeout.tv_sec = initial_login_retry_max * conn->login_timeout; + if (gettimeofday(&now, NULL)) { + log_error("Could not get time of day. Dropping down to " + "max retry check."); + return initial_login_retry_max > conn->session->reopen_cnt; + } + timeradd(&conn->initial_connect_time, &timeout, &fail_time); + + /* + * if we have been trying for login_retry_max * login_timeout + * then it is time to give up + */ + if (timercmp(&now, &fail_time, >)) { + log_debug(1, "Giving up on initial login attempt after " + "%u seconds.", + initial_login_retry_max * conn->login_timeout); + return 0; + } + + return 1; +} + +static int iscsi_login_is_fatal_err(int err) +{ + if (err == ISCSI_ERR_LOGIN_AUTH_FAILED || + err == ISCSI_ERR_FATAL_LOGIN) + return 1; + return 0; +} + +static void iscsi_login_eh(struct iscsi_conn *conn, struct queue_task *qtask, + int err) +{ + struct iscsi_session *session = conn->session; + + log_debug(3, "iscsi_login_eh"); + /* + * Flush polls and other events + */ + iscsi_flush_context_pool(conn->session); + + switch (conn->state) { + case ISCSI_CONN_STATE_XPT_WAIT: + switch (session->r_stage) { + case R_STAGE_NO_CHANGE: + log_debug(6, "login failed ISCSI_CONN_STATE_XPT_WAIT/" + "R_STAGE_NO_CHANGE"); + /* timeout during initial connect. + * clean connection. write ipc rsp or retry */ + if (iscsi_login_is_fatal_err(err) || + !iscsi_retry_initial_login(conn)) + session_conn_shutdown(conn, qtask, err); + else { + session->reopen_cnt++; + session->t->template->ep_disconnect(conn); + if (iscsi_conn_connect(conn, qtask)) + queue_delayed_reopen(qtask, + ISCSI_CONN_ERR_REOPEN_DELAY); + } + break; + case R_STAGE_SESSION_REDIRECT: + log_debug(6, "login failed ISCSI_CONN_STATE_XPT_WAIT/" + "R_STAGE_SESSION_REDIRECT"); + /* timeout during initial redirect connect + * clean connection. write ipc rsp or retry */ + if (iscsi_login_is_fatal_err(err) || + !iscsi_retry_initial_login(conn)) + session_conn_shutdown(conn, qtask, err); + else + session_conn_reopen(conn, qtask, 0); + break; + case R_STAGE_SESSION_REOPEN: + log_debug(6, "login failed ISCSI_CONN_STATE_XPT_WAIT/" + "R_STAGE_SESSION_REOPEN (reopen_cnt=%d, reopen_max=%d)", + session->reopen_cnt, session->reopen_max); + if (session->reopen_max && + (session->reopen_cnt > session->reopen_max)) { + log_info("Giving up on session %d after %d retries", + session->id, session->reopen_max); + session_conn_shutdown(conn, qtask, err); + break; + } + /* timeout during reopen connect. try again */ + session_conn_reopen(conn, qtask, 0); + break; + case R_STAGE_SESSION_CLEANUP: + session_conn_shutdown(conn, qtask, err); + break; + default: + break; + } + + break; + case ISCSI_CONN_STATE_IN_LOGIN: + switch (session->r_stage) { + case R_STAGE_NO_CHANGE: + case R_STAGE_SESSION_REDIRECT: + log_debug(6, "login failed ISCSI_CONN_STATE_IN_LOGIN/" + "R_STAGE_NO_CHANGE %d", + session->reopen_cnt); + /* + * send pdu timeout during initial connect or + * initial redirected connect. Clean connection + * and write rsp or retry. + */ + if (iscsi_login_is_fatal_err(err) || + !iscsi_retry_initial_login(conn)) + session_conn_shutdown(conn, qtask, err); + else + session_conn_reopen(conn, qtask, + STOP_CONN_RECOVER); + break; + case R_STAGE_SESSION_REOPEN: + log_debug(6, "login failed ISCSI_CONN_STATE_IN_LOGIN/" + "R_STAGE_SESSION_REOPEN %d", + session->reopen_cnt); + session_conn_reopen(conn, qtask, STOP_CONN_RECOVER); + break; + case R_STAGE_SESSION_CLEANUP: + session_conn_shutdown(conn, qtask, + ISCSI_ERR_PDU_TIMEOUT); + break; + default: + break; + } + + break; + default: + log_error("Ignoring login error %d in conn state %d.", + err, conn->state); + break; + } +} + +static void +__conn_error_handle(iscsi_session_t *session, iscsi_conn_t *conn) +{ + int i; + + /* + * if we got an error while trying to logout for the user then + * just cleanup and return to the user. + */ + if (conn->logout_qtask) { + session_conn_shutdown(conn, conn->logout_qtask, ISCSI_SUCCESS); + return; + } + + switch (conn->state) { + case ISCSI_CONN_STATE_IN_LOGOUT: + /* logout was from eh - fall down to cleanup */ + case ISCSI_CONN_STATE_LOGGED_IN: + /* mark failed connection */ + conn->state = ISCSI_CONN_STATE_CLEANUP_WAIT; + + if (session->erl > 0) { + /* check if we still have some logged in connections */ + for (i=0; iconn[i].state == + ISCSI_CONN_STATE_LOGGED_IN) + break; + } + if (i != ISCSI_CONN_MAX) { + /* FIXME: re-assign leading connection + * for ERL>0 */ + } + + break; + } + + /* mark all connections as failed */ + for (i=0; iconn[i].state == + ISCSI_CONN_STATE_LOGGED_IN) + session->conn[i].state = + ISCSI_CONN_STATE_CLEANUP_WAIT; + } + session->r_stage = R_STAGE_SESSION_REOPEN; + break; + case ISCSI_CONN_STATE_IN_LOGIN: + if (session->r_stage == R_STAGE_SESSION_REOPEN) { + queue_task_t *qtask; + + if (session->notify_qtask) + qtask = session->notify_qtask; + else + qtask = &session->reopen_qtask; + iscsi_login_eh(conn, qtask, ISCSI_ERR_TRANS); + return; + } + log_debug(1, "ignoring conn error in login. " + "let it timeout"); + return; + case ISCSI_CONN_STATE_XPT_WAIT: + log_debug(1, "ignoring conn error in XPT_WAIT. " + "let connection fail on its own"); + return; + case ISCSI_CONN_STATE_CLEANUP_WAIT: + log_debug(1, "ignoring conn error in CLEANUP_WAIT. " + "let connection stop"); + return; + default: + log_debug(8, "invalid state %d", conn->state); + return; + } + + if (session->r_stage == R_STAGE_SESSION_REOPEN) { + session_conn_reopen(conn, &session->reopen_qtask, + STOP_CONN_RECOVER); + return; + } +} + +static void session_conn_error(void *data) +{ + struct iscsi_ev_context *ev_context = data; + enum iscsi_err error = *(enum iscsi_err *)ev_context->data; + iscsi_conn_t *conn = ev_context->conn; + iscsi_session_t *session = conn->session; + + log_warning("Kernel reported iSCSI connection %d:%d error (%d - %s) " + "state (%d)", session->id, conn->id, error, + kern_err_code_to_string(error), conn->state); + + iscsi_ev_context_put(ev_context); + + switch (error) { + case ISCSI_ERR_INVALID_HOST: + if (session_conn_shutdown(conn, NULL, ISCSI_SUCCESS)) + log_error("BUG: Could not shutdown session."); + break; + default: + __conn_error_handle(session, conn); + } +} + +static void iscsi_login_timedout(void *data) +{ + struct queue_task *qtask = data; + struct iscsi_conn *conn = qtask->conn; + + switch (conn->state) { + case ISCSI_CONN_STATE_XPT_WAIT: + iscsi_login_eh(conn, qtask, ISCSI_ERR_TRANS_TIMEOUT); + break; + case ISCSI_CONN_STATE_IN_LOGIN: + iscsi_login_eh(conn, qtask, ISCSI_ERR_PDU_TIMEOUT); + break; + default: + iscsi_login_eh(conn, qtask, ISCSI_ERR_INTERNAL); + break; + } +} + +static void iscsi_login_redirect(iscsi_conn_t *conn) +{ + iscsi_session_t *session = conn->session; + iscsi_login_context_t *c = &conn->login_context; + + log_debug(3, "login redirect ..."); + + if (session->r_stage == R_STAGE_NO_CHANGE) + session->r_stage = R_STAGE_SESSION_REDIRECT; + + __session_conn_reopen(conn, c->qtask, STOP_CONN_RECOVER, 1); +} + +static int +__send_nopin_rsp(iscsi_conn_t *conn, struct iscsi_nopin *rhdr, char *data) +{ + struct iscsi_nopout hdr; + + memset(&hdr, 0, sizeof(struct iscsi_nopout)); + hdr.opcode = ISCSI_OP_NOOP_OUT | ISCSI_OP_IMMEDIATE; + hdr.flags = ISCSI_FLAG_CMD_FINAL; + hdr.dlength[0] = rhdr->dlength[0]; + hdr.dlength[1] = rhdr->dlength[1]; + hdr.dlength[2] = rhdr->dlength[2]; + memcpy(hdr.lun, rhdr->lun, 8); + hdr.ttt = rhdr->ttt; + hdr.itt = ISCSI_RESERVED_TAG; + + return iscsi_io_send_pdu(conn, (struct iscsi_hdr*)&hdr, + ISCSI_DIGEST_NONE, data, ISCSI_DIGEST_NONE, 0); +} + +static int +__send_nopout(iscsi_conn_t *conn) +{ + struct iscsi_nopout hdr; + + memset(&hdr, 0, sizeof(struct iscsi_nopout)); + hdr.opcode = ISCSI_OP_NOOP_OUT | ISCSI_OP_IMMEDIATE; + hdr.flags = ISCSI_FLAG_CMD_FINAL; + hdr.itt = 0; /* XXX: let kernel send_pdu set for us*/ + hdr.ttt = ISCSI_RESERVED_TAG; + /* we have hdr.lun reserved, and no data */ + return iscsi_io_send_pdu(conn, (struct iscsi_hdr*)&hdr, + ISCSI_DIGEST_NONE, NULL, ISCSI_DIGEST_NONE, 0); +} + +static void conn_nop_out_timeout(void *data) +{ + iscsi_conn_t *conn = (iscsi_conn_t*)data; + iscsi_session_t *session = conn->session; + + log_warning("Nop-out timedout after %d seconds on connection %d:%d " + "state (%d). Dropping session.", conn->noop_out_timeout, + session->id, conn->id, conn->state); + /* XXX: error handle */ + __conn_error_handle(session, conn); +} + +static void conn_send_nop_out(void *data) +{ + iscsi_conn_t *conn = data; + + /* + * we cannot start new request during logout and the logout timer + * will figure things out. + */ + if (conn->state == ISCSI_CONN_STATE_IN_LOGOUT) + return; + + __send_nopout(conn); + + actor_timer(&conn->nop_out_timer, conn->noop_out_timeout, + conn_nop_out_timeout, conn); + log_debug(3, "noop out timeout timer %p start, timeout %d", + &conn->nop_out_timer, conn->noop_out_timeout); +} + +void free_initiator(void) +{ + struct iscsi_transport *t; + iscsi_session_t *session, *tmp; + + list_for_each_entry(t, &transports, list) { + list_for_each_entry_safe(session, tmp, &t->sessions, list) { + list_del(&session->list); + iscsi_flush_context_pool(session); + session_release(session); + } + } + + free_transports(); +} + +static void session_scan_host(struct iscsi_session *session, int hostno, + queue_task_t *qtask) +{ + pid_t pid; + + pid = iscsi_sysfs_scan_host(hostno, 1, idbm_session_autoscan(session)); + if (pid == 0) { + mgmt_ipc_write_rsp(qtask, ISCSI_SUCCESS); + + if (session) + iscsi_sysfs_for_each_device( + &session->nrec.session.queue_depth, + hostno, session->id, + iscsi_sysfs_set_queue_depth); + exit(0); + } else if (pid > 0) { + reap_inc(); + if (qtask && qtask->mgmt_ipc_fd >= 0) { + close(qtask->mgmt_ipc_fd); + free(qtask); + } + } else + mgmt_ipc_write_rsp(qtask, ISCSI_ERR_INTERNAL); +} + +static void +setup_full_feature_phase(iscsi_conn_t *conn) +{ + iscsi_session_t *session = conn->session; + iscsi_login_context_t *c = &conn->login_context; + int rc; + + actor_delete(&conn->login_timer); + + if (iscsi_session_set_neg_params(conn)) { + iscsi_login_eh(conn, c->qtask, ISCSI_ERR_LOGIN); + return; + } + + if (ipc->start_conn(session->t->handle, session->id, conn->id, + &rc) || rc) { + log_error("can't start connection %d:%d retcode %d (%d)", + session->id, conn->id, rc, errno); + iscsi_login_eh(conn, c->qtask, ISCSI_ERR_INTERNAL); + return; + } + + conn->state = ISCSI_CONN_STATE_LOGGED_IN; + if (session->r_stage == R_STAGE_NO_CHANGE || + session->r_stage == R_STAGE_SESSION_REDIRECT) { + /* + * scan host is one-time deal. We + * don't want to re-scan it on recovery. + */ + if (conn->id == 0) + session_scan_host(session, session->hostno, c->qtask); + + log_warning("Connection%d:%d to [target: %s, portal: %s,%d] " + "through [iface: %s] is operational now", + session->id, conn->id, session->nrec.name, + session->nrec.conn[conn->id].address, + session->nrec.conn[conn->id].port, + session->nrec.iface.name); + } else { + session->notify_qtask = NULL; + + session_online_devs(session->hostno, session->id); + mgmt_ipc_write_rsp(c->qtask, ISCSI_SUCCESS); + log_warning("connection%d:%d is operational after recovery " + "(%d attempts)", session->id, conn->id, + session->reopen_cnt); + } + + /* + * reset ERL=0 reopen counter + */ + session->reopen_cnt = 0; + session->r_stage = R_STAGE_NO_CHANGE; + + /* noop_out */ + if (conn->userspace_nop && conn->noop_out_interval) { + actor_timer(&conn->nop_out_timer, conn->noop_out_interval, + conn_send_nop_out, conn); + log_debug(3, "noop out timer %p start", + &conn->nop_out_timer); + } +} + +static void iscsi_logout_timedout(void *data) +{ + struct iscsi_ev_context *ev_context = data; + struct iscsi_conn *conn = ev_context->conn; + + iscsi_ev_context_put(ev_context); + /* + * assume we were in ISCSI_CONN_STATE_IN_LOGOUT or there + * was some nasty error + */ + log_debug(3, "logout timeout, dropping conn..."); + __conn_error_handle(conn->session, conn); +} + +static int iscsi_send_logout(iscsi_conn_t *conn) +{ + struct iscsi_logout hdr; + struct iscsi_ev_context *ev_context; + + if (conn->state != ISCSI_CONN_STATE_LOGGED_IN) + return EINVAL; + + memset(&hdr, 0, sizeof(struct iscsi_logout)); + hdr.opcode = ISCSI_OP_LOGOUT | ISCSI_OP_IMMEDIATE; + hdr.flags = ISCSI_FLAG_CMD_FINAL | + (ISCSI_LOGOUT_REASON_CLOSE_SESSION & ISCSI_FLAG_LOGOUT_REASON_MASK); + /* kernel will set the rest */ + + if (!iscsi_io_send_pdu(conn, (struct iscsi_hdr*)&hdr, + ISCSI_DIGEST_NONE, NULL, ISCSI_DIGEST_NONE, 0)) + return EIO; + conn->state = ISCSI_CONN_STATE_IN_LOGOUT; + + ev_context = iscsi_ev_context_get(conn, 0); + if (!ev_context) + /* unbounded logout */ + log_warning("Could not allocate conn context for logout."); + else { + iscsi_sched_ev_context(ev_context, conn, + conn->logout_timeout, + EV_CONN_LOGOUT_TIMER); + log_debug(3, "logout timeout timer %u", + conn->logout_timeout * 1000); + } + + return 0; +} + +static void iscsi_stop(void *data) +{ + struct iscsi_ev_context *ev_context = data; + struct iscsi_conn *conn = ev_context->conn; + int rc = 0; + + iscsi_ev_context_put(ev_context); + + if (!(conn->session->t->caps & CAP_LOGIN_OFFLOAD)) { + if (!iscsi_send_logout(conn)) + return; + } + + rc = session_conn_shutdown(conn, conn->logout_qtask, ISCSI_SUCCESS); + if (rc) + log_error("BUG: Could not shutdown session."); +} + +static void iscsi_recv_nop_in(iscsi_conn_t *conn, struct iscsi_hdr *hdr) +{ + if (!conn->userspace_nop) { + log_error("Got nop in, but kernel supports nop handling."); + return; + } + + if (hdr->ttt == ISCSI_RESERVED_TAG) { + /* noop out rsp */ + actor_delete(&conn->nop_out_timer); + /* schedule a new ping */ + actor_timer(&conn->nop_out_timer, conn->noop_out_interval, + conn_send_nop_out, conn); + } else /* noop in req */ + if (!__send_nopin_rsp(conn, (struct iscsi_nopin*)hdr, + conn->data)) { + log_error("can not send nopin response"); + } +} + +static void iscsi_recv_logout_rsp(iscsi_conn_t *conn, struct iscsi_hdr *hdr) +{ + struct iscsi_logout_rsp *logout_rsp = (struct iscsi_logout_rsp *)hdr; + + log_debug(3, "Recv: logout response %d", logout_rsp->response); + if (logout_rsp->response == 2 || logout_rsp->response == 3) { + conn->session->def_time2wait = ntohs(logout_rsp->t2wait); + log_debug(4, "logout rsp returned time2wait %u", + conn->session->def_time2wait); + } + /* TODO process the hdr */ + __conn_error_handle(conn->session, conn); +} + +static void iscsi_recv_async_msg(iscsi_conn_t *conn, struct iscsi_hdr *hdr) +{ + iscsi_session_t *session = conn->session; + struct iscsi_async *async_hdr = (struct iscsi_async *)hdr; + char *buf = conn->data; + unsigned int senselen; + struct scsi_sense_hdr sshdr; + + log_debug(3, "Read AEN %d", async_hdr->async_event); + + switch (async_hdr->async_event) { + case ISCSI_ASYNC_MSG_SCSI_EVENT: + senselen = (buf[0] << 8) | buf[1]; + buf += 2; + + if (!scsi_normalize_sense((uint8_t *)buf, senselen, &sshdr)) { + log_error("Could not handle AEN %d. Invalid sense.", + async_hdr->async_event); + break; + } + + if (sshdr.asc == 0x3f && sshdr.ascq == 0x0e + && idbm_session_autoscan(session)) + session_scan_host(session, session->hostno, NULL); + break; + case ISCSI_ASYNC_MSG_REQUEST_LOGOUT: + log_warning("Target requests logout within %u seconds for " + "connection", ntohs(async_hdr->param3)); + if (iscsi_send_logout(conn)) + log_error("Could not send logout in response to" + "logout request aen"); + break; + case ISCSI_ASYNC_MSG_DROPPING_CONNECTION: + log_warning("Target dropping connection %u, reconnect min %u " + "max %u", ntohs(async_hdr->param1), + ntohs(async_hdr->param2), ntohs(async_hdr->param3)); + session->def_time2wait = + (uint32_t)ntohs(async_hdr->param2) & 0x0000FFFFFL; + break; + case ISCSI_ASYNC_MSG_DROPPING_ALL_CONNECTIONS: + log_warning("Target dropping all connections, reconnect min %u " + "max %u", ntohs(async_hdr->param2), + ntohs(async_hdr->param3)); + session->def_time2wait = + (uint32_t)ntohs(async_hdr->param2) & 0x0000FFFFFL; + break; + case ISCSI_ASYNC_MSG_PARAM_NEGOTIATION: + log_warning("Received async event param negotiation, " + "dropping session"); + __conn_error_handle(session, conn); + break; + case ISCSI_ASYNC_MSG_VENDOR_SPECIFIC: + default: + log_warning("AEN not supported"); + } +} + +static void iscsi_recv_login_rsp(struct iscsi_conn *conn) +{ + struct iscsi_session *session = conn->session; + iscsi_login_context_t *c = &conn->login_context; + int err = ISCSI_ERR_FATAL_LOGIN; + + if (iscsi_login_rsp(session, c)) { + log_debug(1, "login_rsp ret (%d)", c->ret); + + switch (__login_response_status(conn, c->ret)) { + case CONN_LOGIN_FAILED: + goto failed; + case CONN_LOGIN_RETRY: + goto retry; + case CONN_LOGIN_IMM_REDIRECT_RETRY: + iscsi_login_redirect(conn); + return; + default: + ; /* success - fall through */ + } + + /* check the login status */ + switch (__check_iscsi_status_class(session, conn->id, + c->status_class, + c->status_detail)) { + case CONN_LOGIN_AUTH_FAILED: + err = ISCSI_ERR_LOGIN_AUTH_FAILED; + goto failed; + case CONN_LOGIN_FAILED: + goto failed; + case CONN_LOGIN_IMM_REDIRECT_RETRY: + iscsi_login_redirect(conn); + return; + case CONN_LOGIN_IMM_RETRY: + case CONN_LOGIN_RETRY: + goto retry; + default: + ; /* success - fall through */ + } + } + + if (conn->current_stage != ISCSI_FULL_FEATURE_PHASE) { + /* more nego. needed! */ + conn->state = ISCSI_CONN_STATE_IN_LOGIN; + if (iscsi_login_req(session, c)) { + iscsi_login_eh(conn, c->qtask, ISCSI_ERR_LOGIN); + return; + } + } else + setup_full_feature_phase(conn); + + return; +retry: + /* retry if not initial login or initial login has not timed out */ + iscsi_login_eh(conn, c->qtask, ISCSI_ERR_LOGIN); + return; +failed: + /* force failure if initial login */ + session->reopen_cnt = session->nrec.session.initial_login_retry_max; + iscsi_login_eh(conn, c->qtask, err); + return; +} + +static void session_conn_recv_pdu(void *data) +{ + struct iscsi_ev_context *ev_context = data; + iscsi_conn_t *conn = ev_context->conn; + struct iscsi_hdr hdr; + + conn->recv_context = ev_context; + + switch (conn->state) { + case ISCSI_CONN_STATE_IN_LOGIN: + iscsi_recv_login_rsp(conn); + break; + case ISCSI_CONN_STATE_LOGGED_IN: + case ISCSI_CONN_STATE_IN_LOGOUT: + case ISCSI_CONN_STATE_LOGOUT_REQUESTED: + /* read incoming PDU */ + if (iscsi_io_recv_pdu(conn, &hdr, ISCSI_DIGEST_NONE, + conn->data, ISCSI_DEF_MAX_RECV_SEG_LEN, + ISCSI_DIGEST_NONE, 0) < 0) + return; + + switch (hdr.opcode & ISCSI_OPCODE_MASK) { + case ISCSI_OP_NOOP_IN: + iscsi_recv_nop_in(conn, &hdr); + break; + case ISCSI_OP_LOGOUT_RSP: + iscsi_recv_logout_rsp(conn, &hdr); + break; + case ISCSI_OP_ASYNC_EVENT: + iscsi_recv_async_msg(conn, &hdr); + break; + default: + log_error("unsupported opcode 0x%x", hdr.opcode); + break; + } + break; + case ISCSI_CONN_STATE_XPT_WAIT: + iscsi_ev_context_put(ev_context); + log_debug(1, "ignoring incoming PDU in XPT_WAIT. " + "let connection re-establish or fail"); + break; + case ISCSI_CONN_STATE_CLEANUP_WAIT: + iscsi_ev_context_put(ev_context); + log_debug(1, "ignoring incoming PDU in XPT_WAIT. " + "let connection cleanup"); + break; + default: + iscsi_ev_context_put(ev_context); + log_error("Invalid state. Dropping PDU."); + } +} + +static void session_increase_wq_priority(struct iscsi_session *session) +{ + DIR *proc_dir; + struct dirent *proc_dent; + struct stat statb; + char stat_file[PATH_SIZE]; + char sbuf[1024]; /* got this from ps */ + int pid, stat_fd, num_read; + char *proc_name, *proc_name_end; + uint32_t host_no; + + /* drivers like bnx2i and qla4xxx do not have a write wq */ + if (session->t->caps & CAP_DATA_PATH_OFFLOAD) + return; + + proc_dir = opendir(PROC_DIR); + if (!proc_dir) + goto fail; + + while ((proc_dent = readdir(proc_dir))) { + if (!strcmp(proc_dent->d_name, ".") || + !strcmp(proc_dent->d_name, "..")) + continue; + if (sscanf(proc_dent->d_name, "%d", &pid) != 1) + continue; + + memset(stat_file, 0, sizeof(stat_file)); + sprintf(stat_file, PROC_DIR"/%d/stat", pid); + if (stat(stat_file, &statb)) + continue; + + if (!S_ISREG( statb.st_mode)) + continue; + + stat_fd = open(stat_file, O_RDONLY); + if (stat_fd == -1) + continue; + + memset(sbuf, 0, sizeof(sbuf)); + num_read = read(stat_fd, sbuf, sizeof(sbuf)); + close(stat_fd); + if (num_read == -1) + continue; + if (num_read == sizeof(sbuf)) + sbuf[num_read - 1] = '\0'; + else + sbuf[num_read] = '\0'; + + /* + * Finally match proc name to iscsi thread name. + * In newer kernels the name is iscsi_wq_%HOST_NO. + * In older kernels before 2.6.30, it was scsi_wq_%HOST_NO. + * + * We only support newer kernels. + */ + proc_name = strchr(sbuf, '(') + 1; + if (!proc_name) + continue; + + proc_name_end = strchr(proc_name, ')'); + if (!proc_name_end) + continue; + + *proc_name_end = '\0'; + + if (sscanf(proc_name, "iscsi_q_%u\n", &host_no) == 1) { + if (host_no == session->hostno) { + if (!setpriority(PRIO_PROCESS, pid, + session->nrec.session.xmit_thread_priority)) { + closedir(proc_dir); + return; + } else + break; + } + } + } + closedir(proc_dir); +fail: + log_error("Could not set session%d priority. " + "READ/WRITE throughout and latency could be " + "affected.", session->id); +} + +static int session_ipc_create(struct iscsi_session *session) +{ + struct iscsi_conn *conn = &session->conn[0]; + int err = 0, pass_ep = 1; + uint32_t host_no = -1; + + if (session->t->template->ep_connect != ktransport_ep_connect) + pass_ep = 0; +retry_create: + err = ipc->create_session(session->t->handle, + pass_ep ? conn->transport_ep_handle : 0, + session->nrec.session.initial_cmdsn, + session->nrec.session.cmds_max, + session->nrec.session.queue_depth, + &session->id, &host_no); + /* + * Older kernels were not passed the sessions's leading conn ep, + * so we will get -EINVAL || -ENOSYS for iser. + * + * 2.6.22 and earlier would send -EINVAL instead of -ENOSYS. + */ + if (pass_ep && (err == -ENOSYS || err == -EINVAL)) { + pass_ep = 0; + goto retry_create; + } + + if (!err) { + session->hostno = host_no; + session_increase_wq_priority(session); + } + return err; +} + +static void setup_offload_login_phase(iscsi_conn_t *conn) +{ + iscsi_session_t *session = conn->session; + iscsi_login_context_t *c = &conn->login_context; + int rc; + + actor_delete(&conn->login_timer); + + if (iscsi_session_set_params(conn)) { + iscsi_login_eh(conn, c->qtask, ISCSI_ERR_LOGIN); + return; + } + + if (iscsi_session_set_neg_params(conn)) { + iscsi_login_eh(conn, c->qtask, ISCSI_ERR_LOGIN); + return; + } + + if (iscsi_host_set_params(session)) { + iscsi_login_eh(conn, c->qtask, ISCSI_ERR_LOGIN); + return; + } + + conn->state = ISCSI_CONN_STATE_IN_LOGIN; + if (ipc->start_conn(session->t->handle, session->id, conn->id, + &rc) || rc) { + if (rc == -EEXIST) { + log_error("Session already exists."); + session_conn_shutdown(conn, c->qtask, + ISCSI_ERR_SESS_EXISTS); + } else { + log_error("can't start connection %d:%d retcode (%d)", + session->id, conn->id, rc); + iscsi_login_eh(conn, c->qtask, ISCSI_ERR_INTERNAL); + } + return; + } + + session->notify_qtask = c->qtask; +} + + +static void session_conn_poll(void *data) +{ + struct iscsi_ev_context *ev_context = data; + iscsi_conn_t *conn = ev_context->conn; + struct iscsi_session *session = conn->session; + int err = ISCSI_SUCCESS; + queue_task_t *qtask = ev_context->data; + iscsi_login_context_t *c = &conn->login_context; + int rc; + + iscsi_ev_context_put(ev_context); + + if (conn->state != ISCSI_CONN_STATE_XPT_WAIT) + return; + + rc = session->t->template->ep_poll(conn, 1); + if (rc == 0) { + log_debug(4, "poll not connected %d", rc); + ev_context = iscsi_ev_context_get(conn, 0); + if (!ev_context) { + /* while polling the recv pool should be full */ + log_error("BUG: session_conn_poll could not get conn " + "context."); + iscsi_login_eh(conn, qtask, ISCSI_ERR_INTERNAL); + return; + } + ev_context->data = qtask; + /* not connected yet, check later */ + iscsi_sched_ev_context(ev_context, conn, 1, EV_CONN_POLL); + } else if (rc > 0) { + /* connected! */ + memset(c, 0, sizeof(iscsi_login_context_t)); + + /* do not allocate new connection in case of reopen */ + if (session->id == -1) { + if (conn->id == 0 && session_ipc_create(session)) { + log_error("Can't create session."); + err = ISCSI_ERR_INTERNAL; + goto cleanup; + } + log_debug(3, "created new iSCSI session sid %d host " + "no %u", session->id, session->hostno); + + err = ipc->create_conn(session->t->handle, + session->id, conn->id, &conn->id); + if (err) { + log_error("Can't create connection."); + err = ISCSI_ERR_INTERNAL; + goto cleanup; + } + log_debug(3, "created new iSCSI connection " + "%d:%d", session->id, conn->id); + } + + iscsi_copy_operational_params(conn, + &session->nrec.session.iscsi, + &session->nrec.conn[conn->id].iscsi); + /* + * TODO: use the iface number or some other value + * so this will be persistent + */ + session->isid[3] = (session->id >> 16) & 0xff; + session->isid[4] = (session->id >> 8) & 0xff; + session->isid[5] = session->id & 0xff; + + if (ipc->bind_conn(session->t->handle, session->id, + conn->id, conn->transport_ep_handle, + (conn->id == 0), &rc) || rc) { + log_error("can't bind conn %d:%d to session %d, " + "retcode %d (%d)", session->id, conn->id, + session->id, rc, errno); + iscsi_login_eh(conn, qtask, ISCSI_ERR_LOGIN); + return; + } + log_debug(3, "bound iSCSI connection %d:%d to session %d", + session->id, conn->id, session->id); + + c->qtask = qtask; + c->cid = conn->id; + c->buffer = conn->data; + c->bufsize = sizeof(conn->data); + + conn->exp_statsn = iscsi_sysfs_get_exp_statsn(session->id); + + if (session->t->caps & CAP_LOGIN_OFFLOAD) { + setup_offload_login_phase(conn); + return; + } + + if (iscsi_session_set_params(conn)) { + iscsi_login_eh(conn, qtask, ISCSI_ERR_LOGIN); + return; + } + + if (iscsi_host_set_params(session)) { + iscsi_login_eh(conn, qtask, ISCSI_ERR_LOGIN); + return; + } + + if (iscsi_login_begin(session, c)) { + iscsi_login_eh(conn, qtask, ISCSI_ERR_LOGIN); + return; + } + + conn->state = ISCSI_CONN_STATE_IN_LOGIN; + if (iscsi_login_req(session, c)) { + iscsi_login_eh(conn, qtask, ISCSI_ERR_LOGIN); + return; + } + } else { + log_debug(4, "poll error %d", rc); + queue_delayed_reopen(qtask, ISCSI_CONN_ERR_REOPEN_DELAY); + } + + return; + +cleanup: + session_conn_shutdown(conn, qtask, err); +} + +static void session_conn_process_login(void *data) +{ + struct iscsi_ev_context *ev_context = data; + enum iscsi_conn_state state = *(enum iscsi_conn_state *) + ev_context->data; + struct iscsi_conn *conn = ev_context->conn; + struct iscsi_session *session = conn->session; + iscsi_login_context_t *c = &conn->login_context; + queue_task_t *qtask; + + iscsi_ev_context_put(ev_context); + if (!(session->t->caps & CAP_LOGIN_OFFLOAD)) + return; + + if (state == ISCSI_CONN_STATE_FREE) + goto failed_login; + + if (conn->state == ISCSI_CONN_STATE_LOGGED_IN) + return; + + conn->state = ISCSI_CONN_STATE_LOGGED_IN; + /* + * ok we were in_login and now we got the notification that we are + * logged in + */ + log_debug(3, "session created sid %u host no %d", session->id, + session->hostno); + + if (session->r_stage == R_STAGE_NO_CHANGE || + session->r_stage == R_STAGE_SESSION_REDIRECT) { + /* + * scan host is one-time deal. We + * don't want to re-scan it on recovery. + */ + session_scan_host(session, session->hostno, + c->qtask); + session->notify_qtask = NULL; + + log_warning("Connection%d:%d to [target: %s, portal: %s,%d] " + "through [iface: %s] is operational now", + session->id, conn->id, session->nrec.name, + session->nrec.conn[conn->id].address, + session->nrec.conn[conn->id].port, + session->nrec.iface.name); + } else { + session->notify_qtask = NULL; + mgmt_ipc_write_rsp(c->qtask, ISCSI_SUCCESS); + } + + /* + * reset ERL=0 reopen counter + */ + session->reopen_cnt = 0; + session->r_stage = R_STAGE_NO_CHANGE; + + return; + +failed_login: + qtask = session->notify_qtask; + session->notify_qtask = NULL; + mgmt_ipc_write_rsp(qtask, ISCSI_ERR_LOGIN); + if (ipc->destroy_conn(session->t->handle, session->id, conn->id)) + log_error("can not safely destroy connection %d", conn->id); + if (ipc->destroy_session(session->t->handle, session->id)) + log_error("can not safely destroy session %d", session->id); + __session_destroy(session); + +} + +static int iscsi_sched_ev_context(struct iscsi_ev_context *ev_context, + struct iscsi_conn *conn, unsigned long tmo, + int event) +{ + enum iscsi_err error; + + log_debug(7, "sched conn context %p event %d, tmo %lu", + &ev_context->actor, event, tmo); + + ev_context->conn = conn; + switch (event) { + case EV_CONN_RECV_PDU: + actor_init(&ev_context->actor, session_conn_recv_pdu, + ev_context); + actor_schedule(&ev_context->actor); + break; + case EV_CONN_ERROR: + error = *(enum iscsi_err *)ev_context->data; + + actor_init(&ev_context->actor, session_conn_error, + ev_context); + /* + * We handle invalid host, by killing the session. + * It must go at the head of the queue, so we do not + * initiate error handling or logout or some other op. + */ + if (error == ISCSI_ERR_INVALID_HOST) + actor_schedule_head(&ev_context->actor); + else + actor_schedule(&ev_context->actor); + break; + case EV_CONN_LOGIN: + actor_init(&ev_context->actor, session_conn_process_login, + ev_context); + actor_schedule(&ev_context->actor); + break; + case EV_CONN_POLL: + actor_timer(&ev_context->actor, tmo, + session_conn_poll, ev_context); + break; + case EV_CONN_LOGOUT_TIMER: + actor_timer(&ev_context->actor, tmo, + iscsi_logout_timedout, ev_context); + break; + case EV_CONN_STOP: + actor_init(&ev_context->actor, iscsi_stop, + ev_context); + actor_schedule(&ev_context->actor); + break; + default: + log_error("Invalid event type %d.", event); + } + return 0; +} + +static iscsi_session_t* session_find_by_rec(node_rec_t *rec) +{ + struct iscsi_transport *t; + iscsi_session_t *session; + + list_for_each_entry(t, &transports, list) { + list_for_each_entry(session, &t->sessions, list) { + if (__iscsi_match_session(rec, session->nrec.name, + session->nrec.conn[0].address, + session->nrec.conn[0].port, + &session->nrec.iface, + MATCH_ANY_SID)) + return session; + } + } + return NULL; +} + +/* + * a session could be running in the kernel but not in iscsid + * due to a resync or because some other app started the session + */ +static int session_is_running(node_rec_t *rec) +{ + int nr_found = 0; + + if (session_find_by_rec(rec)) + return 1; + + if (iscsi_sysfs_for_each_session(rec, &nr_found, iscsi_match_session, + 0)) + return 1; + + return 0; +} + +static int __session_login_task(node_rec_t *rec, queue_task_t *qtask) +{ + iscsi_session_t *session; + iscsi_conn_t *conn; + struct iscsi_transport *t; + int rc; + + if (session_is_running(rec)) { + if (rec->session.multiple) + log_debug(2, "Adding a copy of an existing session"); + else + return ISCSI_ERR_SESS_EXISTS; + } + + t = iscsi_sysfs_get_transport_by_name(rec->iface.transport_name); + if (!t) + return ISCSI_ERR_TRANS_NOT_FOUND; + + if ((!(t->caps & CAP_RECOVERY_L0) && + rec->session.iscsi.ERL != 0) || + (!(t->caps & CAP_RECOVERY_L1) && + rec->session.iscsi.ERL > 1)) { + log_error("Transport '%s' does not support ERL %d." + "Setting ERL to ERL0.", + t->name, rec->session.iscsi.ERL); + rec->session.iscsi.ERL = 0; + } + + if (!(t->caps & CAP_MULTI_R2T) && + rec->session.iscsi.MaxOutstandingR2T) { + log_error("Transport '%s' does not support " + "MaxOutstandingR2T %d. Setting " + "MaxOutstandingR2T to 1.", t->name, + rec->session.iscsi.MaxOutstandingR2T); + rec->session.iscsi.MaxOutstandingR2T = 1; + } + + if (!(t->caps & CAP_HDRDGST) && + rec->conn[0].iscsi.HeaderDigest) { + log_error("Transport '%s' does not support " + "HeaderDigest != None. Setting HeaderDigest " + "to None.", t->name); + rec->conn[0].iscsi.HeaderDigest = CONFIG_DIGEST_NEVER; + } + + if (!(t->caps & CAP_DATADGST) && + rec->conn[0].iscsi.DataDigest) { + log_error("Transport '%s' does not support " + "DataDigest != None. Setting DataDigest " + "to None", t->name); + rec->conn[0].iscsi.DataDigest = CONFIG_DIGEST_NEVER; + } + + if (!(t->caps & CAP_MARKERS) && + rec->conn[0].iscsi.IFMarker) { + log_error("Transport '%s' does not support IFMarker. " + "Disabling IFMarkers.", t->name); + rec->conn[0].iscsi.IFMarker = 0; + } + + if (!(t->caps & CAP_MARKERS) && + rec->conn[0].iscsi.OFMarker) { + log_error("Transport '%s' does not support OFMarker." + "Disabling OFMarkers.", t->name); + rec->conn[0].iscsi.OFMarker = 0; + } + + session = __session_create(rec, t, &rc); + if (rc == ISCSI_ERR_HOST_NOT_FOUND) + return rc; + else if (!session) + return ISCSI_ERR_LOGIN; + + /* FIXME: login all connections! marked as "automatic" */ + + /* create leading connection */ + rc = __session_conn_create(session, 0); + if (rc) { + __session_destroy(session); + return rc; + } + conn = &session->conn[0]; + qtask->conn = conn; + + rc = iscsi_host_set_net_params(&rec->iface, session); + if (rc == ISCSI_ERR_AGAIN) { + /* + * host/iscsiuio not ready. Cannot block iscsid, so caller is + * going to internally retry the operation. + */ + __session_destroy(session); + return ISCSI_ERR_HOST_NOT_FOUND; + } else if (rc) { + __session_destroy(session); + return ISCSI_ERR_LOGIN; + } + + if (gettimeofday(&conn->initial_connect_time, NULL)) + log_error("Could not get initial connect time. If " + "login errors iscsid may give up the initial " + "login early. You should manually login."); + + conn->state = ISCSI_CONN_STATE_XPT_WAIT; + qtask->rsp.command = MGMT_IPC_SESSION_LOGIN; + qtask->rsp.err = ISCSI_SUCCESS; + + if (iscsi_conn_connect(conn, qtask)) { + log_debug(4, "Initial connect failed. Waiting %u seconds " + "before trying to reconnect.", + ISCSI_CONN_ERR_REOPEN_DELAY); + queue_delayed_reopen(qtask, ISCSI_CONN_ERR_REOPEN_DELAY); + } + + return ISCSI_SUCCESS; +} + +int +session_login_task(node_rec_t *rec, queue_task_t *qtask) +{ + int rc; + + rc = __session_login_task(rec, qtask); + if (rc == ISCSI_ERR_HOST_NOT_FOUND) { + rc = queue_session_login_task_retry(NULL, rec, qtask); + if (rc) + return rc; + /* + * we are going to internally retry. Will return final rc + * when completed + */ + return ISCSI_SUCCESS; + } + return rc; +} + +static void session_login_task_retry(void *data) +{ + struct login_task_retry_info *info = data; + struct node_rec *rec = info->rec; + int rc; + + rc = __session_login_task(rec, info->qtask); + if (rc == ISCSI_ERR_HOST_NOT_FOUND) { + if (info->retry_count == rec->conn[0].timeo.login_timeout) { + /* give up */ + goto write_rsp; + } + + rc = queue_session_login_task_retry(info, rec, info->qtask); + if (rc) + goto write_rsp; + /* we are going to internally retry */ + return; + } else if (rc) { + /* hard error - no retry */ + goto write_rsp; + } else + /* successfully started login operation */ + goto free; +write_rsp: + mgmt_ipc_write_rsp(info->qtask, rc); +free: + free(info); +} + +static int queue_session_login_task_retry(struct login_task_retry_info *info, + node_rec_t *rec, queue_task_t *qtask) +{ + if (!info) { + info = malloc(sizeof(*info)); + if (!info) + return ISCSI_ERR_NOMEM; + memset(info, 0, sizeof(*info)); + info->qtask = qtask; + info->rec = rec; + } + + info->retry_count++; + log_debug(4, "queue session setup attempt in %d secs, retries %d", + 1, info->retry_count); + actor_timer(&info->retry_actor, 1, session_login_task_retry, info); + return 0; +} + +static int +sync_conn(iscsi_session_t *session, uint32_t cid) +{ + iscsi_conn_t *conn; + int rc; + + rc = __session_conn_create(session, cid); + if (rc) + return rc; + conn = &session->conn[cid]; + + /* TODO: must export via sysfs so we can pick this up */ + conn->state = ISCSI_CONN_STATE_CLEANUP_WAIT; + return 0; +} + +int +iscsi_sync_session(node_rec_t *rec, queue_task_t *qtask, uint32_t sid) +{ + iscsi_session_t *session; + struct iscsi_transport *t; + int err; + + t = iscsi_sysfs_get_transport_by_name(rec->iface.transport_name); + if (!t) + return ISCSI_ERR_TRANS_NOT_FOUND; + + session = __session_create(rec, t, &err); + if (!session) + return ISCSI_ERR_LOGIN; + + session->id = sid; + session->hostno = iscsi_sysfs_get_host_no_from_sid(sid, &err); + if (err) { + log_error("Could not get hostno for session %d", sid); + goto destroy_session; + } + + session->r_stage = R_STAGE_SESSION_REOPEN; + + err = sync_conn(session, 0); + if (err) + goto destroy_session; + + qtask->rsp.command = MGMT_IPC_SESSION_SYNC; + + log_debug(3, "Started sync iSCSI session %d", session->id); + session->notify_qtask = qtask; + session_conn_reopen(&session->conn[0], qtask, + STOP_CONN_RECOVER); + + return 0; + +destroy_session: + __session_destroy(session); + log_error("Could not sync session%d err %d", sid, err); + return err; +} + +static int session_unbind(struct iscsi_session *session) +{ + int err; + + err = ipc->unbind_session(session->t->handle, session->id); + if (err) + /* older kernels did not support unbind */ + log_debug(2, "Could not unbind session %d.", err); + return err; +} + +int session_logout_task(int sid, queue_task_t *qtask) +{ + iscsi_session_t *session; + iscsi_conn_t *conn; + int rc = ISCSI_SUCCESS; + + session = session_find_by_sid(sid); + if (!session) { + log_debug(1, "session sid %d not found.", sid); + return ISCSI_ERR_SESS_NOT_FOUND; + } + conn = &session->conn[0]; + + /* + * If syncing up, in XPT_WAIT, and REOPENing, then return + * an informative error, since the target for this session + * is likely not connected + */ + if (session->notify_qtask && + (conn->state == ISCSI_CONN_STATE_XPT_WAIT) && + (session->r_stage == R_STAGE_SESSION_REOPEN)) { + log_warning("session cannot be terminted because it's trying to reconnect: try again later"); + return ISCSI_ERR_SESSION_NOT_CONNECTED; + } + + /* + * If syncing up and not reconnecting, + * or if this is the initial login and mgmt_ipc + * has not been notified of that result fail the logout request + */ + if (session->notify_qtask || + ((conn->state == ISCSI_CONN_STATE_XPT_WAIT || + conn->state == ISCSI_CONN_STATE_IN_LOGIN) && + (session->r_stage == R_STAGE_NO_CHANGE || + session->r_stage == R_STAGE_SESSION_REDIRECT))) { +invalid_state: + log_error("session in invalid state for logout. " + "Try again later"); + return ISCSI_ERR_INTERNAL; + } + + if (dconfig->safe_logout && session_in_use(sid)) { + log_error("Session is actively in use for mounted storage, " + "and iscsid.safe_logout is configured."); + return ISCSI_ERR_BUSY; + } + + /* FIXME: logout all active connections */ + conn = &session->conn[0]; + if (conn->logout_qtask) + goto invalid_state; + + qtask->conn = conn; + qtask->rsp.command = MGMT_IPC_SESSION_LOGOUT; + conn->logout_qtask = qtask; + + switch (conn->state) { + case ISCSI_CONN_STATE_LOGGED_IN: + if (!session_unbind(session)) + return ISCSI_SUCCESS; + + /* LLDs that offload login also offload logout */ + if (!(session->t->caps & CAP_LOGIN_OFFLOAD)) { + /* unbind is not supported so just do old logout */ + if (!iscsi_send_logout(conn)) + return ISCSI_SUCCESS; + } + + log_error("Could not send logout pdu. Dropping session"); + /* fallthrough */ + default: + rc = session_conn_shutdown(conn, qtask, ISCSI_SUCCESS); + break; + } + + return rc; +} + +int +iscsi_host_send_targets(queue_task_t *qtask, int host_no, int do_login, + struct sockaddr_storage *ss) +{ + struct iscsi_transport *t; + + t = iscsi_sysfs_get_transport_by_hba(host_no); + if (!t) { + log_error("Invalid host no %d for sendtargets", host_no); + return ISCSI_ERR_TRANS_NOT_FOUND; + } + if (!(t->caps & CAP_SENDTARGETS_OFFLOAD)) + return ISCSI_ERR_TRANS_CAPS; + + if (ipc->sendtargets(t->handle, host_no, (struct sockaddr *)ss)) + return ISCSI_ERR; + + return ISCSI_SUCCESS; +} + +/* + * HW drivers like qla4xxx present an interface that hides most of the iscsi + * details. Userspace sends down a discovery event then it gets notified + * if the sessions that were logged in as a result asynchronously, or + * the card will have sessions preset in the FLASH and will log into them + * automaotically then send us notification that a session is setup. + */ +static void iscsi_async_session_creation(uint32_t host_no, uint32_t sid) +{ + struct iscsi_transport *transport; + + transport = iscsi_sysfs_get_transport_by_hba(host_no); + if (!transport) + return; + + if (!(transport->caps & CAP_FW_DB)) + return; + + log_debug(3, "session created sid %u host no %d", sid, host_no); + session_online_devs(host_no, sid); + session_scan_host(NULL, host_no, NULL); +} + +static void iscsi_async_session_destruction(uint32_t host_no, uint32_t sid) +{ + log_debug(3, "session destroyed sid %u host no %d", sid, host_no); +} + +static struct iscsi_ipc_ev_clbk ipc_clbk = { + .create_session = iscsi_async_session_creation, + .destroy_session = iscsi_async_session_destruction, + .get_ev_context = iscsi_ev_context_get, + .put_ev_context = iscsi_ev_context_put, + .sched_ev_context = iscsi_sched_ev_context, +}; + +void iscsi_initiator_init(void) +{ + ipc_register_ev_callback(&ipc_clbk); +} diff --git a/usr/initiator.h b/usr/initiator.h new file mode 100644 index 0000000..eccafb9 --- /dev/null +++ b/usr/initiator.h @@ -0,0 +1,365 @@ +/* + * iSCSI Initiator + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#ifndef INITIATOR_H +#define INITIATOR_H + +#include +#include +#include + +#include "types.h" +#include "iscsi_proto.h" +#include "iscsi_if.h" +#include "auth.h" +#include "mgmt_ipc.h" +#include "config.h" +#include "actor.h" +#include "list.h" + +#define ISCSI_CONFIG_ROOT "/etc/iscsi/" + +#define CONFIG_FILE ISCSI_CONFIG_ROOT"iscsid.conf" +#define INITIATOR_NAME_FILE ISCSI_CONFIG_ROOT"initiatorname.iscsi" + +#define PID_FILE "/run/iscsid.pid" +#ifndef LOCK_DIR +#define LOCK_DIR "/run/lock/iscsi" +#endif +#define LOCK_FILE LOCK_DIR"/lock" +#define LOCK_WRITE_FILE LOCK_DIR"/lock.write" + +typedef enum iscsi_session_r_stage_e { + R_STAGE_NO_CHANGE, + R_STAGE_SESSION_CLEANUP, + R_STAGE_SESSION_REOPEN, + R_STAGE_SESSION_REDIRECT, + R_STAGE_SESSION_DESTOYED, +} iscsi_session_r_stage_e; + +typedef enum conn_login_status_e { + CONN_LOGIN_SUCCESS = 0, + CONN_LOGIN_FAILED = 1, + CONN_LOGIN_IO_ERR = 2, + CONN_LOGIN_RETRY = 3, + CONN_LOGIN_IMM_RETRY = 4, + CONN_LOGIN_IMM_REDIRECT_RETRY = 5, + CONN_LOGIN_AUTH_FAILED = 6, +} conn_login_status_e; + +enum iscsi_login_status { + LOGIN_OK = 0, + LOGIN_IO_ERROR = 1, + LOGIN_FAILED = 2, + LOGIN_VERSION_MISMATCH = 3, + LOGIN_NEGOTIATION_FAILED = 4, + LOGIN_AUTHENTICATION_FAILED = 5, + LOGIN_REDIRECTION_FAILED = 6, + LOGIN_INVALID_PDU = 7, + LOGIN_REDIRECT = 8, +}; + +typedef enum iscsi_event_e { + EV_UNKNOWN, + EV_CONN_RECV_PDU, + EV_CONN_POLL, + EV_CONN_ERROR, + EV_CONN_LOGOUT_TIMER, + EV_CONN_STOP, + EV_CONN_LOGIN, +} iscsi_event_e; + +struct queue_task; + +typedef struct iscsi_login_context { + int cid; + char *buffer; + size_t bufsize; + uint8_t status_class; + uint8_t status_detail; + struct iscsi_acl *auth_client; + struct iscsi_hdr pdu; + struct iscsi_login_rsp *login_rsp; + char *data; + int received_pdu; + int max_data_length; + int timeout; + int final; + enum iscsi_login_status ret; + struct queue_task *qtask; +} iscsi_login_context_t; + +struct iscsi_session; +struct iscsi_conn; +struct iscsi_ev_context; + +/* daemon's connection structure */ +typedef struct iscsi_conn { + uint32_t id; + struct iscsi_session *session; + iscsi_login_context_t login_context; + struct iscsi_ev_context *recv_context; + struct queue_task *logout_qtask; + char data[ISCSI_DEF_MAX_RECV_SEG_LEN]; + char host[NI_MAXHOST]; /* scratch */ + enum iscsi_conn_state state; + int userspace_nop; + + struct timeval initial_connect_time; + actor_t login_timer; + actor_t nop_out_timer; + +#define CONTEXT_POOL_MAX 32 + struct iscsi_ev_context *context_pool[CONTEXT_POOL_MAX]; + + /* login state machine */ + int current_stage; + int next_stage; + int partial_response; + conn_login_status_e status; + + /* tcp/socket settings */ + + /* + * Either a tcp/ip or a netlink socket to do + * IO through. + */ + int socket_fd; + /* address being used for normal session connection */ + struct sockaddr_storage saddr; + /* address received during login */ + struct sockaddr_storage failback_saddr; + int tcp_window_size; + int type_of_service; + + /* used for the IPC of bind and for connect/poll/disconnect by + * transports (eg iser) which does these ops from the kernel. + * In the case of TCP, it is just the transport_fd casted to u64. */ + uint64_t transport_ep_handle; + int bind_ep; + + /* timeouts */ + int login_timeout; + int logout_timeout; + int auth_timeout; + int active_timeout; + + int noop_out_interval; + int noop_out_timeout; + + /* sequencing */ + uint32_t exp_statsn; + + /* negotiated parameters */ + uint32_t hdrdgst_en; + uint32_t datadgst_en; + uint32_t max_recv_dlength; /* the value we declare */ + uint32_t max_xmit_dlength; /* the value declared by the target */ +} iscsi_conn_t; + +struct iscsi_ev_context { + struct actor actor; + struct iscsi_conn *conn; + int allocated; + void *data; +}; + +typedef struct queue_task { + iscsi_conn_t *conn; + iscsiadm_req_t req; + iscsiadm_rsp_t rsp; + int mgmt_ipc_fd; + int allocated : 1; + /* Newer request types include a + * variable-length payload */ + void *payload; +} queue_task_t; + +struct iscsi_transport_template; +struct iscsi_transport; + +/* daemon's session structure */ +typedef struct iscsi_session { + struct list_head list; + uint32_t id; + uint32_t hostno; + char netdev[IFNAMSIZ]; + struct iscsi_transport *t; + uint8_t use_ipc; + node_rec_t nrec; /* copy of original Node record in database */ + unsigned int irrelevant_keys_bitmap; + int send_async_text; + uint32_t itt; + uint32_t cmdsn; + uint32_t exp_cmdsn; + uint32_t max_cmdsn; + int erl; + uint32_t imm_data_en; + uint32_t initial_r2t_en; + uint32_t max_r2t; + uint32_t fast_abort; + uint32_t first_burst; + uint32_t max_burst; + uint32_t pdu_inorder_en; + uint32_t dataseq_inorder_en; + uint32_t def_time2wait; + uint32_t def_time2retain; + int type; + int portal_group_tag; + uint8_t isid[6]; + uint16_t tsih; + char target_name[TARGET_NAME_MAXLEN + 1]; + char *target_alias; + char *initiator_name; + char *initiator_alias; + struct auth_str_block auth_recv_string_block; + struct auth_str_block auth_send_string_block; + struct auth_large_binary auth_recv_binary_block; + struct auth_large_binary auth_send_binary_block; + struct iscsi_acl auth_client_block; + struct iscsi_acl *auth_client; + int num_auth_buffers; + struct auth_buffer_desc auth_buffers[5]; + int bidirectional_auth; + char username[AUTH_STR_MAX_LEN]; + uint8_t password[AUTH_STR_MAX_LEN]; + int password_length; + char username_in[AUTH_STR_MAX_LEN]; + uint8_t password_in[AUTH_STR_MAX_LEN]; + int password_in_length; + iscsi_conn_t conn[ISCSI_CONN_MAX]; + uint64_t param_mask; + + /* connection reopens during recovery */ + int reopen_cnt; + int reopen_max; + queue_task_t reopen_qtask; + iscsi_session_r_stage_e r_stage; + uint32_t replacement_timeout; + + int host_reset_timeout; + int tgt_reset_timeout; + int lu_reset_timeout; + int abort_timeout; + + /* + * used for hw and sync up to notify caller that the operation + * is complete + */ + queue_task_t *notify_qtask; +} iscsi_session_t; + +/* login.c */ + +#define ISCSI_SESSION_TYPE_NORMAL 0 +#define ISCSI_SESSION_TYPE_DISCOVERY 1 + +/* not defined by iSCSI, but used in the login code to determine + * when to send the initial Login PDU + */ +#define ISCSI_INITIAL_LOGIN_STAGE -1 + +#define ISCSI_TEXT_SEPARATOR '=' + +/* implemented in iscsi-login.c for use on all platforms */ +extern int iscsi_add_text(struct iscsi_hdr *hdr, char *data, int max_data_length, + char *param, char *value); +extern enum iscsi_login_status iscsi_login(iscsi_session_t *session, int cid, + char *buffer, size_t bufsize, uint8_t * status_class, + uint8_t * status_detail); +extern int iscsi_update_address(iscsi_conn_t *conn, char *address); +extern int iscsi_login_begin(iscsi_session_t *session, + iscsi_login_context_t *c); +extern int iscsi_login_req(iscsi_session_t *session, iscsi_login_context_t *c); +extern int iscsi_login_rsp(iscsi_session_t *session, iscsi_login_context_t *c); +extern int resolve_address(char *host, char *port, struct sockaddr_storage *ss); + +/* Digest types */ +#define ISCSI_DIGEST_NONE 0 +#define ISCSI_DIGEST_CRC32C 1 +#define ISCSI_DIGEST_CRC32C_NONE 2 /* offer both, prefer CRC32C */ +#define ISCSI_DIGEST_NONE_CRC32C 3 /* offer both, prefer None */ + +#define IRRELEVANT_MAXCONNECTIONS 0x01 +#define IRRELEVANT_INITIALR2T 0x02 +#define IRRELEVANT_IMMEDIATEDATA 0x04 +#define IRRELEVANT_MAXBURSTLENGTH 0x08 +#define IRRELEVANT_FIRSTBURSTLENGTH 0x10 +#define IRRELEVANT_MAXOUTSTANDINGR2T 0x20 +#define IRRELEVANT_DATAPDUINORDER 0x40 +#define IRRELEVANT_DATASEQUENCEINORDER 0x80 + + +/* + * These user/kernel IPC calls are used by transports (eg iSER) that have their + * native connection managed from the kernel. The IPC for having the user space + * code being able to do it, is implemented as an enhancement of the open iscsi + * netlink IPC scheme, currently with the ability to connect/poll-for-establish + * ment/disconnect an opaque transport dependent 64 bit ep (endpoint) handle. + * The exact IPC ABI for that matter is defined in iscsi_if.h + */ +/* netlink.c */ +extern int ktransport_ep_connect(iscsi_conn_t *conn, int non_blocking); +extern int ktransport_ep_poll(iscsi_conn_t *conn, int timeout_ms); +extern void ktransport_ep_disconnect(iscsi_conn_t *conn); + +/* io.c */ +extern int iscsi_io_tcp_poll(iscsi_conn_t *conn, int timeout_ms); +extern int iscsi_io_tcp_connect(iscsi_conn_t *conn, int non_blocking); +extern void iscsi_io_tcp_disconnect(iscsi_conn_t *conn); + +extern int iscsi_io_connect(iscsi_conn_t *conn); +extern void iscsi_io_disconnect(iscsi_conn_t *conn); +extern int iscsi_io_send_pdu(iscsi_conn_t *conn, struct iscsi_hdr *hdr, + int hdr_digest, char *data, int data_digest, int timeout); +extern int iscsi_io_recv_pdu(iscsi_conn_t *conn, struct iscsi_hdr *hdr, + int hdr_digest, char *data, int max_data_length, int data_digest, + int timeout); + +/* initiator.c */ +extern int session_login_task(node_rec_t *rec, queue_task_t *qtask); +extern int session_logout_task(int sid, queue_task_t *qtask); +extern iscsi_session_t *session_find_by_sid(uint32_t sid); +extern int iscsi_sync_session(node_rec_t *rec, queue_task_t + *tsk, uint32_t sid); +extern int iscsi_host_send_targets(queue_task_t *qtask, + int host_no, int do_login, struct sockaddr_storage *ss); + +extern void free_initiator(void); +extern void iscsi_initiator_init(void); + +/* initiator code common to discovery and normal sessions */ +extern int iscsi_session_set_neg_params(struct iscsi_conn *conn); +extern int iscsi_session_set_params(struct iscsi_conn *conn); +extern int iscsi_host_set_params(struct iscsi_session *session); +extern int iscsi_host_set_net_params(struct iface_rec *iface, + struct iscsi_session *session); +extern void iscsi_copy_operational_params(struct iscsi_conn *conn, + struct iscsi_session_operational_config *session_conf, + struct iscsi_conn_operational_config *conn_conf); +extern int iscsi_setup_authentication(struct iscsi_session *session, + struct iscsi_auth_config *auth_cfg); +extern int iscsi_setup_portal(struct iscsi_conn *conn, char *address, int port); +extern int iscsi_set_net_config(struct iscsi_transport *t, + iscsi_session_t *session, + struct iface_rec *iface); +extern void iscsi_session_init_params(struct iscsi_session *session); + +extern int session_in_use(int sid); +#endif /* INITIATOR_H */ diff --git a/usr/initiator_common.c b/usr/initiator_common.c new file mode 100644 index 0000000..790f13d --- /dev/null +++ b/usr/initiator_common.c @@ -0,0 +1,734 @@ +/* + * Common code for setting up discovery and normal sessions. + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * Copyright (C) 2006 - 2009 Mike Christie + * Copyright (C) 2006 - 2009 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#include +#include +#include +#include +#include +#include + +#include "initiator.h" +#include "transport.h" +#include "iscsid.h" +#include "iscsi_ipc.h" +#include "log.h" +#include "iscsi_sysfs.h" +#include "iscsi_settings.h" +#include "iface.h" +#include "host.h" +#include "sysdeps.h" +#include "iscsi_err.h" +#include "iscsi_net_util.h" + +struct iscsi_session *session_find_by_sid(uint32_t sid) +{ + struct iscsi_transport *t; + struct iscsi_session *session; + + list_for_each_entry(t, &transports, list) { + list_for_each_entry(session, &t->sessions, list) { + if (session->id == sid) + return session; + } + } + return NULL; +} + +const static unsigned int align_32_down(unsigned int param) +{ + return param & ~0x3; +} + +int iscsi_setup_authentication(struct iscsi_session *session, + struct iscsi_auth_config *auth_cfg) +{ + /* if we have any incoming credentials, we insist on authenticating + * the target or not logging in at all + */ + if (auth_cfg->username_in[0] || auth_cfg->password_in_length) { + /* sanity check the config */ + if (auth_cfg->password_length == 0) { + log_warning("CHAP configuration has incoming " + "authentication credentials but has no " + "outgoing credentials configured."); + return EINVAL; + } + session->bidirectional_auth = 1; + } else { + /* no or 1-way authentication */ + session->bidirectional_auth = 0; + } + + /* copy in whatever credentials we have */ + strlcpy(session->username, auth_cfg->username, + sizeof (session->username)); + session->username[sizeof (session->username) - 1] = '\0'; + if ((session->password_length = auth_cfg->password_length)) + memcpy(session->password, auth_cfg->password, + session->password_length); + + strlcpy(session->username_in, auth_cfg->username_in, + sizeof (session->username_in)); + session->username_in[sizeof (session->username_in) - 1] = '\0'; + if ((session->password_in_length = + auth_cfg->password_in_length)) + memcpy(session->password_in, auth_cfg->password_in, + session->password_in_length); + + if (session->password_length || session->password_in_length) { + /* setup the auth buffers */ + session->auth_buffers[0].address = &session->auth_client_block; + session->auth_buffers[0].length = + sizeof (session->auth_client_block); + session->auth_buffers[1].address = + &session->auth_recv_string_block; + session->auth_buffers[1].length = + sizeof (session->auth_recv_string_block); + + session->auth_buffers[2].address = + &session->auth_send_string_block; + session->auth_buffers[2].length = + sizeof (session->auth_send_string_block); + + session->auth_buffers[3].address = + &session->auth_recv_binary_block; + session->auth_buffers[3].length = + sizeof (session->auth_recv_binary_block); + + session->auth_buffers[4].address = + &session->auth_send_binary_block; + session->auth_buffers[4].length = + sizeof (session->auth_send_binary_block); + + session->num_auth_buffers = 5; + log_debug(6, "authentication setup complete..."); + } else { + session->num_auth_buffers = 0; + log_debug(6, "no authentication configured..."); + } + + return 0; +} + +void +iscsi_copy_operational_params(struct iscsi_conn *conn, + struct iscsi_session_operational_config *session_conf, + struct iscsi_conn_operational_config *conn_conf) +{ + struct iscsi_session *session = conn->session; + struct iscsi_transport *t = session->t; + + conn->hdrdgst_en = conn_conf->HeaderDigest; + conn->datadgst_en = conn_conf->DataDigest; + + conn->max_recv_dlength = + align_32_down(conn_conf->MaxRecvDataSegmentLength); + if (conn->max_recv_dlength < ISCSI_MIN_MAX_RECV_SEG_LEN || + conn->max_recv_dlength > ISCSI_MAX_MAX_RECV_SEG_LEN) { + log_error("Invalid iscsi.MaxRecvDataSegmentLength. Must be " + "within %u and %u. Setting to %u", + ISCSI_MIN_MAX_RECV_SEG_LEN, + ISCSI_MAX_MAX_RECV_SEG_LEN, + DEF_INI_MAX_RECV_SEG_LEN); + conn_conf->MaxRecvDataSegmentLength = + DEF_INI_MAX_RECV_SEG_LEN; + conn->max_recv_dlength = DEF_INI_MAX_RECV_SEG_LEN; + } + + /* zero indicates to use the target's value */ + conn->max_xmit_dlength = + align_32_down(conn_conf->MaxXmitDataSegmentLength); + if (conn->max_xmit_dlength == 0) + conn->max_xmit_dlength = ISCSI_DEF_MAX_RECV_SEG_LEN; + if (conn->max_xmit_dlength < ISCSI_MIN_MAX_RECV_SEG_LEN || + conn->max_xmit_dlength > ISCSI_MAX_MAX_RECV_SEG_LEN) { + log_error("Invalid iscsi.MaxXmitDataSegmentLength. Must be " + "within %u and %u. Setting to %u", + ISCSI_MIN_MAX_RECV_SEG_LEN, + ISCSI_MAX_MAX_RECV_SEG_LEN, + DEF_INI_MAX_RECV_SEG_LEN); + conn_conf->MaxXmitDataSegmentLength = + DEF_INI_MAX_RECV_SEG_LEN; + conn->max_xmit_dlength = DEF_INI_MAX_RECV_SEG_LEN; + } + + /* session's operational parameters */ + session->initial_r2t_en = session_conf->InitialR2T; + session->max_r2t = session_conf->MaxOutstandingR2T; + session->imm_data_en = session_conf->ImmediateData; + session->first_burst = align_32_down(session_conf->FirstBurstLength); + /* + * some targets like netapp fail the login if sent bad first_burst + * and max_burst lens, even when immediate data=no and + * initial r2t = Yes, so we always check the user values. + */ + if (session->first_burst < ISCSI_MIN_FIRST_BURST_LEN || + session->first_burst > ISCSI_MAX_FIRST_BURST_LEN) { + log_error("Invalid iscsi.FirstBurstLength of %u. Must be " + "within %u and %u. Setting to %u", + session->first_burst, + ISCSI_MIN_FIRST_BURST_LEN, + ISCSI_MAX_FIRST_BURST_LEN, + DEF_INI_FIRST_BURST_LEN); + session_conf->FirstBurstLength = DEF_INI_FIRST_BURST_LEN; + session->first_burst = DEF_INI_FIRST_BURST_LEN; + } + + session->max_burst = align_32_down(session_conf->MaxBurstLength); + if (session->max_burst < ISCSI_MIN_MAX_BURST_LEN || + session->max_burst > ISCSI_MAX_MAX_BURST_LEN) { + log_error("Invalid iscsi.MaxBurstLength of %u. Must be " + "within %u and %u. Setting to %u", + session->max_burst, ISCSI_MIN_MAX_BURST_LEN, + ISCSI_MAX_MAX_BURST_LEN, DEF_INI_MAX_BURST_LEN); + session_conf->MaxBurstLength = DEF_INI_MAX_BURST_LEN; + session->max_burst = DEF_INI_MAX_BURST_LEN; + } + + if (session->first_burst > session->max_burst) { + log_error("Invalid iscsi.FirstBurstLength of %u. Must be " + "less than iscsi.MaxBurstLength. Setting to %u", + session->first_burst, session->max_burst); + session_conf->FirstBurstLength = session->max_burst; + session->first_burst = session->max_burst; + } + + session->def_time2wait = session_conf->DefaultTime2Wait; + session->def_time2retain = session_conf->DefaultTime2Retain; + session->erl = session_conf->ERL; + + if (session->type == ISCSI_SESSION_TYPE_DISCOVERY) { + /* + * Right now, we only support 8K max for kernel based + * sendtargets discovery, because the recv pdu buffers are + * limited to this size. + */ + if ((t->caps & CAP_TEXT_NEGO) && + conn->max_recv_dlength > ISCSI_DEF_MAX_RECV_SEG_LEN) + conn->max_recv_dlength = ISCSI_DEF_MAX_RECV_SEG_LEN; + + /* We do not support discovery sessions with digests */ + conn->hdrdgst_en = ISCSI_DIGEST_NONE; + conn->datadgst_en = ISCSI_DIGEST_NONE; + } + + if (t->template->create_conn) + t->template->create_conn(conn); +} + +int iscsi_setup_portal(struct iscsi_conn *conn, char *address, int port) +{ + char serv[NI_MAXSERV]; + + sprintf(serv, "%d", port); + if (resolve_address(address, serv, &conn->saddr)) { + log_error("cannot resolve host name %s", address); + return ISCSI_ERR_TRANS; + } + conn->failback_saddr = conn->saddr; + + getnameinfo((struct sockaddr *)&conn->saddr, sizeof(conn->saddr), + conn->host, sizeof(conn->host), NULL, 0, NI_NUMERICHOST); + log_debug(4, "resolved %s to %s", address, conn->host); + return 0; +} + +static int host_set_param(struct iscsi_transport *t, + uint32_t host_no, int param, char *value, + int type) +{ + int rc; + + rc = ipc->set_host_param(t->handle, host_no, param, value, type); + /* 2.6.20 and below returns EINVAL */ + if (rc && rc != -ENOSYS && rc != -EINVAL) { + log_error("can't set operational parameter %d for " + "host %d, retcode %d (%d)", param, host_no, + rc, errno); + return ISCSI_ERR_INVAL; + } + return 0; +} + +static void print_param_value(enum iscsi_param param, void *value, int type) +{ + log_debug(3, "set operational parameter %d to:", param); + + if (type == ISCSI_STRING) + log_debug(3, "%s", value ? (char *)value : "NULL"); + else + log_debug(3, "%u", *(uint32_t *)value); +} + +#define MAX_HOST_PARAMS 2 + +int iscsi_host_set_params(struct iscsi_session *session) +{ + struct iscsi_transport *t = session->t; + int i; + struct hostparam { + int param; + int type; + void *value; + } hosttbl[MAX_HOST_PARAMS] = { + { + .param = ISCSI_HOST_PARAM_NETDEV_NAME, + .value = session->nrec.iface.netdev, + .type = ISCSI_STRING, + }, { + .param = ISCSI_HOST_PARAM_HWADDRESS, + .value = session->nrec.iface.hwaddress, + .type = ISCSI_STRING, + }, + }; + + for (i = 0; i < MAX_HOST_PARAMS; i++) { + if (host_set_param(t, session->hostno, + hosttbl[i].param, hosttbl[i].value, + hosttbl[i].type)) { + return EPERM; + } + + print_param_value(hosttbl[i].param, hosttbl[i].value, + hosttbl[i].type); + } + + return 0; +} + +static inline void iscsi_session_clear_param(struct iscsi_session *session, + int param) +{ + session->param_mask &= ~(1ULL << param); +} + +void iscsi_session_init_params(struct iscsi_session *session) +{ + session->param_mask = ~0ULL; + if (!(session->t->caps & CAP_MULTI_R2T)) + iscsi_session_clear_param(session, ISCSI_PARAM_MAX_R2T); + if (!(session->t->caps & CAP_HDRDGST)) + iscsi_session_clear_param(session, ISCSI_PARAM_HDRDGST_EN); + if (!(session->t->caps & CAP_DATADGST)) + iscsi_session_clear_param(session, ISCSI_PARAM_DATADGST_EN); + if (!(session->t->caps & CAP_MARKERS)) { + iscsi_session_clear_param(session, ISCSI_PARAM_IFMARKER_EN); + iscsi_session_clear_param(session, ISCSI_PARAM_OFMARKER_EN); + } +} + +#define MAX_SESSION_NEG_PARAMS 16 + +int iscsi_session_set_neg_params(struct iscsi_conn *conn) +{ + struct iscsi_session *session = conn->session; + int i, rc; + uint32_t zero = 0; + struct connparam { + int param; + int type; + void *value; + int conn_only; + } conntbl[MAX_SESSION_NEG_PARAMS] = { + { + .param = ISCSI_PARAM_MAX_RECV_DLENGTH, + .value = &conn->max_recv_dlength, + .type = ISCSI_INT, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_MAX_XMIT_DLENGTH, + .value = &conn->max_xmit_dlength, + .type = ISCSI_INT, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_HDRDGST_EN, + .value = &conn->hdrdgst_en, + .type = ISCSI_INT, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_DATADGST_EN, + .value = &conn->datadgst_en, + .type = ISCSI_INT, + .conn_only = 1, + }, { + .param = ISCSI_PARAM_INITIAL_R2T_EN, + .value = &session->initial_r2t_en, + .type = ISCSI_INT, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_MAX_R2T, + .value = &session->max_r2t, + .type = ISCSI_INT, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_IMM_DATA_EN, + .value = &session->imm_data_en, + .type = ISCSI_INT, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_FIRST_BURST, + .value = &session->first_burst, + .type = ISCSI_INT, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_MAX_BURST, + .value = &session->max_burst, + .type = ISCSI_INT, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_PDU_INORDER_EN, + .value = &session->pdu_inorder_en, + .type = ISCSI_INT, + .conn_only = 0, + }, { + .param =ISCSI_PARAM_DATASEQ_INORDER_EN, + .value = &session->dataseq_inorder_en, + .type = ISCSI_INT, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_ERL, + .value = &zero, /* FIXME: session->erl */ + .type = ISCSI_INT, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_IFMARKER_EN, + .value = &zero,/* FIXME: session->ifmarker_en */ + .type = ISCSI_INT, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_OFMARKER_EN, + .value = &zero,/* FIXME: session->ofmarker_en */ + .type = ISCSI_INT, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_EXP_STATSN, + .value = &conn->exp_statsn, + .type = ISCSI_UINT, + .conn_only = 1, + }, { + .param = ISCSI_PARAM_TPGT, + .value = &session->portal_group_tag, + .type = ISCSI_INT, + .conn_only = 0, + }, + }; + + iscsi_session_init_params(session); + + /* Entered full-feature phase! */ + for (i = 0; i < MAX_SESSION_NEG_PARAMS; i++) { + if (conn->id != 0 && !conntbl[i].conn_only) + continue; + + if (!(session->param_mask & (1ULL << conntbl[i].param))) + continue; + + rc = ipc->set_param(session->t->handle, session->id, + conn->id, conntbl[i].param, conntbl[i].value, + conntbl[i].type); + if (rc && rc != -ENOSYS) { + log_error("can't set operational parameter %d for " + "connection %d:%d, retcode %d (%d)", + conntbl[i].param, session->id, conn->id, + rc, errno); + return EPERM; + } + + print_param_value(conntbl[i].param, conntbl[i].value, + conntbl[i].type); + } + + return 0; +} + +#define MAX_SESSION_PARAMS 20 + +int iscsi_session_set_params(struct iscsi_conn *conn) +{ + struct iscsi_session *session = conn->session; + int i, rc; + struct connparam { + int param; + int type; + void *value; + int conn_only; + } conntbl[MAX_SESSION_PARAMS] = { + { + .param = ISCSI_PARAM_TARGET_NAME, + .conn_only = 0, + .type = ISCSI_STRING, + .value = session->target_name, + }, { + .param = ISCSI_PARAM_PERSISTENT_ADDRESS, + .value = session->nrec.conn[conn->id].address, + .type = ISCSI_STRING, + .conn_only = 1, + }, { + .param = ISCSI_PARAM_PERSISTENT_PORT, + .value = &session->nrec.conn[conn->id].port, + .type = ISCSI_INT, + .conn_only = 1, + }, { + .param = ISCSI_PARAM_SESS_RECOVERY_TMO, + .value = &session->replacement_timeout, + .type = ISCSI_INT, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_USERNAME, + .value = session->username, + .type = ISCSI_STRING, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_USERNAME_IN, + .value = session->username_in, + .type = ISCSI_STRING, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_PASSWORD, + .value = session->password, + .type = ISCSI_STRING, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_PASSWORD_IN, + .value = session->password_in, + .type = ISCSI_STRING, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_FAST_ABORT, + .value = &session->fast_abort, + .type = ISCSI_INT, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_ABORT_TMO, + .value = &session->abort_timeout, + .type = ISCSI_INT, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_LU_RESET_TMO, + .value = &session->lu_reset_timeout, + .type = ISCSI_INT, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_TGT_RESET_TMO, + .value = &session->tgt_reset_timeout, + .type = ISCSI_INT, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_PING_TMO, + .value = &conn->noop_out_timeout, + .type = ISCSI_INT, + .conn_only = 1, + }, { + .param = ISCSI_PARAM_RECV_TMO, + .value = &conn->noop_out_interval, + .type = ISCSI_INT, + .conn_only = 1, + }, { + .param = ISCSI_PARAM_IFACE_NAME, + .value = session->nrec.iface.name, + .type = ISCSI_STRING, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_INITIATOR_NAME, + .value = session->initiator_name, + .type = ISCSI_STRING, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_BOOT_ROOT, + .value = session->nrec.session.boot_root, + .type = ISCSI_STRING, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_BOOT_NIC, + .value = session->nrec.session.boot_nic, + .type = ISCSI_STRING, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_BOOT_TARGET, + .value = session->nrec.session.boot_target, + .type = ISCSI_STRING, + .conn_only = 0, + }, { + .param = ISCSI_PARAM_DISCOVERY_SESS, + .value = &session->type, + .type = ISCSI_INT, + .conn_only = 0, + }, + }; + + iscsi_session_init_params(session); + + /* some llds will send nops internally */ + if (!iscsi_sysfs_session_supports_nop(session->id)) { + iscsi_session_clear_param(session, ISCSI_PARAM_PING_TMO); + iscsi_session_clear_param(session, ISCSI_PARAM_RECV_TMO); + } + + /* Entered full-feature phase! */ + for (i = 0; i < MAX_SESSION_PARAMS; i++) { + if (conn->id != 0 && !conntbl[i].conn_only) + continue; + + if (!(session->param_mask & (1ULL << conntbl[i].param))) + continue; + + rc = ipc->set_param(session->t->handle, session->id, + conn->id, conntbl[i].param, conntbl[i].value, + conntbl[i].type); + if (rc && rc != -ENOSYS) { + log_error("can't set operational parameter %d for " + "connection %d:%d, retcode %d (%d)", + conntbl[i].param, session->id, conn->id, + rc, errno); + return EPERM; + } + + if (rc == -ENOSYS) { + switch (conntbl[i].param) { + case ISCSI_PARAM_PING_TMO: + /* + * older kernels may not support nops + * in kernel + */ + conn->userspace_nop = 1; + break; +#if 0 +TODO handle this + case ISCSI_PARAM_INITIATOR_NAME: + /* use host level one instead */ + hosttbl[ISCSI_HOST_PARAM_INITIATOR_NAME].set = 1; + break; +#endif + } + } + + print_param_value(conntbl[i].param, conntbl[i].value, + conntbl[i].type); + } + + return 0; +} + +int iscsi_set_net_config(struct iscsi_transport *t, iscsi_session_t *session, + struct iface_rec *iface) +{ + if (t->template->set_net_config) { + /* uip needs the netdev name */ + struct host_info hinfo; + int hostno, rc; + + /* this assumes that the netdev or hw address is going to be + set */ + hostno = iscsi_sysfs_get_host_no_from_hwinfo(iface, &rc); + if (rc) { + log_debug(4, "Couldn't get host no."); + return rc; + } + + /* uip needs the netdev name */ + if (!strlen(iface->netdev)) { + memset(&hinfo, 0, sizeof(hinfo)); + hinfo.host_no = hostno; + iscsi_sysfs_get_hostinfo_by_host_no(&hinfo); + strcpy(iface->netdev, hinfo.iface.netdev); + } + + return t->template->set_net_config(t, iface, session); + } + + return 0; +} + +int iscsi_host_set_net_params(struct iface_rec *iface, + struct iscsi_session *session) +{ + struct iscsi_transport *t = session->t; + int rc = 0; + char *netdev; + struct host_info hinfo; + + log_debug(3, "setting iface %s, dev %s, set ip %s, hw %s, " + "transport %s.", + iface->name, iface->netdev, iface->ipaddress, + iface->hwaddress, iface->transport_name); + + if (!t->template->set_host_ip) + return 0; + + /* if we need to set the ip addr then set all the iface net settings */ + if (!iface_is_bound_by_ipaddr(iface)) { + if (t->template->set_host_ip == SET_HOST_IP_REQ) { + log_warning("Please set the iface.ipaddress for iface " + "%s, then retry the login command.", + iface->name); + return ISCSI_ERR_INVAL; + } else if (t->template->set_host_ip == SET_HOST_IP_OPT) { + log_info("Optional iface.ipaddress for iface %s " + "not set.", iface->name); + return 0; + } else { + return ISCSI_ERR_INVAL; + } + } + + /* these type of drivers need the netdev upd */ + if (strlen(iface->netdev)) + netdev = iface->netdev; + else { + memset(&hinfo, 0, sizeof(hinfo)); + hinfo.host_no = session->hostno; + iscsi_sysfs_get_hostinfo_by_host_no(&hinfo); + + netdev = hinfo.iface.netdev; + } + + if (!t->template->no_netdev && net_ifup_netdev(netdev)) + log_warning("Could not brining up netdev %s. Try running " + "'ifup %s' first if login fails.", netdev, netdev); + + rc = iscsi_set_net_config(t, session, iface); + if (rc != 0) + return rc; + + rc = host_set_param(t, session->hostno, + ISCSI_HOST_PARAM_IPADDRESS, + iface->ipaddress, ISCSI_STRING); + if (rc) + return rc; + + if (iface_is_bound_by_netdev(iface)) { + rc = host_set_param(t, session->hostno, + ISCSI_HOST_PARAM_NETDEV_NAME, + iface->netdev, ISCSI_STRING); + if (rc) + return rc; + } + + if (iface_is_bound_by_hwaddr(iface)) { + rc = host_set_param(t, session->hostno, + ISCSI_HOST_PARAM_HWADDRESS, + iface->hwaddress, ISCSI_STRING); + if (rc) + return rc; + } + return 0; +} diff --git a/usr/io.c b/usr/io.c new file mode 100644 index 0000000..e4d00ae --- /dev/null +++ b/usr/io.c @@ -0,0 +1,868 @@ +/* + * iSCSI I/O Library + * + * Copyright (C) 2002 Cisco Systems, Inc. + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "types.h" +#include "iscsi_proto.h" +#include "iscsi_settings.h" +#include "initiator.h" +#include "iscsi_ipc.h" +#include "log.h" +#include "transport.h" +#include "idbm.h" +#include "iface.h" +#include "sysdeps.h" + +#define LOG_CONN_CLOSED(conn) \ +do { \ + getnameinfo((struct sockaddr *) &conn->saddr, sizeof(conn->saddr), \ + conn->host, sizeof(conn->host), NULL, 0, NI_NUMERICHOST); \ + log_error("Connection to Discovery Address %s closed", conn->host); \ +} while (0) + +#define LOG_CONN_FAIL(conn) \ +do { \ + getnameinfo((struct sockaddr *) &conn->saddr, sizeof(conn->saddr), \ + conn->host, sizeof(conn->host), NULL, 0, NI_NUMERICHOST); \ + log_error("Connection to Discovery Address %s failed", conn->host); \ +} while (0) + +static int timedout; + +static void +sigalarm_handler(int unused) +{ + timedout = 1; +} + +static void +set_non_blocking(int fd) +{ + int res = fcntl(fd, F_GETFL); + + if (res != -1) { + res = fcntl(fd, F_SETFL, res | O_NONBLOCK); + if (res) + log_warning("unable to set fd flags (%s)!", + strerror(errno)); + } else + log_warning("unable to get fd flags (%s)!", strerror(errno)); + +} + +#if 0 +/* not used by anyone */ +static int get_hwaddress_from_netdev(char *netdev, char *hwaddress) +{ + struct ifaddrs *ifap, *ifa; + struct sockaddr_in *s4; + struct sockaddr_in6 *s6; + struct ifreq if_hwaddr; + int found = 0, sockfd; + unsigned char *hwaddr; + char buf[INET6_ADDRSTRLEN]; + + if (getifaddrs(&ifap)) { + log_error("Could not match hwaddress %s to netdev. " + "getifaddrs failed %d", hwaddress, errno); + return 0; + } + + /* Open a basic socket. */ + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + log_error("Could not open socket for ioctl."); + goto free_ifap; + } + + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (!ifa->ifa_addr) + continue; + + switch (ifa->ifa_addr->sa_family) { + case AF_INET: + s4 = (struct sockaddr_in *)(ifa->ifa_addr); + if (!inet_ntop(ifa->ifa_addr->sa_family, + (void *)&(s4->sin_addr), buf, + INET_ADDRSTRLEN)) + continue; + log_debug(4, "name %s addr %s", ifa->ifa_name, buf); + break; + case AF_INET6: + s6 = (struct sockaddr_in6 *)(ifa->ifa_addr); + if (!inet_ntop(ifa->ifa_addr->sa_family, + (void *)&(s6->sin6_addr), buf, INET6_ADDRSTRLEN)) + continue; + log_debug(4, "name %s addr %s", ifa->ifa_name, buf); + break; + default: + continue; + } + + if (strcmp(ifa->ifa_name, netdev)) + continue; + + strncpy(if_hwaddr.ifr_name, ifa->ifa_name, IFNAMSIZ); + if (ioctl(sockfd, SIOCGIFHWADDR, &if_hwaddr) < 0) { + log_error("Could not match %s to netdevice.", + hwaddress); + continue; + } + + /* check for ARPHRD_ETHER (ethernet) */ + if (if_hwaddr.ifr_hwaddr.sa_family != 1) + continue; + hwaddr = (unsigned char *)if_hwaddr.ifr_hwaddr.sa_data; + + memset(hwaddress, 0, ISCSI_MAX_IFACE_LEN); + /* TODO should look and covert so we do not need tmp buf */ + sprintf(hwaddress, "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x", + hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], + hwaddr[4], hwaddr[5]); + log_debug(4, "Found hardware address %s", hwaddress); + found = 1; + break; + } + + close(sockfd); +free_ifap: + freeifaddrs(ifap); + return found; +} +#endif + + +#if 0 + +This is not supported for now, because it is not exactly what we want. +It also turns out that targets will send packets to other interfaces +causing all types of weird things to happen. + + +static int bind_src_by_address(int sockfd, char *address) +{ + int rc = 0; + char port[NI_MAXSERV]; + struct sockaddr_storage saddr; + + memset(&saddr, 0, sizeof(struct sockaddr_storage)); + if (resolve_address(address, port, &saddr)) { + log_error("Could not bind %s to conn.", address); + return -1; + } + + switch (saddr.ss_family) { + case AF_INET: + rc = bind(sockfd, (struct sockaddr *)&saddr, + sizeof(struct sockaddr_in)); + break; + case AF_INET6: + rc = bind(sockfd, (struct sockaddr *)&saddr, + sizeof(struct sockaddr_in6)); + break; + default: + rc = -1; + } + if (rc) + log_error("Could not bind %s to %d.", address, sockfd); + else + log_debug(4, "Bound %s to socket fd %d", address, sockfd); + return rc; +} +#endif + +static int bind_conn_to_iface(iscsi_conn_t *conn, struct iface_rec *iface) +{ + struct iscsi_session *session = conn->session; + + if (strcmp(iface->transport_name, DEFAULT_TRANSPORT)) + return 0; + + memset(session->netdev, 0, IFNAMSIZ); + if (iface_is_bound_by_hwaddr(iface)) { + if (net_get_netdev_from_hwaddress(iface->hwaddress, + session->netdev)) { + log_error("Cannot match %s to net/scsi interface.", + iface->hwaddress); + return -1; + } + } else if (iface_is_bound_by_netdev(iface)) { + strcpy(session->netdev, iface->netdev); + } else if (iface_is_bound_by_ipaddr(iface)) { + /* + * we never supported this but now with offload having to + * set the ip address in the iface, useris may forget to + * set the offload's transport type and we end up here by + * accident. + */ + log_error("Cannot bind %s to net/scsi interface. This is not " + "supported with software iSCSI (iscsi_tcp).", + iface->ipaddress); + return -1; + } + + if (strlen(session->netdev)) { + struct ifreq ifr; + + log_debug(4, "Binding session %d to %s", session->id, + session->netdev); + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, session->netdev, IFNAMSIZ); + + if (setsockopt(conn->socket_fd, SOL_SOCKET, SO_BINDTODEVICE, + session->netdev, + strlen(session->netdev) + 1) < 0) { + log_error("Could not bind connection %d to %s", + conn->id, session->netdev); + return -1; + } + } + + return 0; +} + +int +iscsi_io_tcp_connect(iscsi_conn_t *conn, int non_blocking) +{ + int rc, onearg; + struct sockaddr_storage *ss = &conn->saddr; + char serv[NI_MAXSERV]; + + /* create a socket */ + conn->socket_fd = socket(ss->ss_family, SOCK_STREAM, IPPROTO_TCP); + + /* the trasport ep handle is used to bind with */ + conn->transport_ep_handle = conn->socket_fd; + + if (conn->socket_fd < 0) { + log_error("cannot create TCP socket"); + return -1; + } + + if (bind_conn_to_iface(conn, &conn->session->nrec.iface)) + return -1; + + onearg = 1; + rc = setsockopt(conn->socket_fd, IPPROTO_TCP, TCP_NODELAY, &onearg, + sizeof (onearg)); + if (rc < 0) { + log_error("cannot set TCP_NODELAY option on socket"); + close(conn->socket_fd); + conn->socket_fd = -1; + return rc; + } + + /* optionally set the window sizes */ + if (conn->tcp_window_size) { + int window_size = conn->tcp_window_size; + socklen_t arglen = sizeof (window_size); + + if (setsockopt(conn->socket_fd, SOL_SOCKET, SO_RCVBUF, + (char *) &window_size, sizeof (window_size)) < 0) { + log_warning("failed to set TCP recv window size " + "to %u", window_size); + } else { + if (getsockopt(conn->socket_fd, SOL_SOCKET, SO_RCVBUF, + (char *) &window_size, &arglen) >= 0) { + log_debug(4, "set TCP recv window size to %u, " + "actually got %u", + conn->tcp_window_size, window_size); + } + } + + window_size = conn->tcp_window_size; + arglen = sizeof (window_size); + + if (setsockopt(conn->socket_fd, SOL_SOCKET, SO_SNDBUF, + (char *) &window_size, sizeof (window_size)) < 0) { + log_warning("failed to set TCP send window size " + "to %u", window_size); + } else { + if (getsockopt(conn->socket_fd, SOL_SOCKET, SO_SNDBUF, + (char *) &window_size, &arglen) >= 0) { + log_debug(4, "set TCP send window size to %u, " + "actually got %u", + conn->tcp_window_size, window_size); + } + } + } + + /* + * Build a TCP connection to the target + */ + getnameinfo((struct sockaddr *) ss, sizeof(*ss), + conn->host, sizeof(conn->host), serv, sizeof(serv), + NI_NUMERICHOST|NI_NUMERICSERV); + + log_debug(1, "connecting to %s:%s", conn->host, serv); + if (non_blocking) + set_non_blocking(conn->socket_fd); + rc = connect(conn->socket_fd, (struct sockaddr *) ss, sizeof (*ss)); + return rc; +} + +int +iscsi_io_tcp_poll(iscsi_conn_t *conn, int timeout_ms) +{ + int rc; + struct pollfd pdesc; + char serv[NI_MAXSERV], lserv[NI_MAXSERV]; + struct sockaddr_storage ss; + socklen_t len; + + pdesc.fd = conn->socket_fd; + pdesc.events = POLLOUT; + rc = poll(&pdesc, 1, timeout_ms); + if (rc == 0) + return 0; + + if (rc < 0) { + getnameinfo((struct sockaddr *) &conn->saddr, + sizeof(conn->saddr), + conn->host, sizeof(conn->host), serv, sizeof(serv), + NI_NUMERICHOST|NI_NUMERICSERV); + + log_error("cannot make connection to %s:%s (%s)", + conn->host, serv, strerror(errno)); + return rc; + } + + len = sizeof(int); + if (getsockopt(conn->socket_fd, SOL_SOCKET, SO_ERROR, + (char *) &rc, &len) < 0) { + log_error("getsockopt for connect poll failed"); + return -1; + } + if (rc) { + getnameinfo((struct sockaddr *) &conn->saddr, + sizeof(conn->saddr), + conn->host, sizeof(conn->host), serv, sizeof(serv), + NI_NUMERICHOST|NI_NUMERICSERV); + + log_error("connect to %s:%s failed (%s)", + conn->host, serv, strerror(rc)); + return -rc; + } + + len = sizeof(ss); + if (log_level > 0 && + getsockname(conn->socket_fd, (struct sockaddr *) &ss, &len) >= 0) { + getnameinfo((struct sockaddr *) &conn->saddr, + sizeof(conn->saddr), conn->host, + sizeof(conn->host), serv, sizeof(serv), + NI_NUMERICHOST|NI_NUMERICSERV); + + getnameinfo((struct sockaddr *) &ss, sizeof(ss), + NULL, 0, lserv, sizeof(lserv), NI_NUMERICSERV); + + log_debug(1, "connected local port %s to %s:%s", + lserv, conn->host, serv); + } + return 1; +} + +void +iscsi_io_tcp_disconnect(iscsi_conn_t *conn) +{ + struct linger so_linger = { .l_onoff = 1, .l_linger = 0 }; + + if (conn->socket_fd >= 0) { + log_debug(1, "disconnecting conn %p, fd %d", conn, + conn->socket_fd); + + /* If the state is not IN_LOGOUT, this isn't a clean shutdown + * and there's some sort of error handling going on. In that + * case, set a 0 SO_LINGER to force an abortive close (RST) and + * free whatever is sitting in the TCP transmit queue. This is + * done to prevent stale data from being sent should the + * network connection be restored before TCP times out. + */ + if (conn->state != ISCSI_CONN_STATE_IN_LOGOUT) { + setsockopt(conn->socket_fd, SOL_SOCKET, SO_LINGER, + &so_linger, sizeof(so_linger)); + } + + close(conn->socket_fd); + conn->socket_fd = -1; + } +} + +int +iscsi_io_connect(iscsi_conn_t *conn) +{ + int rc, ret; + struct sigaction action; + struct sigaction old; + + /* set a timeout, since the socket calls may take a long time to + * timeout on their own + */ + memset(&action, 0, sizeof (struct sigaction)); + memset(&old, 0, sizeof (struct sigaction)); + action.sa_sigaction = NULL; + action.sa_flags = 0; + action.sa_handler = sigalarm_handler; + sigaction(SIGALRM, &action, &old); + timedout = 0; + alarm(conn->login_timeout); + + /* perform blocking TCP connect operation when no async request + * associated. SendTargets Discovery know to work in such a mode. + */ + rc = iscsi_io_tcp_connect(conn, 0); + if (timedout) { + log_error("connect to %s timed out", conn->host); + + log_debug(1, "socket %d connect timed out", conn->socket_fd); + ret = 0; + goto done; + } else if (rc < 0) { + log_error("cannot make connection to %s: %s", + conn->host, strerror(errno)); + close(conn->socket_fd); + ret = 0; + goto done; + } else if (log_level > 0) { + struct sockaddr_storage ss; + char lserv[NI_MAXSERV]; + char serv[NI_MAXSERV]; + socklen_t salen = sizeof(ss); + + if (getsockname(conn->socket_fd, (struct sockaddr *) &ss, + &salen) >= 0) { + getnameinfo((struct sockaddr *) &conn->saddr, + sizeof(conn->saddr), + conn->host, sizeof(conn->host), serv, + sizeof(serv), NI_NUMERICHOST|NI_NUMERICSERV); + + getnameinfo((struct sockaddr *) &ss, + sizeof(ss), + NULL, 0, lserv, sizeof(lserv), + NI_NUMERICSERV); + + log_debug(1, "connected local port %s to %s:%s", + lserv, conn->host, serv); + } + } + + ret = 1; + +done: + alarm(0); + sigaction(SIGALRM, &old, NULL); + return ret; +} + +void +iscsi_io_disconnect(iscsi_conn_t *conn) +{ + iscsi_io_tcp_disconnect(conn); +} + +static void +iscsi_log_text(struct iscsi_hdr *pdu, char *data) +{ + int dlength = ntoh24(pdu->dlength); + char *text = data; + char *end = text + dlength; + + while (text && (text < end)) { + log_debug(4, "> %s", text); + text += strlen(text); + while ((text < end) && (*text == '\0')) + text++; + } +} + +int +iscsi_io_send_pdu(iscsi_conn_t *conn, struct iscsi_hdr *hdr, + int hdr_digest, char *data, int data_digest, int timeout) +{ + int rc, ret = 0; + char *header = (char *) hdr; + char *end; + char pad[4]; + struct iovec vec[3]; + int pad_bytes; + int pdu_length = sizeof (*hdr) + hdr->hlength + ntoh24(hdr->dlength); + int remaining; + struct sigaction action; + struct sigaction old; + iscsi_session_t *session = conn->session; + + /* set a timeout, since the socket calls may take a long time + * to timeout on their own + */ + if (!session->use_ipc) { + memset(&action, 0, sizeof (struct sigaction)); + memset(&old, 0, sizeof (struct sigaction)); + action.sa_sigaction = NULL; + action.sa_flags = 0; + action.sa_handler = sigalarm_handler; + sigaction(SIGALRM, &action, &old); + timedout = 0; + alarm(timeout); + } + + memset(&pad, 0, sizeof (pad)); + memset(&vec, 0, sizeof (vec)); + + switch (hdr->opcode & ISCSI_OPCODE_MASK) { + case ISCSI_OP_LOGIN:{ + struct iscsi_login *login_hdr = (struct iscsi_login *) hdr; + + log_debug(4, "sending login PDU with current stage " + "%d, next stage %d, transit 0x%x, isid" + " 0x%02x%02x%02x%02x%02x%02x exp_statsn %u", + ISCSI_LOGIN_CURRENT_STAGE(login_hdr->flags), + ISCSI_LOGIN_NEXT_STAGE(login_hdr->flags), + login_hdr->flags & ISCSI_FLAG_LOGIN_TRANSIT, + login_hdr->isid[0], login_hdr->isid[1], + login_hdr->isid[2], login_hdr->isid[3], + login_hdr->isid[4], login_hdr->isid[5], + ntohl(login_hdr->exp_statsn)); + + iscsi_log_text(hdr, data); + break; + } + case ISCSI_OP_TEXT:{ + struct iscsi_text *text_hdr = (struct iscsi_text *) hdr; + + log_debug(4, "sending text pdu with CmdSN %x, exp_statsn %u", + ntohl(text_hdr->cmdsn), ntohl(text_hdr->cmdsn)); + iscsi_log_text(hdr, data); + break; + } + case ISCSI_OP_NOOP_OUT:{ + struct iscsi_nopout *nopout_hdr = (struct iscsi_nopout *) hdr; + + log_debug(4, "sending Nop-out pdu with ttt %x, CmdSN %x:", + ntohl(nopout_hdr->ttt), ntohl(nopout_hdr->cmdsn)); + iscsi_log_text(hdr, data); + break; + } + default: + log_debug(4, "sending pdu opcode 0x%x:", hdr->opcode); + break; + } + + /* send the PDU header */ + header = (char *) hdr; + end = header + sizeof (*hdr) + hdr->hlength; + + /* send all the data and any padding */ + if (pdu_length % ISCSI_PAD_LEN) + pad_bytes = ISCSI_PAD_LEN - (pdu_length % ISCSI_PAD_LEN); + else + pad_bytes = 0; + + if (session->use_ipc) + ipc->send_pdu_begin(session->t->handle, session->id, + conn->id, end - header, + ntoh24(hdr->dlength) + pad_bytes); + + while (header < end) { + vec[0].iov_base = header; + vec[0].iov_len = end - header; + + if (!session->use_ipc) + rc = writev(conn->socket_fd, vec, 1); + else + rc = ipc->writev(0, vec, 1); + if (timedout) { + log_error("socket %d write timed out", + conn->socket_fd); + ret = 0; + goto done; + } else if ((rc <= 0) && (errno != EAGAIN)) { + LOG_CONN_FAIL(conn); + ret = 0; + goto done; + } else if (rc > 0) { + log_debug(4, "wrote %d bytes of PDU header", rc); + header += rc; + } + } + + end = data + ntoh24(hdr->dlength); + remaining = ntoh24(hdr->dlength) + pad_bytes; + + while (remaining > 0) { + vec[0].iov_base = data; + vec[0].iov_len = end - data; + vec[1].iov_base = (void *) &pad; + vec[1].iov_len = pad_bytes; + + if (!session->use_ipc) + rc = writev(conn->socket_fd, vec, 2); + else + rc = ipc->writev(0, vec, 2); + if (timedout) { + log_error("socket %d write timed out", + conn->socket_fd); + ret = 0; + goto done; + } else if ((rc <= 0) && (errno != EAGAIN)) { + LOG_CONN_FAIL(conn); + ret = 0; + goto done; + } else if (rc > 0) { + log_debug(4, "wrote %d bytes of PDU data", rc); + remaining -= rc; + if (data < end) { + data += rc; + if (data > end) + data = end; + } + } + } + + if (session->use_ipc) { + if (ipc->send_pdu_end(session->t->handle, session->id, + conn->id, &rc)) { + ret = 0; + goto done; + } + } + + ret = 1; + + done: + if (!session->use_ipc) { + alarm(0); + sigaction(SIGALRM, &old, NULL); + timedout = 0; + } + return ret; +} + +int +iscsi_io_recv_pdu(iscsi_conn_t *conn, struct iscsi_hdr *hdr, + int hdr_digest, char *data, int max_data_length, int data_digest, + int timeout) +{ + uint32_t h_bytes = 0; + uint32_t ahs_bytes = 0; + uint32_t d_bytes = 0; + uint32_t ahslength = 0; + uint32_t dlength = 0; + uint32_t pad = 0; + int rlen = 0; + int failed = 0; + char *header = (char *) hdr; + char *end = data + max_data_length; + struct sigaction action; + struct sigaction old; + iscsi_session_t *session = conn->session; + + memset(data, 0, max_data_length); + + /* set a timeout, since the socket calls may take a long + * time to timeout on their own + */ + if (!session->use_ipc) { + memset(&action, 0, sizeof (struct sigaction)); + memset(&old, 0, sizeof (struct sigaction)); + action.sa_sigaction = NULL; + action.sa_flags = 0; + action.sa_handler = sigalarm_handler; + sigaction(SIGALRM, &action, &old); + timedout = 0; + alarm(timeout); + } else { + failed = ipc->recv_pdu_begin(conn); + if (failed == -EAGAIN) + return -EAGAIN; + else if (failed < 0) { + failed = 1; + goto done; + } + } + + /* read a response header */ + do { + if (!session->use_ipc) + rlen = read(conn->socket_fd, header, + sizeof (*hdr) - h_bytes); + else + rlen = ipc->read(header, sizeof (*hdr) - h_bytes); + if (timedout) { + log_error("socket %d header read timed out", + conn->socket_fd); + failed = 1; + goto done; + } else if (rlen == 0) { + LOG_CONN_CLOSED(conn); + failed = 1; + goto done; + } else if ((rlen < 0) && (errno != EAGAIN)) { + LOG_CONN_FAIL(conn); + failed = 1; + goto done; + } else if (rlen > 0) { + log_debug(4, "read %d bytes of PDU header", rlen); + header += rlen; + h_bytes += rlen; + } + } while (h_bytes < sizeof (*hdr)); + + log_debug(4, "read %d PDU header bytes, opcode 0x%x, dlength %u, " + "data %p, max %u", h_bytes, hdr->opcode & ISCSI_OPCODE_MASK, + ntoh24(hdr->dlength), data, max_data_length); + + /* check for additional headers */ + ahslength = hdr->hlength; /* already includes padding */ + if (ahslength) { + log_warning("additional header segment length %u not supported", + ahslength); + failed = 1; + goto done; + } + + /* read exactly what we expect, plus padding */ + dlength = hdr->dlength[0] << 16; + dlength |= hdr->dlength[1] << 8; + dlength |= hdr->dlength[2]; + + /* if we only expected to receive a header, exit */ + if (dlength == 0) + goto done; + + if (data + dlength > end) { + log_warning("buffer size %u too small for data length %u", + max_data_length, dlength); + failed = 1; + goto done; + } + + /* read the rest into our buffer */ + d_bytes = 0; + while (d_bytes < dlength) { + if (!session->use_ipc) + rlen = read(conn->socket_fd, data + d_bytes, + dlength - d_bytes); + else + rlen = ipc->read(data + d_bytes, dlength - d_bytes); + if (timedout) { + log_error("socket %d data read timed out", + conn->socket_fd); + failed = 1; + goto done; + } else if (rlen == 0) { + LOG_CONN_CLOSED(conn); + failed = 1; + goto done; + } else if ((rlen < 0 && errno != EAGAIN)) { + LOG_CONN_FAIL(conn); + failed = 1; + goto done; + } else if (rlen > 0) { + log_debug(4, "read %d bytes of PDU data", rlen); + d_bytes += rlen; + } + } + + /* handle PDU data padding. + * data is padded in case of kernel_io */ + pad = dlength % ISCSI_PAD_LEN; + if (pad && !session->use_ipc) { + int pad_bytes = pad = ISCSI_PAD_LEN - pad; + char bytes[ISCSI_PAD_LEN]; + + while (pad_bytes > 0) { + rlen = read(conn->socket_fd, &bytes, pad_bytes); + if (timedout) { + log_error("socket %d pad read timed out", + conn->socket_fd); + failed = 1; + goto done; + } else if (rlen == 0) { + LOG_CONN_CLOSED(conn); + failed = 1; + goto done; + } else if ((rlen < 0 && errno != EAGAIN)) { + LOG_CONN_FAIL(conn); + failed = 1; + goto done; + } else if (rlen > 0) { + log_debug(4, "read %d pad bytes", rlen); + pad_bytes -= rlen; + } + } + } + + switch (hdr->opcode) { + case ISCSI_OP_TEXT_RSP: + log_debug(4, "finished reading text PDU, %u hdr, %u " + "ah, %u data, %u pad", + h_bytes, ahs_bytes, d_bytes, pad); + iscsi_log_text(hdr, data); + break; + case ISCSI_OP_LOGIN_RSP:{ + struct iscsi_login_rsp *login_rsp = + (struct iscsi_login_rsp *) hdr; + + log_debug(4, "finished reading login PDU, %u hdr, " + "%u ah, %u data, %u pad", + h_bytes, ahs_bytes, d_bytes, pad); + log_debug(4, "login current stage %d, next stage " + "%d, transit 0x%x", + ISCSI_LOGIN_CURRENT_STAGE(login_rsp->flags), + ISCSI_LOGIN_NEXT_STAGE(login_rsp->flags), + login_rsp->flags & ISCSI_FLAG_LOGIN_TRANSIT); + iscsi_log_text(hdr, data); + break; + } + case ISCSI_OP_ASYNC_EVENT: + /* FIXME: log the event info */ + break; + default: + break; + } + +done: + if (!session->use_ipc) { + alarm(0); + sigaction(SIGALRM, &old, NULL); + } else { + /* finalyze receive transaction */ + if (ipc->recv_pdu_end(conn)) { + failed = 1; + } + } + + if (timedout || failed) { + timedout = 0; + return -EIO; + } + + return h_bytes + ahs_bytes + d_bytes; +} diff --git a/usr/iscsi_err.c b/usr/iscsi_err.c new file mode 100644 index 0000000..23c61fc --- /dev/null +++ b/usr/iscsi_err.c @@ -0,0 +1,78 @@ +/* + * iSCSI error helpers + * + * Copyright (C) 2011 Mike Christie + * Copyright (C) 2011 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#include "stdlib.h" +#include "iscsi_err.h" +#include "log.h" + +static char *iscsi_err_msgs[] = { + /* 0 */ "", + /* 1 */ "unknown error", + /* 2 */ "session not found", + /* 3 */ "no available memory", + /* 4 */ "encountered connection failure", + /* 5 */ "encountered iSCSI login failure", + /* 6 */ "encountered iSCSI database failure", + /* 7 */ "invalid parameter", + /* 8 */ "connection timed out", + /* 9 */ "internal error", + /* 10 */ "encountered iSCSI logout failure", + /* 11 */ "iSCSI PDU timed out", + /* 12 */ "iSCSI driver not found. Please make sure it is loaded, and retry the operation", + /* 13 */ "daemon access denied", + /* 14 */ "iSCSI driver does not support requested capability.", + /* 15 */ "session exists", + /* 16 */ "Unknown request", + /* 17 */ "iSNS service not supported", + /* 18 */ "could not communicate to iscsid", + /* 19 */ "encountered non-retryable iSCSI login failure", + /* 20 */ "could not connect to iscsid", + /* 21 */ "no objects found", + /* 22 */ "sysfs lookup failure", + /* 23 */ "host not found", + /* 24 */ "iSCSI login failed due to authorization failure", + /* 25 */ "iSNS query failed", + /* 26 */ "iSNS registration failed", + /* 27 */ "operation not supported", + /* 28 */ "device or resource in use", + /* 29 */ "operation failed but retry may succeed", + /* 30 */ "unknown discovery type", + /* 31 */ "child process terminated", + /* 32 */ "target likely not connected", +}; + +char *iscsi_err_to_str(int err) +{ + if (err >= ISCSI_MAX_ERR_VAL || err < 0) { + log_error("invalid error code %d", err); + return NULL; + } + + return iscsi_err_msgs[err]; +} + +void iscsi_err_print_msg(int err) +{ + if (err >= ISCSI_MAX_ERR_VAL || err < 0) { + log_error("invalid error code %d", err); + return; + } + log_error("initiator reported error (%d - %s)", err, + iscsi_err_msgs[err]); +} diff --git a/usr/iscsi_ipc.h b/usr/iscsi_ipc.h new file mode 100644 index 0000000..47857dd --- /dev/null +++ b/usr/iscsi_ipc.h @@ -0,0 +1,165 @@ +/* + * User/Kernel Transport IPC API Ioctl/NETLINK/etc + * + * Copyright (C) 2005 Dmitry Yusupov, Alex Aizman + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + * + * NOTE: OSes must implement this API in terms of user's control-plane re-use. + */ + +#ifndef ISCSI_IPC_H +#define ISCSI_IPC_H + +#include "iscsi_if.h" + +enum { + ISCSI_INT, + ISCSI_UINT, + ISCSI_STRING, +}; + +struct iscsi_conn; +struct iscsi_ev_context; + +/* + * When handling async events, the initiator may not be able to + * handle the event in the same context, so this allows the interface + * code to call into the initiator to shedule handling. + */ +struct iscsi_ipc_ev_clbk { + void (*create_session) (uint32_t host_no, uint32_t sid); + void (*destroy_session) (uint32_t host_no, uint32_t sid); + + struct iscsi_ev_context *(*get_ev_context) (struct iscsi_conn *conn, + int ev_size); + void (*put_ev_context) (struct iscsi_ev_context *ev_context); + int (*sched_ev_context) (struct iscsi_ev_context *ev_context, + struct iscsi_conn *conn, + unsigned long tmo, int event); +}; + +extern void ipc_register_ev_callback(struct iscsi_ipc_ev_clbk *ipc_ev_clbk); + +/** + * struct iscsi_ipc - Open-iSCSI Interface for Kernel IPC + * + * All functions allowed to return POSIX kind of error. i.e. 0 - OK, non-zero + * means IPC error and errno set. + */ +struct iscsi_ipc { + char *name; + + int ctldev_bufmax; + + int (*ctldev_open) (void); + + void (*ctldev_close) (void); + + int (*ctldev_handle) (void); + + int (*sendtargets) (uint64_t transport_handle, uint32_t host_no, + struct sockaddr *addr); + + int (*create_session) (uint64_t transport_handle, uint64_t ep_handle, + uint32_t initial_cmdsn, uint16_t cmds_max, + uint16_t qdepth, uint32_t *out_sid, + uint32_t *hostno); + + int (*destroy_session) (uint64_t transport_handle, uint32_t sid); + + int (*unbind_session) (uint64_t transport_handle, uint32_t sid); + + int (*create_conn) (uint64_t transport_handle, + uint32_t sid, uint32_t cid, uint32_t *out_cid); + + int (*destroy_conn) (uint64_t transport_handle, uint32_t sid, + uint32_t cid); + + int (*bind_conn) (uint64_t transport_handle, uint32_t sid, + uint32_t cid, uint64_t transport_eph, + int is_leading, int *retcode); + + int (*set_param) (uint64_t transport_handle, uint32_t sid, + uint32_t cid, enum iscsi_param param, + void *value, int type); + + int (*set_host_param) (uint64_t transport_handle, uint32_t host_no, + enum iscsi_host_param param, + void *value, int type); + + /* not implemented yet */ + int (*get_param) (uint64_t transport_handle, uint32_t sid, + uint32_t cid, enum iscsi_param param, + uint32_t *value, int *retcode); + + int (*get_stats) (uint64_t transport_handle, uint32_t sid, + uint32_t cid, char *statsbuf, int statsbuf_max); + + int (*start_conn) (uint64_t transport_handle, uint32_t sid, + uint32_t cid, int *retcode); + + int (*stop_conn) (uint64_t transport_handle, uint32_t sid, + uint32_t cid, int flag); + + int (*read) (char *data, int count); + + void (*send_pdu_begin) (uint64_t transport_handle, uint32_t sid, + uint32_t cid, int hdr_size, int data_size); + + int (*send_pdu_end) (uint64_t transport_handle, uint32_t sid, + uint32_t cid, int *retcode); + + int (*writev) (enum iscsi_uevent_e type, struct iovec *iovp, int count); + + int (*recv_pdu_begin) (struct iscsi_conn *conn); + + int (*recv_pdu_end) (struct iscsi_conn *conn); + + int (*set_net_config) (uint64_t transport_handle, uint32_t host_no, + struct iovec *iovs, uint32_t param_count); + + int (*recv_conn_state) (struct iscsi_conn *conn, uint32_t *state); + + int (*exec_ping) (uint64_t transport_handle, uint32_t host_no, + struct sockaddr *addr, uint32_t iface_num, + uint32_t iface_type, uint32_t size, uint32_t *status); + + int (*get_chap) (uint64_t transport_handle, uint32_t host_no, + uint16_t chap_tbl_idx, uint32_t num_entries, + char *chap_buf, uint32_t *valid_chap_entries); + + int (*set_chap) (uint64_t transport_handle, uint32_t host_no, + struct iovec *iovs, uint32_t param_count); + + int (*delete_chap) (uint64_t transport_handle, uint32_t host_no, + uint16_t chap_tbl_idx); + int (*set_flash_node_params) (uint64_t transport_handle, + uint32_t host_no, uint32_t flashnode_idx, + struct iovec *iovs, uint32_t param_count); + int (*new_flash_node) (uint64_t transport_handle, uint32_t host_no, + void *value, uint32_t *flashnode_idx); + int (*del_flash_node) (uint64_t transport_handle, uint32_t host_no, + uint32_t flashnode_idx); + int (*login_flash_node) (uint64_t transport_handle, uint32_t host_no, + uint32_t flashnode_idx); + int (*logout_flash_node) (uint64_t transport_handle, uint32_t host_no, + uint32_t flashnode_idx); + int (*logout_flash_node_sid) (uint64_t transport_handle, + uint32_t host_no, uint32_t sid); + int (*get_host_stats) (uint64_t transport_handle, uint32_t host_no, + char *host_stats); +}; + +#endif /* ISCSI_IPC_H */ diff --git a/usr/iscsi_net_util.c b/usr/iscsi_net_util.c new file mode 100644 index 0000000..b5a910f --- /dev/null +++ b/usr/iscsi_net_util.c @@ -0,0 +1,455 @@ +/* + * net helpers + * + * Copyright (C) 2010 Mike Christie + * Copyright (C) 2010 Red Hat, Inc. 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 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sysdeps.h" +#include "ethtool-copy.h" +#include "iscsi_net_util.h" +#include "log.h" + +struct iscsi_net_driver { + const char *net_drv_name; + const char *iscsi_transport; +}; + +static struct iscsi_net_driver net_drivers[] = { + {"cxgb3", "cxgb3i" }, + {"cxgb4", "cxgb4i" }, + {"bnx2", "bnx2i" }, + {"bnx2x", "bnx2i"}, + {NULL, NULL} +}; + +/** + * net_get_transport_name_from_netdev - get name of transport to use for iface + * @netdev: netdev iface name + * @transport: buffer to hold transport name + * + * transport buffer should be ISCSI_TRANSPORT_NAME_MAXLEN bytes + */ +int net_get_transport_name_from_netdev(char *netdev, char *transport) +{ + struct ethtool_drvinfo drvinfo; + struct ifreq ifr; + int err, fd, i; + + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, netdev); + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + log_error("Could not open socket for ioctl."); + return errno; + } + + drvinfo.cmd = ETHTOOL_GDRVINFO; + ifr.ifr_data = (caddr_t)&drvinfo; + err = ioctl(fd, SIOCETHTOOL, &ifr); + if (err < 0) { + log_error("Could not get driver %s.", netdev); + err = errno; + goto close_sock; + } + + /* + * iSCSI hardware offload for bnx2{,x} is only supported if the + * iscsiuio executable is available. + */ + if (!strcmp(drvinfo.driver, "bnx2x") || + !strcmp(drvinfo.driver, "bnx2")) { + struct stat buf; + + if (stat(ISCSIUIO_PATH, &buf)) { + log_debug(1, "ISCSI offload not supported " + "(%s not found).", ISCSIUIO_PATH); + err = ENODEV; + goto close_sock; + } + } + + for (i = 0; net_drivers[i].net_drv_name != NULL; i++) { + struct iscsi_net_driver *net_driver = &net_drivers[i]; + + if (!strcmp(net_driver->net_drv_name, drvinfo.driver)) { + strcpy(transport, net_driver->iscsi_transport); + err = 0; + goto close_sock; + } + } + err = ENODEV; + +close_sock: + close(fd); + return err; +} + +/** + * net_get_netdev_from_hwaddress - given a hwaddress return the ethX + * @hwaddress: hw address no larger than ISCSI_HWADDRESS_BUF_SIZE + * @netdev: buffer of IFNAMSIZ size that will hold the ethX + * + * Does not support interfaces like a bond or alias because + * multiple interfaces will have the same hwaddress. + */ +int net_get_netdev_from_hwaddress(char *hwaddress, char *netdev) +{ + struct if_nameindex *ifni; + struct ifreq if_hwaddr; + int found = 0, sockfd, i = 0; + unsigned char *hwaddr; + char tmp_hwaddress[ISCSI_HWADDRESS_BUF_SIZE]; + + ifni = if_nameindex(); + if (ifni == NULL) { + log_error("Could not match hwaddress %s to netdev. " + "getifaddrs failed %d", hwaddress, errno); + return errno; + } + + /* Open a basic socket. */ + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + log_error("Could not open socket for ioctl."); + goto free_ifni; + } + + for (i = 0; ifni[i].if_index && ifni[i].if_name; i++) { + struct if_nameindex *n = &ifni[i]; + + strlcpy(if_hwaddr.ifr_name, n->if_name, IFNAMSIZ); + if (ioctl(sockfd, SIOCGIFHWADDR, &if_hwaddr) < 0) { + log_error("Could not match %s to netdevice.", + hwaddress); + continue; + } + + /* check for ARPHRD_ETHER (ethernet) */ + if (if_hwaddr.ifr_hwaddr.sa_family != 1) + continue; + hwaddr = (unsigned char *)if_hwaddr.ifr_hwaddr.sa_data; + + memset(tmp_hwaddress, 0, ISCSI_HWADDRESS_BUF_SIZE); + /* TODO should look and covert so we do not need tmp buf */ + sprintf(tmp_hwaddress, "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x", + hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], + hwaddr[4], hwaddr[5]); + log_debug(4, "Found hardware address %s", tmp_hwaddress); + if (!strcasecmp(tmp_hwaddress, hwaddress)) { + log_debug(4, "Matches %s to %s", hwaddress, + n->if_name); + memset(netdev, 0, IFNAMSIZ); + strlcpy(netdev, n->if_name, IFNAMSIZ); + found = 1; + break; + } + } + + close(sockfd); +free_ifni: + if_freenameindex(ifni); + if (!found) + return ENODEV; + return 0; +} + +static char *find_vlan_dev(char *netdev, int vlan_id) { + struct ifreq if_hwaddr; + struct ifreq vlan_hwaddr; + struct vlan_ioctl_args vlanrq = { .cmd = GET_VLAN_VID_CMD, }; + struct if_nameindex *ifni; + char *vlan = NULL; + int sockfd, i, rc; + + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + + strlcpy(if_hwaddr.ifr_name, netdev, IFNAMSIZ); + ioctl(sockfd, SIOCGIFHWADDR, &if_hwaddr); + + if (if_hwaddr.ifr_hwaddr.sa_family != ARPHRD_ETHER) { + close(sockfd); + return NULL; + } + + ifni = if_nameindex(); + for (i = 0; ifni[i].if_index && ifni[i].if_name; i++) { + strlcpy(vlan_hwaddr.ifr_name, ifni[i].if_name, IFNAMSIZ); + ioctl(sockfd, SIOCGIFHWADDR, &vlan_hwaddr); + + if (vlan_hwaddr.ifr_hwaddr.sa_family != ARPHRD_ETHER) + continue; + + if (!memcmp(if_hwaddr.ifr_hwaddr.sa_data, vlan_hwaddr.ifr_hwaddr.sa_data, ETH_ALEN)) { + strlcpy(vlanrq.device1, ifni[i].if_name, IFNAMSIZ); + rc = ioctl(sockfd, SIOCGIFVLAN, &vlanrq); + if ((rc == 0) && (vlanrq.u.VID == vlan_id)) { + vlan = strdup(vlanrq.device1); + break; + } + } + } + if_freenameindex(ifni); + + close(sockfd); + return vlan; +} + +/** + * net_setup_netdev - bring up NIC + * @netdev: network device name + * @local: ip address for netdev + * @mask: net mask + * @gateway: gateway + * @remote_ip: target portal ip + * @needs_bringup: bool indicating if the netdev needs to be started + * + * Bring up required NIC and use routing + * to force iSCSI traffic through correct NIC. + */ +int net_setup_netdev(char *netdev, char *local_ip, char *mask, char *gateway, + char *vlan, char *remote_ip, int needs_bringup) +{ + struct sockaddr_in sk_ipaddr = { .sin_family = AF_INET }; + struct sockaddr_in sk_netmask = { .sin_family = AF_INET }; + struct sockaddr_in sk_hostmask = { .sin_family = AF_INET }; + struct sockaddr_in sk_gateway = { .sin_family = AF_INET }; + struct sockaddr_in sk_tgt_ipaddr = { .sin_family = AF_INET }; + struct rtentry rt; + struct ifreq ifr; + char *physdev = NULL; + int sock; + int ret; + int vlan_id; + + if (!strlen(netdev)) { + log_error("No netdev name in fw entry."); + return EINVAL; + } + + vlan_id = atoi(vlan); + + if (vlan_id != 0) { + physdev = netdev; + netdev = find_vlan_dev(physdev, vlan_id); + } + + if (vlan_id && !netdev) { + /* TODO: create vlan if not found */ + log_error("No matching vlan found for fw entry."); + return EINVAL; + } + + /* Create socket for making networking changes */ + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { + log_error("Could not open socket to manage network " + "(err %d - %s)", errno, strerror(errno)); + ret = errno; + goto done; + } + + /* Bring up NIC with correct address - unless it + * has already been handled (2 targets in IBFT may share one NIC) + */ + if (!inet_aton(local_ip, &sk_ipaddr.sin_addr)) { + log_error("Invalid or missing ipaddr in fw entry"); + ret = EINVAL; + goto done; + } + + if (!inet_aton(mask, &sk_netmask.sin_addr)) { + log_error("Invalid or missing netmask in fw entry"); + ret = EINVAL; + goto done; + } + + inet_aton("255.255.255.255", &sk_hostmask.sin_addr); + + if (!inet_aton(remote_ip, &sk_tgt_ipaddr.sin_addr)) { + log_error("Invalid or missing target ipaddr in fw entry"); + ret = EINVAL; + goto done; + } + + /* Only set IP/NM if this is a new interface */ + if (needs_bringup) { + + if (physdev) { + /* Bring up interface */ + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, physdev, IFNAMSIZ); + ifr.ifr_flags = IFF_UP | IFF_RUNNING; + if (ioctl(sock, SIOCSIFFLAGS, &ifr) < 0) { + log_error("Could not bring up netdev %s (err %d - %s)", + physdev, errno, strerror(errno)); + ret = errno; + goto done; + } + } + + /* Bring up interface */ + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, netdev, IFNAMSIZ); + ifr.ifr_flags = IFF_UP | IFF_RUNNING; + if (ioctl(sock, SIOCSIFFLAGS, &ifr) < 0) { + log_error("Could not bring up netdev %s (err %d - %s)", + netdev, errno, strerror(errno)); + ret = errno; + goto done; + } + /* Set IP address */ + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, netdev, IFNAMSIZ); + memcpy(&ifr.ifr_addr, &sk_ipaddr, sizeof(struct sockaddr)); + if (ioctl(sock, SIOCSIFADDR, &ifr) < 0) { + log_error("Could not set ip for %s (err %d - %s)", + netdev, errno, strerror(errno)); + ret = errno; + goto done; + } + + /* Set netmask */ + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, netdev, IFNAMSIZ); + memcpy(&ifr.ifr_addr, &sk_netmask, sizeof(struct sockaddr)); + if (ioctl(sock, SIOCSIFNETMASK, &ifr) < 0) { + log_error("Could not set ip for %s (err %d - %s)", + netdev, errno, strerror(errno)); + ret = errno; + goto done; + } + } + + /* Set static route to target via this interface */ + memset((char *) &rt, 0, sizeof(rt)); + memcpy(&rt.rt_dst, &sk_tgt_ipaddr, sizeof(sk_tgt_ipaddr)); + memcpy(&rt.rt_genmask, &sk_hostmask, sizeof(sk_hostmask)); + rt.rt_flags = RTF_UP | RTF_HOST; + rt.rt_dev = netdev; + + if ((sk_tgt_ipaddr.sin_addr.s_addr & sk_netmask.sin_addr.s_addr) == + (sk_ipaddr.sin_addr.s_addr & sk_netmask.sin_addr.s_addr)) { + /* Same subnet */ + if (ioctl(sock, SIOCADDRT, &rt) < 0) { + if (errno != EEXIST) { + log_error("Could not set ip for %s " + "(err %d - %s)", netdev, + errno, strerror(errno)); + ret = errno; + goto done; + } + } + } else { + /* Different subnet. Use gateway */ + rt.rt_flags |= RTF_GATEWAY; + if (!inet_aton(gateway, &sk_gateway.sin_addr)) { + log_error("Invalid or missing gateway for %s " + "(err %d - %s)", + netdev, errno, strerror(errno)); + ret = errno; + goto done; + } + memcpy(&rt.rt_gateway, &sk_gateway, sizeof(sk_gateway)); + if (ioctl(sock, SIOCADDRT, &rt) < 0) { + if (errno != EEXIST) { + log_error("Could not set gateway for %s " + "(err %d - %s)", netdev, + errno, strerror(errno)); + ret = errno; + goto done; + } + } + } + ret = 0; + +done: + if (sock >= 0) + close(sock); + if (vlan_id) + free(netdev); + return ret; +} + +/** + * net_ifup_netdev - bring up network interface + * @netdev: netdevice to bring up. + */ +int net_ifup_netdev(char *netdev) +{ + struct ifreq ifr; + int sock; + int ret = 0; + + if (!strlen(netdev)) { + log_error("No netdev name in fw entry."); + return EINVAL; + } + + /* Create socket for making networking changes */ + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { + log_error("Could not open socket to manage network " + "(err %d - %s)", errno, strerror(errno)); + return errno; + } + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, netdev, IFNAMSIZ); + if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) { + log_error("Could not bring up netdev %s (err %d - %s)", + netdev, errno, strerror(errno)); + ret = errno; + goto done; + } + + if (ifr.ifr_flags & IFF_UP) { + log_debug(3, "%s up", netdev); + goto done; + } + + log_debug(3, "bringing %s up", netdev); + + /* Bring up interface */ + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, netdev, IFNAMSIZ); + ifr.ifr_flags = IFF_UP; + if (ioctl(sock, SIOCSIFFLAGS, &ifr) < 0) { + log_error("Could not bring up netdev %s (err %d - %s)", + netdev, errno, strerror(errno)); + ret = errno; + goto done; + } +done: + close(sock); + return ret; +} + + diff --git a/usr/iscsi_netlink.h b/usr/iscsi_netlink.h new file mode 100644 index 0000000..25b41db --- /dev/null +++ b/usr/iscsi_netlink.h @@ -0,0 +1,33 @@ +/* + * iSCSI Netlink attr helpers + * + * Copyright (C) 2011 Red Hat, Inc. 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 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#ifndef ISCSI_NLA_H +#define ISCSI_NLA_H + +#include + +struct iovec; + +#define ISCSI_NLA_HDRLEN ((int) NLA_ALIGN(sizeof(struct nlattr))) +#define ISCSI_NLA_DATA(nla) ((void *)((char*)(nla) + ISCSI_NLA_HDRLEN)) +#define ISCSI_NLA_LEN(len) ((len) + NLA_ALIGN(ISCSI_NLA_HDRLEN)) +#define ISCSI_NLA_TOTAL_LEN(len) (NLA_ALIGN(ISCSI_NLA_LEN(len))) + +extern struct nlattr *iscsi_nla_alloc(uint16_t type, uint16_t len); + +#endif diff --git a/usr/iscsi_settings.h b/usr/iscsi_settings.h new file mode 100644 index 0000000..a4e54a9 --- /dev/null +++ b/usr/iscsi_settings.h @@ -0,0 +1,53 @@ +/* + * Default initiator settings. These may not be the same as + * in the RFC. See iscsi_proto.h for those. + */ +/* timeouts in seconds */ +#define DEF_LOGIN_TIMEO 30 +#define DEF_LOGOUT_TIMEO 15 +#define DEF_NOOP_OUT_INTERVAL 5 +#define DEF_NOOP_OUT_TIMEO 5 +#define DEF_REPLACEMENT_TIMEO 120 + +#define DEF_ABORT_TIMEO 15 +#define DEF_LU_RESET_TIMEO 30 +#define DEF_TGT_RESET_TIMEO 30 +#define DEF_HOST_RESET_TIMEO 60 + +/* session reopen max retries */ +#define DEF_SESSION_REOPEN_MAX 0 + +/* q depths */ +#define CMDS_MAX 128 +#define QUEUE_DEPTH 32 + +/* system */ +#define XMIT_THREAD_PRIORITY -20 + +/* interface */ +#define UNKNOWN_VALUE "" +#define DEFAULT_IFACENAME "default" +#define DEFAULT_NETDEV "default" +#define DEFAULT_IPADDRESS "default" +#define DEFAULT_HWADDRESS "default" +#define DEFAULT_TRANSPORT "tcp" + +#define PORTAL_GROUP_TAG_UNKNOWN -1 + +/* default window size */ +#define TCP_WINDOW_SIZE (512 * 1024) + +/* default iSCSI port number */ +#define ISCSI_DEFAULT_PORT 3260 + +/* data and segment lengths in bytes */ +#define DEF_INI_FIRST_BURST_LEN 262144 +#define DEF_INI_MAX_BURST_LEN 16776192 +#define DEF_INI_MAX_RECV_SEG_LEN 262144 +#define DEF_INI_DISC_MAX_RECV_SEG_LEN 32768 + +/* login retries */ +#define DEF_INITIAL_LOGIN_RETRIES_MAX 4 + +/* autoscan enabled */ +#define DEF_INITIAL_SCAN 1 diff --git a/usr/iscsi_sysfs.c b/usr/iscsi_sysfs.c new file mode 100644 index 0000000..418f51b --- /dev/null +++ b/usr/iscsi_sysfs.c @@ -0,0 +1,1992 @@ +/* + * iSCSI sysfs + * + * Copyright (C) 2006 Mike Christie + * Copyright (C) 2006 Red Hat, Inc. 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 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "initiator.h" +#include "transport.h" +#include "idbm.h" +#include "idbm_fields.h" +#include "version.h" +#include "iscsi_sysfs.h" +#include "sysdeps.h" +#include "iscsi_settings.h" +#include "iface.h" +#include "session_info.h" +#include "host.h" +#include "iscsi_err.h" +#include "flashnode.h" + +/* + * TODO: remove the _DIR defines and search for subsys dirs like + * is done in sysfs.c. + */ +#define ISCSI_TRANSPORT_DIR "/sys/class/iscsi_transport" +#define ISCSI_SESSION_DIR "/sys/class/iscsi_session" +#define ISCSI_HOST_DIR "/sys/class/iscsi_host" +#define ISCSI_FLASHNODE_DIR "/sys/bus/iscsi_flashnode/devices" + +#define ISCSI_SESSION_SUBSYS "iscsi_session" +#define ISCSI_CONN_SUBSYS "iscsi_connection" +#define ISCSI_HOST_SUBSYS "iscsi_host" +#define ISCSI_TRANSPORT_SUBSYS "iscsi_transport" +#define ISCSI_IFACE_SUBSYS "iscsi_iface" +#define ISCSI_FLASHNODE_SUBSYS "iscsi_flashnode" +#define SCSI_HOST_SUBSYS "scsi_host" +#define SCSI_SUBSYS "scsi" + +#define ISCSI_SESSION_ID "session%d" +#define ISCSI_CONN_ID "connection%d:0" +#define ISCSI_HOST_ID "host%d" +#define ISCSI_FLASHNODE_SESS "flashnode_sess-%d:%d" +#define ISCSI_FLASHNODE_CONN "flashnode_conn-%d:%d:0" + +/* + * TODO: make this into a real API and check inputs better and add doc. + */ + +static int num_transports; +LIST_HEAD(transports); + +void free_transports(void) +{ + struct iscsi_transport *t, *tmp; + + list_for_each_entry_safe(t, tmp, &transports, list) { + list_del(&t->list); + free(t); + } +} + +static int trans_filter(const struct dirent *dir) +{ + return strcmp(dir->d_name, ".") && strcmp(dir->d_name, ".."); +} + +static int read_transports(void) +{ + struct dirent **namelist; + int i, n, found; + struct iscsi_transport *t; + + log_debug(7, "in %s", __FUNCTION__); + + n = scandir(ISCSI_TRANSPORT_DIR, &namelist, trans_filter, + alphasort); + if (n < 0) { + log_error("Could not scan %s.", ISCSI_TRANSPORT_DIR); + return n; + } + + for (i = 0; i < n; i++) { + found = 0; + + list_for_each_entry(t, &transports, list) { + if (!strcmp(t->name, namelist[i]->d_name)) { + found = 1; + break; + } + } + + if (!found) { + /* copy new transport */ + t = malloc(sizeof(*t)); + if (!t) + continue; + log_debug(7, "Adding new transport %s", + namelist[i]->d_name); + + INIT_LIST_HEAD(&t->sessions); + INIT_LIST_HEAD(&t->list); + strlcpy(t->name, namelist[i]->d_name, + ISCSI_TRANSPORT_NAME_MAXLEN); + if (set_transport_template(t)) { + free(t); + return -1; + } + } else + log_debug(7, "Updating transport %s", + namelist[i]->d_name); + + if (sysfs_get_uint64(t->name, ISCSI_TRANSPORT_SUBSYS, + "handle", &t->handle)) { + if (list_empty(&t->list)) + free(t); + else + log_error("Could not update %s.", + t->name); + continue; + } + + if (sysfs_get_uint(t->name, ISCSI_TRANSPORT_SUBSYS, + "caps", &t->caps)) { + if (list_empty(&t->list)) + free(t); + else + log_error("Could not update %s.", + t->name); + continue; + } + /* + * tmp hack for qla4xx compat + */ + if (!strcmp(t->name, "qla4xxx")) { + t->caps |= CAP_DATA_PATH_OFFLOAD; + } + + if (list_empty(&t->list)) + list_add_tail(&t->list, &transports); + } + + for (i = 0; i < n; i++) + free(namelist[i]); + free(namelist); + num_transports = n; + + return 0; +} + +/* caller must check lengths */ +void iscsi_sysfs_get_auth_conf(int sid, struct iscsi_auth_config *conf) +{ + char id[NAME_SIZE]; + + memset(conf, 0, sizeof(*conf)); + snprintf(id, sizeof(id), ISCSI_SESSION_ID, sid); + + sysfs_get_str(id, ISCSI_SESSION_SUBSYS, "username", conf->username, + sizeof(conf->username)); + sysfs_get_str(id, ISCSI_SESSION_SUBSYS, "username_in", + conf->username_in, sizeof(conf->username_in)); + + sysfs_get_str(id, ISCSI_SESSION_SUBSYS, "password", + (char *)conf->password, sizeof(conf->password)); + if (strlen((char *)conf->password)) + conf->password_length = strlen((char *)conf->password); + + sysfs_get_str(id, ISCSI_SESSION_SUBSYS, "password_in", + (char *)conf->password_in, sizeof(conf->password_in)); + if (strlen((char *)conf->password_in)) + conf->password_in_length = strlen((char *)conf->password_in); +} + +/* called must check for -1=invalid value */ +void iscsi_sysfs_get_negotiated_conn_conf(int sid, + struct iscsi_conn_operational_config *conf) +{ + char id[NAME_SIZE]; + + memset(conf, 0, sizeof(*conf)); + snprintf(id, sizeof(id), ISCSI_CONN_ID, sid); + + sysfs_get_int(id, ISCSI_CONN_SUBSYS, "data_digest", &conf->DataDigest); + sysfs_get_int(id, ISCSI_CONN_SUBSYS, "header_digest", + &conf->HeaderDigest); + sysfs_get_int(id, ISCSI_CONN_SUBSYS, "max_xmit_dlength", + &conf->MaxXmitDataSegmentLength); + sysfs_get_int(id, ISCSI_CONN_SUBSYS, "max_recv_dlength", + &conf->MaxRecvDataSegmentLength); +} + +/* called must check for -1=invalid value */ +void iscsi_sysfs_get_negotiated_session_conf(int sid, + struct iscsi_session_operational_config *conf) +{ + char id[NAME_SIZE]; + + memset(conf, 0, sizeof(*conf)); + snprintf(id, sizeof(id), ISCSI_SESSION_ID, sid); + + sysfs_get_int(id, ISCSI_SESSION_SUBSYS, "data_pdu_in_order", + &conf->DataPDUInOrder); + sysfs_get_int(id, ISCSI_SESSION_SUBSYS, "data_seq_in_order", + &conf->DataSequenceInOrder); + sysfs_get_int(id, ISCSI_SESSION_SUBSYS, "erl", &conf->ERL); + sysfs_get_int(id, ISCSI_SESSION_SUBSYS, "first_burst_len", + &conf->FirstBurstLength); + sysfs_get_int(id, ISCSI_SESSION_SUBSYS, "max_burst_len", + &conf->MaxBurstLength); + sysfs_get_int(id, ISCSI_SESSION_SUBSYS, "immediate_data", + &conf->ImmediateData); + sysfs_get_int(id, ISCSI_SESSION_SUBSYS, "initial_r2t", + &conf->InitialR2T); + sysfs_get_int(id, ISCSI_SESSION_SUBSYS, "max_outstanding_r2t", + &conf->MaxOutstandingR2T); +} + +/* + * iscsi_sysfs_session_user_created - return if session was setup by userspace + * @sid: id of session to test + * + * Returns -1 if we could not tell due to kernel not supporting the + * feature. 0 is returned if kernel created it. And 1 is returned + * if userspace created it. + */ +int iscsi_sysfs_session_user_created(int sid) +{ + char id[NAME_SIZE]; + pid_t pid; + + snprintf(id, sizeof(id), ISCSI_SESSION_ID, sid); + if (sysfs_get_int(id, ISCSI_SESSION_SUBSYS, "creator", &pid)) + return -1; + + if (pid == -1) + return 0; + else + return 1; +} + +uint32_t iscsi_sysfs_get_host_no_from_sid(uint32_t sid, int *err) +{ + struct sysfs_device *session_dev, *host_dev; + char devpath[PATH_SIZE]; + char id[NAME_SIZE]; + + *err = 0; + snprintf(id, sizeof(id), "session%u", sid); + if (!sysfs_lookup_devpath_by_subsys_id(devpath, sizeof(devpath), + ISCSI_SESSION_SUBSYS, id)) { + log_error("Could not lookup devpath for %s. Possible sysfs " + "incompatibility.", id); + *err = ISCSI_ERR_SYSFS_LOOKUP; + return 0; + } + + session_dev = sysfs_device_get(devpath); + if (!session_dev) { + log_error("Could not get dev for %s. Possible sysfs " + "incompatibility.", id); + *err = ISCSI_ERR_SYSFS_LOOKUP; + return 0; + } + + /* + * 2.6.27 moved from scsi_host to scsi for the subsys when + * sysfs compat is not on. + */ + host_dev = sysfs_device_get_parent_with_subsystem(session_dev, + SCSI_SUBSYS); + if (!host_dev) { + struct sysfs_device *dev_parent; + + dev_parent = sysfs_device_get_parent(session_dev); + while (dev_parent != NULL) { + if (strncmp(dev_parent->kernel, "host", 4) == 0) { + host_dev = dev_parent; + break; + } + dev_parent = sysfs_device_get_parent(dev_parent); + } + + if (!host_dev) { + log_error("Could not get host dev for %s. Possible " + "sysfs incompatibility.", id); + *err = ISCSI_ERR_SYSFS_LOOKUP; + return 0; + } + } + + return atol(host_dev->kernel_number); +} + +/* TODO: merge and make macro */ +static int __get_host_no_from_netdev(void *data, struct host_info *info) +{ + struct host_info *ret_info = data; + + if (!strcmp(ret_info->iface.netdev, info->iface.netdev)) { + ret_info->host_no = info->host_no; + return 1; + } + return 0; +} + +static uint32_t get_host_no_from_netdev(char *netdev, int *rc) +{ + uint32_t host_no = -1; + struct host_info *info; + int nr_found, local_rc; + + *rc = 0; + + info = calloc(1, sizeof(*info)); + if (!info) { + *rc = ISCSI_ERR_NOMEM; + return -1; + } + strcpy(info->iface.netdev, netdev); + + local_rc = iscsi_sysfs_for_each_host(info, &nr_found, + __get_host_no_from_netdev); + if (local_rc == 1) + host_no = info->host_no; + else + *rc = ISCSI_ERR_HOST_NOT_FOUND; + free(info); + return host_no; +} + +static int __get_host_no_from_hwaddress(void *data, struct host_info *info) +{ + struct host_info *ret_info = data; + + if (!strcasecmp(ret_info->iface.hwaddress, info->iface.hwaddress)) { + ret_info->host_no = info->host_no; + return 1; + } + return 0; +} + +uint32_t iscsi_sysfs_get_host_no_from_hwaddress(char *hwaddress, int *rc) +{ + uint32_t host_no = -1; + struct host_info *info; + int nr_found, local_rc; + + *rc = 0; + + info = calloc(1, sizeof(*info)); + if (!info) { + *rc = ISCSI_ERR_NOMEM; + return -1; + } + strcpy(info->iface.hwaddress, hwaddress); + + local_rc = iscsi_sysfs_for_each_host(info, &nr_found, + __get_host_no_from_hwaddress); + if (local_rc == 1) + host_no = info->host_no; + else + *rc = ISCSI_ERR_HOST_NOT_FOUND; + free(info); + return host_no; +} + +static int __get_host_no_from_ipaddress(void *data, struct host_info *info) +{ + struct host_info *ret_info = data; + + if (!strcmp(ret_info->iface.ipaddress, info->iface.ipaddress)) { + ret_info->host_no = info->host_no; + return 1; + } + return 0; +} + +static uint32_t get_host_no_from_ipaddress(char *address, int *rc) +{ + uint32_t host_no = -1; + struct host_info *info; + int nr_found; + int local_rc; + + *rc = 0; + + info = calloc(1, sizeof(*info)); + if (!info) { + *rc = ISCSI_ERR_NOMEM; + return -1; + } + strcpy(info->iface.ipaddress, address); + + local_rc = iscsi_sysfs_for_each_host(info, &nr_found, + __get_host_no_from_ipaddress); + if (local_rc == 1) + host_no = info->host_no; + else + *rc = ISCSI_ERR_HOST_NOT_FOUND; + free(info); + return host_no; +} + +uint32_t iscsi_sysfs_get_host_no_from_hwinfo(struct iface_rec *iface, int *rc) +{ + int tmp_rc; + uint32_t host_no = -1; + + if (strlen(iface->hwaddress) && + strcasecmp(iface->hwaddress, DEFAULT_HWADDRESS)) + host_no = iscsi_sysfs_get_host_no_from_hwaddress( + iface->hwaddress, &tmp_rc); + else if (strlen(iface->netdev) && + strcasecmp(iface->netdev, DEFAULT_NETDEV)) + host_no = get_host_no_from_netdev(iface->netdev, &tmp_rc); + else if (strlen(iface->ipaddress) && + strcasecmp(iface->ipaddress, DEFAULT_IPADDRESS)) + host_no = get_host_no_from_ipaddress(iface->ipaddress, &tmp_rc); + else + tmp_rc = ISCSI_ERR_INVAL; + + *rc = tmp_rc; + return host_no; +} + +/* + * Read the flash node attributes based on host and flash node index. + */ +int iscsi_sysfs_get_flashnode_info(struct flashnode_rec *fnode, + uint32_t host_no, + uint32_t flashnode_idx) +{ + char sess_id[NAME_SIZE] = {'\0'}; + char conn_id[NAME_SIZE] = {'\0'}; + char fnode_path[PATH_SIZE] = {'\0'}; + struct iscsi_transport *t; + int ret = 0; + + t = iscsi_sysfs_get_transport_by_hba(host_no); + if (!t) + log_debug(7, "could not get transport name for host%d", + host_no); + else + strlcpy(fnode->transport_name, t->name, + ISCSI_TRANSPORT_NAME_MAXLEN); + + snprintf(sess_id, sizeof(sess_id), ISCSI_FLASHNODE_SESS, host_no, + flashnode_idx); + + snprintf(fnode_path, sizeof(fnode_path), ISCSI_FLASHNODE_DIR"/%s", + sess_id); + if (access(fnode_path, F_OK) != 0) + return errno; + + snprintf(conn_id, sizeof(conn_id), ISCSI_FLASHNODE_CONN, host_no, + flashnode_idx); + + snprintf(fnode_path, sizeof(fnode_path), ISCSI_FLASHNODE_DIR"/%s", + conn_id); + if (access(fnode_path, F_OK) != 0) + return errno; + + + sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "is_fw_assigned_ipv6", + &((fnode->conn[0]).is_fw_assigned_ipv6)); + sysfs_get_str(sess_id, ISCSI_FLASHNODE_SUBSYS, "portal_type", + (fnode->sess).portal_type, + sizeof((fnode->sess).portal_type)); + sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "auto_snd_tgt_disable", + &((fnode->sess).auto_snd_tgt_disable)); + sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "discovery_session", + &((fnode->sess).discovery_session)); + sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "entry_enable", + &((fnode->sess).entry_enable)); + sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "header_digest", + &((fnode->conn[0]).header_digest_en)); + sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "data_digest", + &((fnode->conn[0]).data_digest_en)); + sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "immediate_data", + &((fnode->sess).immediate_data)); + sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "initial_r2t", + &((fnode->sess).initial_r2t)); + sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "data_seq_in_order", + &((fnode->sess).data_seq_in_order)); + sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "data_pdu_in_order", + &((fnode->sess).data_pdu_in_order)); + sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "chap_auth", + &((fnode->sess).chap_auth_en)); + sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "snack_req", + &((fnode->conn[0]).snack_req_en)); + sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "discovery_logout", + &((fnode->sess).discovery_logout_en)); + sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "bidi_chap", + &((fnode->sess).bidi_chap_en)); + sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, + "discovery_auth_optional", + &((fnode->sess).discovery_auth_optional)); + sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "erl", + &((fnode->sess).erl)); + sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "tcp_timestamp_stat", + &((fnode->conn[0]).tcp_timestamp_stat)); + sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "tcp_nagle_disable", + &((fnode->conn[0]).tcp_nagle_disable)); + sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "tcp_wsf_disable", + &((fnode->conn[0]).tcp_wsf_disable)); + sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "tcp_timer_scale", + &((fnode->conn[0]).tcp_timer_scale)); + sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "tcp_timestamp_enable", + &((fnode->conn[0]).tcp_timestamp_en)); + sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "fragment_disable", + &((fnode->conn[0]).fragment_disable)); + sysfs_get_uint(conn_id, ISCSI_FLASHNODE_SUBSYS, "max_recv_dlength", + &((fnode->conn[0]).max_recv_dlength)); + sysfs_get_uint(conn_id, ISCSI_FLASHNODE_SUBSYS, "max_xmit_dlength", + &((fnode->conn[0]).max_xmit_dlength)); + sysfs_get_uint(sess_id, ISCSI_FLASHNODE_SUBSYS, "first_burst_len", + &((fnode->sess).first_burst_len)); + sysfs_get_uint16(sess_id, ISCSI_FLASHNODE_SUBSYS, "def_time2wait", + &((fnode->sess).def_time2wait)); + sysfs_get_uint16(sess_id, ISCSI_FLASHNODE_SUBSYS, "def_time2retain", + &((fnode->sess).def_time2retain)); + sysfs_get_uint16(sess_id, ISCSI_FLASHNODE_SUBSYS, "max_outstanding_r2t", + &((fnode->sess).max_outstanding_r2t)); + sysfs_get_uint16(conn_id, ISCSI_FLASHNODE_SUBSYS, "keepalive_tmo", + &((fnode->conn[0]).keepalive_tmo)); + sysfs_get_str(sess_id, ISCSI_FLASHNODE_SUBSYS, "isid", + (fnode->sess).isid, sizeof((fnode->sess).isid)); + sysfs_get_uint16(sess_id, ISCSI_FLASHNODE_SUBSYS, "tsid", + &((fnode->sess).tsid)); + sysfs_get_uint16(conn_id, ISCSI_FLASHNODE_SUBSYS, "port", + &((fnode->conn[0]).port)); + sysfs_get_uint(sess_id, ISCSI_FLASHNODE_SUBSYS, "max_burst_len", + &((fnode->sess).max_burst_len)); + sysfs_get_uint16(sess_id, ISCSI_FLASHNODE_SUBSYS, "def_taskmgmt_tmo", + &((fnode->sess).def_taskmgmt_tmo)); + sysfs_get_str(sess_id, ISCSI_FLASHNODE_SUBSYS, "targetalias", + (fnode->sess).targetalias, + sizeof((fnode->sess).targetalias)); + sysfs_get_str(conn_id, ISCSI_FLASHNODE_SUBSYS, "ipaddress", + (fnode->conn[0]).ipaddress, + sizeof((fnode->conn[0]).ipaddress)); + sysfs_get_str(conn_id, ISCSI_FLASHNODE_SUBSYS, "redirect_ipaddr", + (fnode->conn[0]).redirect_ipaddr, + sizeof((fnode->conn[0]).redirect_ipaddr)); + sysfs_get_uint(conn_id, ISCSI_FLASHNODE_SUBSYS, "max_segment_size", + &((fnode->conn[0]).max_segment_size)); + sysfs_get_uint16(conn_id, ISCSI_FLASHNODE_SUBSYS, "local_port", + &((fnode->conn[0]).local_port)); + sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "ipv4_tos", + &((fnode->conn[0]).ipv4_tos)); + sysfs_get_uint8(conn_id, ISCSI_FLASHNODE_SUBSYS, "ipv6_traffic_class", + &((fnode->conn[0]).ipv6_traffic_class)); + sysfs_get_uint16(conn_id, ISCSI_FLASHNODE_SUBSYS, "ipv6_flow_label", + &((fnode->conn[0]).ipv6_flow_lbl)); + sysfs_get_str(sess_id, ISCSI_FLASHNODE_SUBSYS, "targetname", + (fnode->sess).targetname, + sizeof((fnode->sess).targetname)); + sysfs_get_str(conn_id, ISCSI_FLASHNODE_SUBSYS, "link_local_ipv6", + (fnode->conn[0]).link_local_ipv6, + sizeof((fnode->conn[0]).link_local_ipv6)); + sysfs_get_uint16(sess_id, ISCSI_FLASHNODE_SUBSYS, + "discovery_parent_idx", + &((fnode->sess).discovery_parent_idx)); + sysfs_get_str(sess_id, ISCSI_FLASHNODE_SUBSYS, + "discovery_parent_type", + (fnode->sess).discovery_parent_type, + sizeof((fnode->sess).discovery_parent_type)); + sysfs_get_uint16(sess_id, ISCSI_FLASHNODE_SUBSYS, "tpgt", + &((fnode->sess).tpgt)); + sysfs_get_uint(conn_id, ISCSI_FLASHNODE_SUBSYS, "tcp_xmit_wsf", + &((fnode->conn[0]).tcp_xmit_wsf)); + sysfs_get_uint(conn_id, ISCSI_FLASHNODE_SUBSYS, "tcp_recv_wsf", + &((fnode->conn[0]).tcp_recv_wsf)); + sysfs_get_uint16(sess_id, ISCSI_FLASHNODE_SUBSYS, "chap_out_idx", + &((fnode->sess).chap_out_idx)); + sysfs_get_uint16(sess_id, ISCSI_FLASHNODE_SUBSYS, "chap_in_idx", + &((fnode->sess).chap_in_idx)); + sysfs_get_str(sess_id, ISCSI_FLASHNODE_SUBSYS, "username", + (fnode->sess).username, sizeof((fnode->sess).username)); + sysfs_get_str(sess_id, ISCSI_FLASHNODE_SUBSYS, "username_in", + (fnode->sess).username_in, + sizeof((fnode->sess).username_in)); + sysfs_get_str(sess_id, ISCSI_FLASHNODE_SUBSYS, "password", + (fnode->sess).password, sizeof((fnode->sess).password)); + sysfs_get_str(sess_id, ISCSI_FLASHNODE_SUBSYS, "password_in", + (fnode->sess).password_in, + sizeof((fnode->sess).password_in)); + sysfs_get_uint(conn_id, ISCSI_FLASHNODE_SUBSYS, "statsn", + &((fnode->conn[0]).stat_sn)); + sysfs_get_uint(conn_id, ISCSI_FLASHNODE_SUBSYS, "exp_statsn", + &((fnode->conn[0]).exp_stat_sn)); + sysfs_get_uint8(sess_id, ISCSI_FLASHNODE_SUBSYS, "is_boot_target", + &((fnode->sess).is_boot_target)); + return ret; +} + +/* + * For each flash node of the given host, perform operation specified in fn. + */ +int iscsi_sysfs_for_each_flashnode(void *data, uint32_t host_no, int *nr_found, + iscsi_sysfs_flashnode_op_fn *fn) +{ + struct dirent **namelist; + int rc = 0, i, n; + struct flashnode_rec *fnode; + uint32_t flashnode_idx; + uint32_t hostno; + + fnode = malloc(sizeof(*fnode)); + if (!fnode) + return ISCSI_ERR_NOMEM; + + n = scandir(ISCSI_FLASHNODE_DIR, &namelist, trans_filter, alphasort); + if (n <= 0) + goto free_fnode; + + for (i = 0; i < n; i++) { + memset(fnode, 0, sizeof(*fnode)); + + if (!strncmp(namelist[i]->d_name, "flashnode_conn", + strlen("flashnode_conn"))) + continue; + + if (sscanf(namelist[i]->d_name, ISCSI_FLASHNODE_SESS, + &hostno, &flashnode_idx) != 2) { + log_error("Invalid iscsi target dir: %s", + namelist[i]->d_name); + break; + } + + if (host_no != hostno) + continue; + + rc = iscsi_sysfs_get_flashnode_info(fnode, host_no, + flashnode_idx); + if (rc) + break; + + rc = fn(data, fnode, host_no, flashnode_idx); + if (rc != 0) + break; + (*nr_found)++; + } + + for (i = 0; i < n; i++) + free(namelist[i]); + free(namelist); + +free_fnode: + free(fnode); + return rc; +} + +static int iscsi_sysfs_read_boot(struct iface_rec *iface, char *session) +{ + char boot_root[BOOT_NAME_MAXLEN], boot_nic[BOOT_NAME_MAXLEN]; + char boot_name[BOOT_NAME_MAXLEN], boot_content[BOOT_NAME_MAXLEN]; + + /* Extract boot info */ + strlcpy(boot_name, "boot_target", sizeof(boot_name)); + if (sysfs_get_str(session, ISCSI_SESSION_SUBSYS, boot_name, + boot_content, BOOT_NAME_MAXLEN)) + return -1; + strlcpy(boot_name, "boot_nic", sizeof(boot_name)); + if (sysfs_get_str(session, ISCSI_SESSION_SUBSYS, boot_name, boot_nic, + BOOT_NAME_MAXLEN)) + return -1; + strlcpy(boot_name, "boot_root", sizeof(boot_name)); + if (sysfs_get_str(session, ISCSI_SESSION_SUBSYS, boot_name, boot_root, + BOOT_NAME_MAXLEN)) + return -1; + + /* If all boot_root/boot_target/boot_nic exist, then extract the + info from the boot nic */ + if (sysfs_get_str(boot_nic, boot_root, "vlan", boot_content, + BOOT_NAME_MAXLEN)) + log_debug(5, "could not read %s/%s/vlan", boot_root, boot_nic); + else + iface->vlan_id = atoi(boot_content); + + if (sysfs_get_str(boot_nic, boot_root, "subnet-mask", + iface->subnet_mask, NI_MAXHOST)) + log_debug(5, "could not read %s/%s/subnet", boot_root, + boot_nic); + + if (sysfs_get_str(boot_nic, boot_root, "gateway", + iface->gateway, NI_MAXHOST)) + log_debug(5, "could not read %s/%s/gateway", boot_root, + boot_nic); + + log_debug(5, "sysfs read boot returns %s/%s/ vlan = %d subnet = %s", + boot_root, boot_nic, iface->vlan_id, iface->subnet_mask); + return 0; +} + +/* + * Read in iface settings based on host and session values. If + * session is not passed in, then the ifacename will not be set. And + * if the session is not passed in then iname will only be set for + * qla4xxx. + */ +static int iscsi_sysfs_read_iface(struct iface_rec *iface, int host_no, + char *session, char *iface_kern_id) +{ + uint32_t tmp_host_no, iface_num; + char host_id[NAME_SIZE]; + struct iscsi_transport *t; + int ret, iface_type; + + t = iscsi_sysfs_get_transport_by_hba(host_no); + if (!t) + log_debug(7, "could not get transport name for host%d", + host_no); + else + strcpy(iface->transport_name, t->name); + + snprintf(host_id, sizeof(host_id), ISCSI_HOST_ID, host_no); + /* + * backward compat + * If we cannot get the address we assume we are doing the old + * style and use default. + */ + ret = sysfs_get_str(host_id, ISCSI_HOST_SUBSYS, "hwaddress", + iface->hwaddress, sizeof(iface->hwaddress)); + if (ret) + log_debug(7, "could not read hwaddress for host%d", host_no); + + if (iface_kern_id) + ret = sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, + "ipaddress", + iface->ipaddress, sizeof(iface->ipaddress)); + else + /* if not found just print out default */ + ret = sysfs_get_str(host_id, ISCSI_HOST_SUBSYS, "ipaddress", + iface->ipaddress, sizeof(iface->ipaddress)); + if (ret) + log_debug(7, "could not read local address for host%d", + host_no); + + /* if not found just print out default */ + ret = sysfs_get_str(host_id, ISCSI_HOST_SUBSYS, "netdev", + iface->netdev, sizeof(iface->netdev)); + if (ret) + log_debug(7, "could not read netdev for host%d", host_no); + + /* + * For drivers like qla4xxx we can only set the iname at the + * host level because we cannot create different initiator ports + * (cannot set isid either). The LLD also exports the iname at the + * hba level so apps can see it, but we no longer set the iname for + * each iscsid controlled host since bnx2i cxgbi can support multiple + * initiator names and of course software iscsi can support anything. + */ + ret = 1; + memset(iface->iname, 0, sizeof(iface->iname)); + if (session) { + ret = sysfs_get_str(session, ISCSI_SESSION_SUBSYS, + "initiatorname", + iface->iname, sizeof(iface->iname)); + /* + * qlaxxx will not set this at the session level so we + * always drop down for it. + * + * And. + * + * For older kernels/tools (2.6.26 and below and 2.0.870) + * we will not have a session level initiator name, so + * we will drop down. + */ + } + + if (ret) { + ret = sysfs_get_str(host_id, ISCSI_HOST_SUBSYS, "initiatorname", + iface->iname, sizeof(iface->iname)); + if (ret) + /* + * default iname is picked up later from + * initiatorname.iscsi if software/partial-offload. + * + * TODO: we should make it easier to get the + * global iname so we can just fill it in here. + */ + log_debug(7, "Could not read initiatorname for " + "host%d", host_no); + /* optional so do not return error */ + ret = 0; + } + + sysfs_get_str(host_id, ISCSI_HOST_SUBSYS, "port_state", + iface->port_state, sizeof(iface->port_state)); + + sysfs_get_str(host_id, ISCSI_HOST_SUBSYS, "port_speed", + iface->port_speed, sizeof(iface->port_speed)); + + /* + * this is on the session, because we support multiple bindings + * per device. + */ + memset(iface->name, 0, sizeof(iface->name)); + if (session) { + /* + * this was added after 2.0.869 so we could be doing iscsi_tcp + * session binding, but there may not be an ifacename set + * if binding is not used. + */ + ret = sysfs_get_str(session, ISCSI_SESSION_SUBSYS, "ifacename", + iface->name, sizeof(iface->name)); + if (ret) { + log_debug(7, "could not read iface name for " + "session %s", session); + /* + * if the ifacename file is not there then we are + * using a older kernel and can try to find the + * binding by the net info which was used on these + * older kernels. + */ + if (iface_get_by_net_binding(iface, iface)) + log_debug(7, "Could not find iface for session " + "bound to:" iface_fmt "", + iface_str(iface)); + } + } + + if (session && t->template->use_boot_info) + iscsi_sysfs_read_boot(iface, session); + + if (!iface_kern_id) + goto done; + + strlcpy(iface->name, iface_kern_id, sizeof(iface->name)); + + if (!strncmp(iface_kern_id, "ipv4", 4)) { + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "bootproto", + iface->bootproto, sizeof(iface->bootproto)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "gateway", + iface->gateway, sizeof(iface->gateway)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "subnet", + iface->subnet_mask, sizeof(iface->subnet_mask)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, + "dhcp_alt_client_id_en", + iface->dhcp_alt_client_id_state, + sizeof(iface->dhcp_alt_client_id_state)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, + "dhcp_alt_client_id", + iface->dhcp_alt_client_id, + sizeof(iface->dhcp_alt_client_id)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, + "dhcp_dns_address_en", + iface->dhcp_dns, sizeof(iface->dhcp_dns)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, + "dhcp_learn_iqn_en", + iface->dhcp_learn_iqn, + sizeof(iface->dhcp_learn_iqn)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, + "dhcp_req_vendor_id_en", + iface->dhcp_req_vendor_id_state, + sizeof(iface->dhcp_req_vendor_id_state)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, + "dhcp_use_vendor_id_en", + iface->dhcp_vendor_id_state, + sizeof(iface->dhcp_vendor_id_state)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, + "dhcp_vendor_id", + iface->dhcp_vendor_id, + sizeof(iface->dhcp_vendor_id)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, + "dhcp_slp_da_info_en", + iface->dhcp_slp_da, sizeof(iface->dhcp_slp_da)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, + "fragment_disable", + iface->fragmentation, + sizeof(iface->fragmentation)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, + "grat_arp_en", + iface->gratuitous_arp, + sizeof(iface->gratuitous_arp)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, + "incoming_forwarding_en", + iface->incoming_forwarding, + sizeof(iface->incoming_forwarding)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, + "tos_en", + iface->tos_state, sizeof(iface->tos_state)); + + if (sysfs_get_uint8(iface_kern_id, ISCSI_IFACE_SUBSYS, + "tos", &iface->tos)) + iface->tos = 0; + + if (sysfs_get_uint8(iface_kern_id, ISCSI_IFACE_SUBSYS, + "ttl", &iface->ttl)) + iface->ttl = 0; + } else { + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, + "ipaddr_autocfg", + iface->ipv6_autocfg, sizeof(iface->ipv6_autocfg)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, + "link_local_addr", iface->ipv6_linklocal, + sizeof(iface->ipv6_linklocal)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, + "link_local_autocfg", iface->linklocal_autocfg, + sizeof(iface->linklocal_autocfg)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "router_addr", + iface->ipv6_router, + sizeof(iface->ipv6_router)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "router_state", + iface->router_autocfg, + sizeof(iface->router_autocfg)); + + if (sysfs_get_uint8(iface_kern_id, ISCSI_IFACE_SUBSYS, + "dup_addr_detect_cnt", + &iface->dup_addr_detect_cnt)) + iface->dup_addr_detect_cnt = 0; + + if (sysfs_get_uint(iface_kern_id, ISCSI_IFACE_SUBSYS, + "flow_label", &iface->flow_label)) + iface->flow_label = 0; + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, + "grat_neighbor_adv_en", + iface->gratuitous_neighbor_adv, + sizeof(iface->gratuitous_neighbor_adv)); + + if (sysfs_get_uint8(iface_kern_id, ISCSI_IFACE_SUBSYS, + "hop_limit", &iface->hop_limit)) + iface->hop_limit = 0; + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "mld_en", + iface->mld, sizeof(iface->mld)); + + if (sysfs_get_uint(iface_kern_id, ISCSI_IFACE_SUBSYS, + "nd_reachable_tmo", + &iface->nd_reachable_tmo)) + iface->nd_reachable_tmo = 0; + + if (sysfs_get_uint(iface_kern_id, ISCSI_IFACE_SUBSYS, + "nd_rexmit_time", &iface->nd_rexmit_time)) + iface->nd_rexmit_time = 0; + + if (sysfs_get_uint(iface_kern_id, ISCSI_IFACE_SUBSYS, + "nd_stale_tmo", &iface->nd_stale_tmo)) + iface->nd_stale_tmo = 0; + + if (sysfs_get_uint(iface_kern_id, ISCSI_IFACE_SUBSYS, + "router_adv_link_mtu", + &iface->router_adv_link_mtu)) + iface->router_adv_link_mtu = 0; + + if (sysfs_get_uint(iface_kern_id, ISCSI_IFACE_SUBSYS, + "traffic_class", &iface->traffic_class)) + iface->traffic_class = 0; + } + + if (sysfs_get_uint16(iface_kern_id, ISCSI_IFACE_SUBSYS, "port", + &iface->port)) + iface->port = 0; + if (sysfs_get_uint16(iface_kern_id, ISCSI_IFACE_SUBSYS, "mtu", + &iface->mtu)) + iface->mtu = 0; + if (sysfs_get_uint16(iface_kern_id, ISCSI_IFACE_SUBSYS, "vlan_id", + &iface->vlan_id)) + iface->vlan_id = UINT16_MAX; + + if (sysfs_get_uint8(iface_kern_id, ISCSI_IFACE_SUBSYS, "vlan_priority", + &iface->vlan_priority)) + iface->vlan_priority = UINT8_MAX; + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "vlan_enabled", + iface->vlan_state, sizeof(iface->vlan_state)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "enabled", + iface->state, sizeof(iface->state)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "delayed_ack_en", + iface->delayed_ack, sizeof(iface->delayed_ack)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "tcp_nagle_disable", + iface->nagle, sizeof(iface->nagle)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "tcp_wsf_disable", + iface->tcp_wsf_state, sizeof(iface->tcp_wsf_state)); + + if (sysfs_get_uint8(iface_kern_id, ISCSI_IFACE_SUBSYS, "tcp_wsf", + &iface->tcp_wsf)) + iface->tcp_wsf = 0; + + if (sysfs_get_uint8(iface_kern_id, ISCSI_IFACE_SUBSYS, + "tcp_timer_scale", &iface->tcp_timer_scale)) + iface->tcp_timer_scale = 0; + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "tcp_timestamp_en", + iface->tcp_timestamp, sizeof(iface->tcp_timestamp)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "redirect_en", + iface->redirect, sizeof(iface->redirect)); + + if (sysfs_get_uint16(iface_kern_id, ISCSI_IFACE_SUBSYS, + "def_taskmgmt_tmo", &iface->def_task_mgmt_tmo)) + iface->def_task_mgmt_tmo = 0; + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "header_digest", + iface->header_digest, sizeof(iface->header_digest)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "data_digest", + iface->data_digest, sizeof(iface->data_digest)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "immediate_data", + iface->immediate_data, sizeof(iface->immediate_data)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "initial_r2t", + iface->initial_r2t, sizeof(iface->initial_r2t)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "data_seq_in_order", + iface->data_seq_inorder, sizeof(iface->data_seq_inorder)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "data_pdu_in_order", + iface->data_pdu_inorder, sizeof(iface->data_pdu_inorder)); + + if (sysfs_get_uint8(iface_kern_id, ISCSI_IFACE_SUBSYS, "erl", + &iface->erl)) + iface->erl = 0; + + if (sysfs_get_uint(iface_kern_id, ISCSI_IFACE_SUBSYS, + "max_recv_dlength", &iface->max_recv_dlength)) + iface->max_recv_dlength = 0; + + if (sysfs_get_uint(iface_kern_id, ISCSI_IFACE_SUBSYS, + "first_burst_len", &iface->first_burst_len)) + iface->first_burst_len = 0; + + if (sysfs_get_uint16(iface_kern_id, ISCSI_IFACE_SUBSYS, + "max_outstanding_r2t", &iface->max_out_r2t)) + iface->max_out_r2t = 0; + + if (sysfs_get_uint(iface_kern_id, ISCSI_IFACE_SUBSYS, + "max_burst_len", &iface->max_burst_len)) + iface->max_burst_len = 0; + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "chap_auth", + iface->chap_auth, sizeof(iface->chap_auth)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "bidi_chap", + iface->bidi_chap, sizeof(iface->bidi_chap)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "strict_login_comp_en", + iface->strict_login_comp, + sizeof(iface->strict_login_comp)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, + "discovery_auth_optional", + iface->discovery_auth, sizeof(iface->discovery_auth)); + + sysfs_get_str(iface_kern_id, ISCSI_IFACE_SUBSYS, "discovery_logout", + iface->discovery_logout, sizeof(iface->discovery_logout)); + + if (sscanf(iface_kern_id, "ipv%d-iface-%u-%u", &iface_type, + &tmp_host_no, &iface_num) == 3) + iface->iface_num = iface_num; + +done: + if (ret) + return ISCSI_ERR_SYSFS_LOOKUP; + else + return 0; +} + +int iscsi_sysfs_get_hostinfo_by_host_no(struct host_info *hinfo) +{ + return iscsi_sysfs_read_iface(&hinfo->iface, hinfo->host_no, NULL, + NULL); +} + +int iscsi_sysfs_for_each_host(void *data, int *nr_found, + iscsi_sysfs_host_op_fn *fn) +{ + struct dirent **namelist; + int rc = 0, i, n; + struct host_info *info; + + info = malloc(sizeof(*info)); + if (!info) + return ISCSI_ERR_NOMEM; + + n = scandir(ISCSI_HOST_DIR, &namelist, trans_filter, + alphasort); + if (n <= 0) + goto free_info; + + for (i = 0; i < n; i++) { + memset(info, 0, sizeof(*info)); + if (sscanf(namelist[i]->d_name, "host%u", &info->host_no) != + 1) { + log_error("Invalid iscsi host dir: %s", + namelist[i]->d_name); + break; + } + + iscsi_sysfs_get_hostinfo_by_host_no(info); + rc = fn(data, info); + if (rc != 0) + break; + (*nr_found)++; + } + + for (i = 0; i < n; i++) + free(namelist[i]); + free(namelist); + +free_info: + free(info); + return rc; +} + +int iscsi_sysfs_for_each_iface_on_host(void *data, uint32_t host_no, + int *nr_found, + iscsi_sysfs_iface_op_fn *fn) +{ + struct dirent **namelist; + int rc = 0, i, n; + struct iface_rec iface; + char devpath[PATH_SIZE]; + char sysfs_dev_iscsi_iface_path[PATH_SIZE]; + char id[NAME_SIZE]; + + snprintf(id, sizeof(id), "host%u", host_no); + if (!sysfs_lookup_devpath_by_subsys_id(devpath, sizeof(devpath), + SCSI_SUBSYS, id)) { + log_error("Could not look up host's ifaces via scsi bus."); + return ISCSI_ERR_SYSFS_LOOKUP; + } + + sprintf(sysfs_dev_iscsi_iface_path, "/sys"); + strlcat(sysfs_dev_iscsi_iface_path, devpath, sizeof(sysfs_dev_iscsi_iface_path)); + strlcat(sysfs_dev_iscsi_iface_path, "/iscsi_iface", sizeof(sysfs_dev_iscsi_iface_path)); + + n = scandir(sysfs_dev_iscsi_iface_path, &namelist, trans_filter, alphasort); + if (n <= 0) + /* older kernels or some drivers will not have ifaces */ + return 0; + + for (i = 0; i < n; i++) { + memset(&iface, 0, sizeof(iface)); + + iscsi_sysfs_read_iface(&iface, host_no, NULL, + namelist[i]->d_name); + rc = fn(data, &iface); + if (rc != 0) + break; + (*nr_found)++; + } + + for (i = 0; i < n; i++) + free(namelist[i]); + free(namelist); + return rc; +} + +/** + * sysfs_session_has_leadconn - checks if session has lead conn in kernel + * @sid: session id + * + * return 1 if session has lead conn and 0 if not. + */ +int iscsi_sysfs_session_has_leadconn(uint32_t sid) +{ + char devpath[PATH_SIZE]; + char id[NAME_SIZE]; + + snprintf(id, sizeof(id), "connection%u:0", sid); + return sysfs_lookup_devpath_by_subsys_id(devpath, sizeof(devpath), + ISCSI_CONN_SUBSYS, id); +} + +/* + * iscsi_sysfs_get_sid_from_path - parse a string for the sid + * @session: session path + * + * Given sysfs_device is a directory name of the form: + * + * /sys/devices/platform/hostH/sessionS/targetH:B:I/H:B:I:L + * /sys/devices/platform/hostH/sessionS/targetH:B:I + * /sys/devices/platform/hostH/sessionS + * + * return the sid S. If just the sid is passed in it will be converted + * to an int. + */ +int iscsi_sysfs_get_sid_from_path(char *session) +{ + struct sysfs_device *dev_parent, *dev; + struct stat statb; + char devpath[PATH_SIZE]; + char *end; + int sid; + + sid = strtol(session, &end, 10); + if (sid > 0 && *session != '\0' && *end == '\0') + return sid; + + if (lstat(session, &statb)) { + log_error("%s is an invalid session ID or path", session); + exit(1); + } + + if (!S_ISDIR(statb.st_mode) && !S_ISLNK(statb.st_mode)) { + log_error("%s is not a directory", session); + exit(1); + } + + if (!strncmp(session, "/sys", 4)) + strlcpy(devpath, session + 4, sizeof(devpath)); + else + strlcpy(devpath, session, sizeof(devpath)); + + dev = sysfs_device_get(devpath); + if (!dev) { + log_error("Could not get dev for %s. Possible sysfs " + "incompatibility.", devpath); + return -1; + } + + if (!strncmp(dev->kernel, "session", 7)) + return atoi(dev->kernel_number); + + dev_parent = sysfs_device_get_parent(dev); + while (dev_parent != NULL) { + if (strncmp(dev_parent->kernel, "session", 7) == 0) + return atoi(dev_parent->kernel_number); + dev_parent = sysfs_device_get_parent(dev_parent); + } + + log_error("Unable to find sid in path %s", session); + return -1; +} + +int iscsi_sysfs_get_sessioninfo_by_id(struct session_info *info, char *session) +{ + char id[NAME_SIZE]; + int ret, pers_failed = 0; + uint32_t host_no; + + if (sscanf(session, "session%d", &info->sid) != 1) { + log_error("invalid session '%s'", session); + return ISCSI_ERR_INVAL; + } + + ret = sysfs_get_str(session, ISCSI_SESSION_SUBSYS, "targetname", + info->targetname, sizeof(info->targetname)); + if (ret) { + log_error("could not read session targetname: %d", ret); + return ISCSI_ERR_SYSFS_LOOKUP; + } + + ret = sysfs_get_str(session, ISCSI_SESSION_SUBSYS, "username", + (info->chap).username, + sizeof((info->chap).username)); + if (ret) + log_debug(5, "could not read username: %d", ret); + + ret = sysfs_get_str(session, ISCSI_SESSION_SUBSYS, "password", + (info->chap).password, + sizeof((info->chap).password)); + if (ret) + log_debug(5, "could not read password: %d", ret); + + ret = sysfs_get_str(session, ISCSI_SESSION_SUBSYS, "username_in", + (info->chap).username_in, + sizeof((info->chap).username_in)); + if (ret) + log_debug(5, "could not read username in: %d", ret); + + ret = sysfs_get_str(session, ISCSI_SESSION_SUBSYS, "password_in", + (info->chap).password_in, + sizeof((info->chap).password_in)); + if (ret) + log_debug(5, "could not read password in: %d", ret); + + ret = sysfs_get_int(session, ISCSI_SESSION_SUBSYS, "recovery_tmo", + &((info->tmo).recovery_tmo)); + if (ret) + (info->tmo).recovery_tmo = -1; + + ret = sysfs_get_int(session, ISCSI_SESSION_SUBSYS, "lu_reset_tmo", + &((info->tmo).lu_reset_tmo)); + if (ret) + (info->tmo).lu_reset_tmo = -1; + + ret = sysfs_get_int(session, ISCSI_SESSION_SUBSYS, "tgt_reset_tmo", + &((info->tmo).tgt_reset_tmo)); + if (ret) + (info->tmo).lu_reset_tmo = -1; + + sysfs_get_int(session, ISCSI_SESSION_SUBSYS, "abort_tmo", + &((info->tmo).abort_tmo)); + if (ret) + (info->tmo).abort_tmo = -1; + + ret = sysfs_get_int(session, ISCSI_SESSION_SUBSYS, "tpgt", + &info->tpgt); + if (ret) { + log_error("could not read session tpgt: %d", ret); + return ISCSI_ERR_SYSFS_LOOKUP; + } + + snprintf(id, sizeof(id), ISCSI_CONN_ID, info->sid); + /* some HW drivers do not export addr and port */ + memset(info->persistent_address, 0, NI_MAXHOST); + ret = sysfs_get_str(id, ISCSI_CONN_SUBSYS, "persistent_address", + info->persistent_address, + sizeof(info->persistent_address)); + if (ret) { + pers_failed = 1; + /* older qlogic does not support this */ + log_debug(5, "could not read pers conn addr: %d", ret); + } + + memset(info->address, 0, NI_MAXHOST); + ret = sysfs_get_str(id, ISCSI_CONN_SUBSYS, "address", + info->address, sizeof(info->address)); + if (ret) { + log_debug(5, "could not read curr addr: %d", ret); + /* iser did not export this */ + if (!pers_failed) + strcpy(info->address, info->persistent_address); + } else if (pers_failed) + /* + * for qla if we could not get the persistent addr + * we will use the current for both addrs + */ + strcpy(info->persistent_address, info->address); + pers_failed = 0; + + info->persistent_port = -1; + ret = sysfs_get_int(id, ISCSI_CONN_SUBSYS, "persistent_port", + &info->persistent_port); + if (ret) { + pers_failed = 1; + log_debug(5, "Could not read pers conn port %d", ret); + } + + info->port = -1; + ret = sysfs_get_int(id, ISCSI_CONN_SUBSYS, "port", &info->port); + if (ret) { + /* iser did not export this */ + if (!pers_failed) + info->port = info->persistent_port; + log_debug(5, "Could not read curr conn port %d", ret); + } else if (pers_failed) + /* + * for qla if we could not get the persistent addr + * we will use the current for both addrs + */ + info->persistent_port = info->port; + + ret = 0; + host_no = iscsi_sysfs_get_host_no_from_sid(info->sid, &ret); + if (ret) { + log_error("could not get host_no for session%d: %s.", + info->sid, iscsi_err_to_str(ret)); + return ret; + } + + iscsi_sysfs_read_iface(&info->iface, host_no, session, NULL); + + log_debug(7, "found targetname %s address %s pers address %s port %d " + "pers port %d driver %s iface name %s ipaddress %s " + "netdev %s hwaddress %s iname %s", + info->targetname, info->address ? info->address : "NA", + info->persistent_address ? info->persistent_address : "NA", + info->port, info->persistent_port, info->iface.transport_name, + info->iface.name, info->iface.ipaddress, + info->iface.netdev, info->iface.hwaddress, + info->iface.iname); + return 0; +} + +int iscsi_sysfs_for_each_session(void *data, int *nr_found, + iscsi_sysfs_session_op_fn *fn, + int in_parallel) +{ + struct dirent **namelist; + int rc = 0, n, i, chldrc = 0; + struct session_info *info; + pid_t pid = 0; + + info = calloc(1, sizeof(*info)); + if (!info) + return ISCSI_ERR_NOMEM; + + info->iscsid_req_tmo = -1; + n = scandir(ISCSI_SESSION_DIR, &namelist, trans_filter, + alphasort); + if (n <= 0) + goto free_info; + + for (i = 0; i < n; i++) { + rc = iscsi_sysfs_get_sessioninfo_by_id(info, + namelist[i]->d_name); + if (rc) { + log_error("could not find session info for %s", + namelist[i]->d_name); + /* raced. session was shutdown while looping */ + rc = 0; + continue; + } + + if (in_parallel) { + pid = fork(); + } + if (pid == 0) { + rc = fn(data, info); + if (in_parallel) { + exit(rc); + } else { + if (rc > 0) { + break; + } else if (rc == 0) { + (*nr_found)++; + } else { + /* if less than zero it means it was not a match */ + rc = 0; + } + } + } else if (pid < 0) { + log_error("could not fork() for session %s, err %d", + namelist[i]->d_name, errno); + } + } + + if (in_parallel) { + while (1) { + if (wait(&chldrc) < 0) { + /* + * ECHILD means no more children which is + * expected to happen sooner or later. + */ + if (errno != ECHILD) { + rc = errno; + } + break; + } + + if (!WIFEXITED(chldrc)) { + /* + * abnormal termination (signal, exception, etc.) + * + * The non-parallel code path returns the first + * error so this keeps the same semantics. + */ + if (rc == 0) + rc = ISCSI_ERR_CHILD_TERMINATED; + } else if ((WEXITSTATUS(chldrc) != 0) && + (WEXITSTATUS(chldrc) != 255)) { + /* + * 0 is success + * 255 is a truncated return code from exit(-1) + * and means no match + * anything else (this case) is an error + */ + if (rc == 0) + rc = WEXITSTATUS(chldrc); + } else if (WEXITSTATUS(chldrc) == 0) { + /* success */ + (*nr_found)++; + } + } + } + + for (i = 0; i < n; i++) + free(namelist[i]); + free(namelist); + +free_info: + free(info); + return rc; +} + +/* + * count the number of sessions -- a much-simplified + * version of iscsi_sysfs_for_each_session + * + * TODO: return an array of the session info we find, for use + * by iscsi_sysfs_for_each_session(), so it doesn't have to + * do it all over again + */ +int iscsi_sysfs_count_sessions(void) +{ + struct dirent **namelist = NULL; + int n, i; + struct session_info *info; + + + info = calloc(1, sizeof(*info)); + if (!info) + /* no sessions found */ + return 0; + info->iscsid_req_tmo = -1; + + n = scandir(ISCSI_SESSION_DIR, &namelist, trans_filter, alphasort); + if (n <= 0) + /* no sessions found */ + goto free_info; + + /* + * try to get session info for each session found, but ignore + * errors if any since it may be a race condition + */ + for (i = 0; i < n; i++) + if (iscsi_sysfs_get_sessioninfo_by_id(info, + namelist[i]->d_name) != 0) + log_warning("could not find session info for %s", + namelist[i]->d_name); + + for (i = 0; i < n; i++) + free(namelist[i]); + free(namelist); + +free_info: + free(info); + return n; +} + +int iscsi_sysfs_get_session_state(char *state, int sid) +{ + char id[NAME_SIZE]; + + snprintf(id, sizeof(id), ISCSI_SESSION_ID, sid); + if (sysfs_get_str(id, ISCSI_SESSION_SUBSYS, "state", state, + SCSI_MAX_STATE_VALUE)) + return ISCSI_ERR_SYSFS_LOOKUP; + return 0; +} + +int iscsi_sysfs_get_host_state(char *state, int host_no) +{ + char id[NAME_SIZE]; + + snprintf(id, sizeof(id), ISCSI_HOST_ID, host_no); + if (sysfs_get_str(id, SCSI_HOST_SUBSYS, "state", state, + SCSI_MAX_STATE_VALUE)) + return ISCSI_ERR_SYSFS_LOOKUP; + return 0; +} + +int iscsi_sysfs_get_device_state(char *state, int host_no, int target, int lun) +{ + char id[NAME_SIZE]; + + snprintf(id, sizeof(id), "%d:0:%d:%d", host_no, target, lun); + if (sysfs_get_str(id, SCSI_SUBSYS, "state", state, + SCSI_MAX_STATE_VALUE)) { + log_debug(3, "Could not read attr state for %s", id); + return ISCSI_ERR_SYSFS_LOOKUP; + } + + return 0; +} + +char *iscsi_sysfs_get_blockdev_from_lun(int host_no, int target, int lun) +{ + char devpath[PATH_SIZE]; + char path_full[PATH_SIZE]; + char id[NAME_SIZE]; + DIR *dirfd; + struct dirent *dent; + size_t sysfs_len; + struct stat statbuf; + char *blockdev, *blockdup = NULL; + + snprintf(id, sizeof(id), "%d:0:%d:%d", host_no, target, lun); + if (!sysfs_lookup_devpath_by_subsys_id(devpath, sizeof(devpath), + SCSI_SUBSYS, id)) { + log_debug(3, "Could not lookup devpath for %s %s", + SCSI_SUBSYS, id); + return NULL; + } + + sysfs_len = strlcpy(path_full, sysfs_path, sizeof(path_full)); + if (sysfs_len >= sizeof(path_full)) + sysfs_len = sizeof(path_full) - 1; + strlcat(path_full, devpath, sizeof(path_full)); + + dirfd = opendir(path_full); + if (!dirfd) + return NULL; + + while ((dent = readdir(dirfd))) { + if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) + continue; + + /* not sure what tape looks like */ + if (strncmp(dent->d_name, "block:", 5)) + continue; + + strlcat(path_full, "/", sizeof(path_full)); + strlcat(path_full, dent->d_name, sizeof(path_full)); + /* + * 2.6.25 dropped the symlink and now block is a dir. + */ + if (lstat(path_full, &statbuf)) { + log_error("Could not stat block path %s err %d", + path_full, errno); + break; + } + + if (S_ISLNK(statbuf.st_mode)) { + blockdev = strchr(dent->d_name, ':'); + if (!blockdev) + break; + /* increment past colon */ + blockdev++; + blockdup = strdup(blockdev); + } else if (S_ISDIR(statbuf.st_mode)) { + DIR *blk_dirfd; + struct dirent *blk_dent; + + /* it should not be this hard should it? :) */ + blk_dirfd = opendir(path_full); + if (!blk_dirfd) { + log_debug(3, "Could not open blk path %s", + path_full); + break; + } + + while ((blk_dent = readdir(blk_dirfd))) { + if (!strcmp(blk_dent->d_name, ".") || + !strcmp(blk_dent->d_name, "..")) + continue; + blockdup = strdup(blk_dent->d_name); + break; + } + closedir(blk_dirfd); + } + + break; + } + closedir(dirfd); + return blockdup; +} + +static uint32_t get_target_no_from_sid(uint32_t sid, int *err) +{ + char devpath[PATH_SIZE]; + char path_full[PATH_SIZE]; + char id[NAME_SIZE]; + DIR *dirfd; + struct dirent *dent; + uint32_t host, bus, target = 0; + size_t sysfs_len; + + *err = ISCSI_ERR_SESS_NOT_FOUND; + + snprintf(id, sizeof(id), "session%u", sid); + if (!sysfs_lookup_devpath_by_subsys_id(devpath, sizeof(devpath), + ISCSI_SESSION_SUBSYS, id)) { + log_debug(3, "Could not lookup devpath for %s %s", + ISCSI_SESSION_SUBSYS, id); + return 0; + } + + /* + * This does not seem safe from future changes, but we currently + * want /devices/platform/hostY/sessionX, but we come from the + * /class/iscsi_session/sessionX/device. + */ + sysfs_len = strlcpy(path_full, sysfs_path, sizeof(path_full)); + if (sysfs_len >= sizeof(path_full)) + sysfs_len = sizeof(path_full) - 1; + strlcat(path_full, devpath, sizeof(path_full)); + strlcat(path_full, "/device", sizeof(devpath)); + + dirfd = opendir(path_full); + if (!dirfd) + return 0; + + while ((dent = readdir(dirfd))) { + if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) + continue; + + if (strncmp(dent->d_name, "target", 6)) + continue; + + if (sscanf(dent->d_name, "target%u:%u:%u", + &host, &bus, &target) != 3) + break; + + *err = 0; + break; + + } + closedir(dirfd); + return target; + +} + +int iscsi_sysfs_is_transport_loaded(char *transport_name) +{ + struct iscsi_transport *t; + + /* sync up kernel and userspace */ + read_transports(); + + /* check if the transport is loaded and matches */ + list_for_each_entry(t, &transports, list) { + if (t->handle && !strncmp(t->name, transport_name, + ISCSI_TRANSPORT_NAME_MAXLEN)) + return 1; + } + + return 0; +} + +struct iscsi_transport *iscsi_sysfs_get_transport_by_name(char *transport_name) +{ + struct iscsi_transport *t; + int retry = 0; + +retry: + /* sync up kernel and userspace */ + read_transports(); + + /* check if the transport is loaded and matches */ + list_for_each_entry(t, &transports, list) { + if (t->handle && !strncmp(t->name, transport_name, + ISCSI_TRANSPORT_NAME_MAXLEN)) + return t; + } + + if (retry < 1) { + retry++; + if (!transport_load_kmod(transport_name)) + goto retry; + } + + return NULL; +} + +/* TODO: replace the following functions with some decent sysfs links */ +struct iscsi_transport *iscsi_sysfs_get_transport_by_hba(uint32_t host_no) +{ + char name[ISCSI_TRANSPORT_NAME_MAXLEN]; + char id[NAME_SIZE]; + int rc; + + if (host_no == -1) + return NULL; + + snprintf(id, sizeof(id), ISCSI_HOST_ID, host_no); + rc = sysfs_get_str(id, SCSI_HOST_SUBSYS, "proc_name", name, + ISCSI_TRANSPORT_NAME_MAXLEN); + if (rc) { + log_error("Could not read proc_name for host%u rc %d.", + host_no, rc); + return NULL; + } + + /* + * stupid, stupid. We named the transports tcp or iser, but the + * the modules are named iscsi_tcp and iscsi_iser + */ + if (strstr(name, "iscsi_")) + return iscsi_sysfs_get_transport_by_name(name + 6); + else + return iscsi_sysfs_get_transport_by_name(name); +} + +struct iscsi_transport *iscsi_sysfs_get_transport_by_sid(uint32_t sid) +{ + uint32_t host_no; + int err; + + host_no = iscsi_sysfs_get_host_no_from_sid(sid, &err); + if (err) + return NULL; + return iscsi_sysfs_get_transport_by_hba(host_no); +} + +/* + * For connection reinstatement we need to send the exp_statsn from + * the previous connection + * + * This is only called when the connection is halted so exp_statsn is safe + * to read without racing. + */ +int iscsi_sysfs_get_exp_statsn(int sid) +{ + char id[NAME_SIZE]; + uint32_t exp_statsn = 0; + + snprintf(id, sizeof(id), ISCSI_CONN_ID, sid); + if (sysfs_get_uint(id, ISCSI_CONN_SUBSYS, "exp_statsn", + &exp_statsn)) { + log_error("Could not read expstatsn for sid %d. " + "Using zero for exp_statsn.", sid); + exp_statsn = 0; + } + return exp_statsn; +} + +int iscsi_sysfs_session_supports_nop(int sid) +{ + char id[NAME_SIZE]; + uint32_t ping_tmo = 0; + + snprintf(id, sizeof(id), ISCSI_CONN_ID, sid); + if (sysfs_get_uint(id, ISCSI_CONN_SUBSYS, "ping_tmo", + &ping_tmo)) { + return 0; + } + return 1; +} + +int iscsi_sysfs_for_each_device(void *data, int host_no, uint32_t sid, + void (* fn)(void *data, int host_no, + int target, int lun)) +{ + struct dirent **namelist; + int h, b, t, l, i, n, err = 0, target; + char devpath[PATH_SIZE]; + char id[NAME_SIZE]; + char path_full[3*PATH_SIZE]; + + target = get_target_no_from_sid(sid, &err); + if (err) + return err; + snprintf(id, sizeof(id), "session%u", sid); + if (!sysfs_lookup_devpath_by_subsys_id(devpath, sizeof(devpath), + ISCSI_SESSION_SUBSYS, id)) { + log_debug(3, "Could not lookup devpath for %s %s", + ISCSI_SESSION_SUBSYS, id); + return ISCSI_ERR_SYSFS_LOOKUP; + } + + snprintf(path_full, sizeof(path_full), "%s%s/device/target%d:0:%d", + sysfs_path, devpath, host_no, target); + + if (strlen(path_full) > PATH_SIZE) { + log_debug(3, "Could not lookup devpath for %s %s (too long)", + ISCSI_SESSION_SUBSYS, id); + return ISCSI_ERR_SYSFS_LOOKUP; + } + + n = scandir(path_full, &namelist, trans_filter, + alphasort); + if (n <= 0) + return 0; + + for (i = 0; i < n; i++) { + if (sscanf(namelist[i]->d_name, "%d:%d:%d:%d\n", + &h, &b, &t, &l) != 4) + continue; + fn(data, h, t, l); + } + + for (i = 0; i < n; i++) + free(namelist[i]); + free(namelist); + + return 0; +} + +void iscsi_sysfs_set_queue_depth(void *data, int hostno, int target, int lun) +{ + char id[NAME_SIZE]; + char write_buf[20]; + int err, qdepth = *((int *)data); + + snprintf(id, sizeof(id), "%d:0:%d:%d", hostno, target, lun); + snprintf(write_buf, sizeof(write_buf), "%d", qdepth); + log_debug(4, "set queue depth for %s to %s", id, write_buf); + + err = sysfs_set_param(id, SCSI_SUBSYS, "queue_depth", write_buf, + strlen(write_buf)); + if (err && err != EINVAL) + log_error("Could not queue depth for LUN %d err %d.", lun, err); +} + +void iscsi_sysfs_set_device_online(void *data, int hostno, int target, int lun) +{ + char *write_buf = "running\n"; + char id[NAME_SIZE]; + int err; + + snprintf(id, sizeof(id), "%d:0:%d:%d", hostno, target, lun); + log_debug(4, "online device %s", id); + + err = sysfs_set_param(id, SCSI_SUBSYS, "state", write_buf, + strlen(write_buf)); + if (err && err != EINVAL) + /* we should read the state */ + log_error("Could not online LUN %d err %d.", lun, err); +} + +void iscsi_sysfs_rescan_device(void *data, int hostno, int target, int lun) +{ + char *write_buf = "1"; + char id[NAME_SIZE]; + + snprintf(id, sizeof(id), "%d:0:%d:%d", hostno, target, lun); + log_debug(4, "rescanning device %s", id); + sysfs_set_param(id, SCSI_SUBSYS, "rescan", write_buf, + strlen(write_buf)); +} + +pid_t iscsi_sysfs_scan_host(int hostno, int async, int autoscan) +{ + char id[NAME_SIZE]; + char *write_buf = "- - -"; + pid_t pid = 0; + + if (async) + pid = fork(); + + if (pid >= 0 && !autoscan) { + if (pid) + log_debug(4, "host%d in manual scan mode, skipping scan", hostno); + } else if (pid == 0) { + /* child */ + log_debug(4, "scanning host%d", hostno); + + snprintf(id, sizeof(id), ISCSI_HOST_ID, hostno); + sysfs_set_param(id, SCSI_HOST_SUBSYS, "scan", write_buf, + strlen(write_buf)); + log_debug(4, "scanning host%d completed", hostno); + } else if (pid > 0) { + log_debug(4, "scanning host%d from pid %d", hostno, pid); + } else + /* + * Session is fine, so log the error and let the user + * scan by hand + */ + log_error("Could not start scanning process for host %d " + "err %d. Try scanning through sysfs.", hostno, + errno); + return pid; +} + +struct iscsi_transport *iscsi_sysfs_get_transport_by_session(char *sys_session) +{ + uint32_t sid; + + if (sscanf(sys_session, "session%u", &sid) != 1) { + log_error("invalid session '%s'.", sys_session); + return NULL; + } + + return iscsi_sysfs_get_transport_by_sid(sid); +} + +char *iscsi_sysfs_get_iscsi_kernel_version(void) +{ + return sysfs_attr_get_value("/module/scsi_transport_iscsi", "version"); +} diff --git a/usr/iscsi_sysfs.h b/usr/iscsi_sysfs.h new file mode 100644 index 0000000..1d0377f --- /dev/null +++ b/usr/iscsi_sysfs.h @@ -0,0 +1,116 @@ +/* + * iSCSI sysfs + * + * Copyright (C) 2006 Mike Christie + * Copyright (C) 2006 Red Hat, Inc. 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 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. + */ +#ifndef ISCSI_SYSFS_H +#define ISCSI_SYSFS_H + +#include + +#include "sysfs.h" +#include "types.h" +#include "iscsi_proto.h" +#include "config.h" + +struct session_info; +struct host_info; +struct iscsi_session; +struct iscsi_conn; +struct iscsi_session_operational_config; +struct iscsi_conn_operational_config; +struct iscsi_auth_config; +struct flashnode_rec; + +#define SCSI_MAX_STATE_VALUE 32 + +extern void free_transports(void); +extern char *iscsi_sysfs_get_iscsi_kernel_version(void); +extern int iscsi_sysfs_get_sessioninfo_by_id(struct session_info *info, + char *sys_session); +extern int iscsi_sysfs_session_has_leadconn(uint32_t sid); + +typedef int (iscsi_sysfs_session_op_fn)(void *, struct session_info *); +typedef int (iscsi_sysfs_host_op_fn)(void *, struct host_info *); +typedef int (iscsi_sysfs_flashnode_op_fn)(void *, struct flashnode_rec *, + uint32_t, uint32_t); +typedef int (iscsi_sysfs_iface_op_fn)(void *, struct iface_rec *); + +extern int iscsi_sysfs_for_each_iface_on_host(void *data, uint32_t host_no, + int *nr_found, + iscsi_sysfs_iface_op_fn *fn); +extern int iscsi_sysfs_for_each_session(void *data, int *nr_found, + iscsi_sysfs_session_op_fn *fn, + int in_parallel); +extern int iscsi_sysfs_count_sessions(void); +extern int iscsi_sysfs_for_each_host(void *data, int *nr_found, + iscsi_sysfs_host_op_fn *fn); +extern uint32_t iscsi_sysfs_get_host_no_from_sid(uint32_t sid, int *err); +extern uint32_t iscsi_sysfs_get_host_no_from_hwinfo(struct iface_rec *iface, + int *rc); +extern uint32_t iscsi_sysfs_get_host_no_from_hwaddress(char *hwaddress, int *rc); +extern int iscsi_sysfs_get_hostinfo_by_host_no(struct host_info *hinfo); +extern int iscsi_sysfs_for_each_flashnode(void *data, uint32_t host_no, + int *nr_found, + iscsi_sysfs_flashnode_op_fn *fn); +extern int iscsi_sysfs_get_flashnode_info(struct flashnode_rec *fnode, + uint32_t host_no, + uint32_t flashnode_id); +extern int iscsi_sysfs_update_flashnode_param(uint32_t host_no, + uint32_t flashnode_id, + char *name, char *val); +extern int iscsi_sysfs_create_flashnode(uint32_t host_no, char *ipver); +extern int iscsi_sysfs_del_flashnode(uint32_t host_no, uint32_t flashnode_id); +extern int iscsi_sysfs_login_flashnode(uint32_t host_no, uint32_t flashnode_id); +extern int iscsi_sysfs_logout_flashnode(uint32_t host_no, + uint32_t flashnode_id); +extern int iscsi_sysfs_get_sid_from_path(char *session); +extern char *iscsi_sysfs_get_blockdev_from_lun(int hostno, int target, int sid); + +static inline int is_valid_operational_value(int value) +{ + return value != -1; +} + +extern void iscsi_sysfs_get_auth_conf(int sid, struct iscsi_auth_config *conf); +extern void iscsi_sysfs_get_negotiated_session_conf(int sid, + struct iscsi_session_operational_config *conf); +extern void iscsi_sysfs_get_negotiated_conn_conf(int sid, + struct iscsi_conn_operational_config *conf); +extern pid_t iscsi_sysfs_scan_host(int hostno, int async, int autoscan); +extern int iscsi_sysfs_get_session_state(char *state, int sid); +extern int iscsi_sysfs_get_host_state(char *state, int host_no); +extern int iscsi_sysfs_get_device_state(char *state, int host_no, int target, + int lun); +extern int iscsi_sysfs_get_exp_statsn(int sid); +extern void iscsi_sysfs_set_queue_depth(void *data, int hostno, int target, + int lun); +extern void iscsi_sysfs_set_device_online(void *data, int hostno, int target, + int lun); +extern void iscsi_sysfs_rescan_device(void *data, int hostno, int target, + int lun); +extern int iscsi_sysfs_for_each_device(void *data, int host_no, uint32_t sid, + void (* fn)(void *, int host_no, int target, + int lun)); +extern struct iscsi_transport *iscsi_sysfs_get_transport_by_hba(uint32_t host_no); +extern struct iscsi_transport *iscsi_sysfs_get_transport_by_session(char *sys_session); +extern struct iscsi_transport *iscsi_sysfs_get_transport_by_sid(uint32_t sid); +extern struct iscsi_transport *iscsi_sysfs_get_transport_by_name(char *transport_name); +extern int iscsi_sysfs_is_transport_loaded(char *transport_name); +extern int iscsi_sysfs_session_supports_nop(int sid); +extern int iscsi_sysfs_session_user_created(int sid); + +extern struct list_head transports; + +#endif diff --git a/usr/iscsi_timer.c b/usr/iscsi_timer.c new file mode 100644 index 0000000..de38286 --- /dev/null +++ b/usr/iscsi_timer.c @@ -0,0 +1,86 @@ +/* + * iSCSI timer + * + * Copyright (C) 2002 Cisco Systems, Inc. + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#include +#include + +void iscsi_timer_clear(struct timeval *timer) +{ + memset(timer, 0, sizeof (*timer)); +} + +/* set timer to now + seconds */ +void iscsi_timer_set(struct timeval *timer, int seconds) +{ + if (timer) { + memset(timer, 0, sizeof (*timer)); + gettimeofday(timer, NULL); + + timer->tv_sec += seconds; + } +} + +int iscsi_timer_expired(struct timeval *timer) +{ + struct timeval now; + + /* no timer, can't have expired */ + if ((timer == NULL) || ((timer->tv_sec == 0) && (timer->tv_usec == 0))) + return 0; + + memset(&now, 0, sizeof (now)); + gettimeofday(&now, NULL); + + if (now.tv_sec > timer->tv_sec) + return 1; + if ((now.tv_sec == timer->tv_sec) && (now.tv_usec >= timer->tv_usec)) + return 1; + return 0; +} + +int iscsi_timer_msecs_until(struct timeval *timer) +{ + struct timeval now; + int msecs; + long partial; + + /* no timer, can't have expired, infinite time til it expires */ + if ((timer == NULL) || ((timer->tv_sec == 0) && (timer->tv_usec == 0))) + return -1; + + memset(&now, 0, sizeof (now)); + gettimeofday(&now, NULL); + + /* already expired? */ + if (now.tv_sec > timer->tv_sec) + return 0; + if ((now.tv_sec == timer->tv_sec) && (now.tv_usec >= timer->tv_usec)) + return 0; + + /* not expired yet, do the math */ + partial = timer->tv_usec - now.tv_usec; + if (partial < 0) { + partial += 1000 * 1000; + msecs = (partial + 500) / 1000; + msecs += (timer->tv_sec - now.tv_sec - 1) * 1000; + } else { + msecs = (partial + 500) / 1000; + msecs += (timer->tv_sec - now.tv_sec) * 1000; + } + + return msecs; +} diff --git a/usr/iscsi_timer.h b/usr/iscsi_timer.h new file mode 100644 index 0000000..13e8368 --- /dev/null +++ b/usr/iscsi_timer.h @@ -0,0 +1,28 @@ +/* + * iSCSI timer + * + * Copyright (C) 2002 Cisco Systems, Inc. + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#ifndef ISCSI_TIMER_H +#define ISCSI_TIMER_H + +struct timeval; + +extern void iscsi_timer_clear(struct timeval *timer); +extern void iscsi_timer_set(struct timeval *timer, int seconds); +extern int iscsi_timer_expired(struct timeval *timer); +extern int iscsi_timer_msecs_until(struct timeval *timer); + +#endif diff --git a/usr/iscsi_util.c b/usr/iscsi_util.c new file mode 100644 index 0000000..fd8fc0c --- /dev/null +++ b/usr/iscsi_util.c @@ -0,0 +1,384 @@ +/* + * Misc helpers + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * Copyright (C) 2006 - 2010 Mike Christie + * Copyright (C) 2006 - 2010 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sysdeps.h" +#include "log.h" +#include "iscsi_settings.h" +#include "iface.h" +#include "session_info.h" +#include "iscsi_util.h" + +int setup_abstract_addr(struct sockaddr_un *addr, char *unix_sock_name) +{ + memset(addr, 0, sizeof(*addr)); + addr->sun_family = AF_LOCAL; + strlcpy(addr->sun_path + 1, unix_sock_name, sizeof(addr->sun_path) - 1); + return offsetof(struct sockaddr_un, sun_path) + + strlen(addr->sun_path + 1) + 1; +} + +void daemon_init(void) +{ + int fd; + + fd = open("/dev/null", O_RDWR); + if (fd == -1) { + exit(-1); + } + + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + setsid(); + if (chdir("/") < 0) + log_debug(1, "Could not chdir to /: %s", strerror(errno)); + close(fd); +} + +#define ISCSI_OOM_PATH_LEN 48 + +int oom_adjust(void) +{ + int fd; + char path[ISCSI_OOM_PATH_LEN]; + struct stat statb; + + errno = 0; + if (nice(-10) == -1 && errno != 0) + log_debug(1, "Could not increase process priority: %s", + strerror(errno)); + + snprintf(path, ISCSI_OOM_PATH_LEN, "/proc/%d/oom_score_adj", getpid()); + if (stat(path, &statb)) { + /* older kernel so use old oom_adj file */ + snprintf(path, ISCSI_OOM_PATH_LEN, "/proc/%d/oom_adj", + getpid()); + } + fd = open(path, O_WRONLY); + if (fd < 0) + return -1; + if (write(fd, "-16", 3) < 0) /* for 2.6.11 */ + log_debug(1, "Could not set oom score to -16: %s", + strerror(errno)); + if (write(fd, "-17", 3) < 0) /* for Andrea's patch */ + log_debug(1, "Could not set oom score to -17: %s", + strerror(errno)); + close(fd); + return 0; +} + +char* +str_to_ipport(char *str, int *port, int *tpgt) +{ + char *stpgt, *sport = str, *ip = str; + + if (!strchr(ip, '.')) { + if (*ip == '[') { + /* IPv6 with [] */ + if (!(sport = strchr(ip, ']'))) + return NULL; + *sport++ = '\0'; + ip++; + str = sport; + } else { + /* hostname or ipv6 */ + sport = strchr(ip, ':'); + if (sport) { + if (strchr(sport + 1, ':')) + /* ipv6 */ + sport = NULL; + else + /* hostname:port */ + str = sport; + } + } + } + + if (sport && (sport = strchr(str, ':'))) { + *sport++ = '\0'; + *port = strtoul(sport, NULL, 10); + str = sport; + } + + if ((stpgt = strchr(str, ','))) { + *stpgt++ = '\0'; + *tpgt = strtoul(stpgt, NULL, 10); + } else + *tpgt = PORTAL_GROUP_TAG_UNKNOWN; + + log_debug(2, "ip %s, port %d, tgpt %d", ip, *port, *tpgt); + return ip; +} + +#define ISCSI_MAX_FILES 16384 + +int increase_max_files(void) +{ + struct rlimit rl; + int err; + + err = getrlimit(RLIMIT_NOFILE, &rl); + if (err) { + log_debug(1, "Could not get file limit (err %d)", errno); + return errno; + } + log_debug(1, "Max file limits %lu %lu", rl.rlim_cur, rl.rlim_max); + + if (rl.rlim_cur < ISCSI_MAX_FILES) + rl.rlim_cur = ISCSI_MAX_FILES; + if (rl.rlim_max < ISCSI_MAX_FILES) + rl.rlim_max = ISCSI_MAX_FILES; + + err = setrlimit(RLIMIT_NOFILE, &rl); + if (err) { + log_debug(1, "Could not set file limit to %lu/%lu (err %d)", + rl.rlim_cur, rl.rlim_max, errno); + return errno; + } + + return 0; +} + +/* + * from linux kernel + */ +char *strstrip(char *s) +{ + size_t size; + char *end; + + size = strlen(s); + if (!size) + return s; + + end = s + size - 1; + while (end >= s && isspace(*end)) + end--; + *(end + 1) = '\0'; + + while (*s && isspace(*s)) + s++; + + return s; +} + +/** + * cfg_get_string_param - return param value + * @pathname: pathname and filename of config file + * @key: param name + * + * Assumes the delim is a "=". "#" comments a line, but if + * the "#" is after the key= then it is a valid value. +*/ +char *cfg_get_string_param(char *pathname, const char *key) +{ + FILE *f = NULL; + char *line, buffer[1024]; + char *value = NULL, *param, *comment; + + if (!pathname) { + log_error("No pathname to load %s from", key); + return NULL; + } + + if ((f = fopen(pathname, "r"))) { + while ((line = fgets(buffer, sizeof (buffer), f))) { + param = strstr(line, key); + if (!param) + continue; + + /* make sure it is not commented out */ + comment = strchr(line, '#'); + if (comment) { + if (comment < param) + continue; + } + + param = strchr(param, '='); + if (!param) { + log_error("Invalid config line for %s. " + "Missing '='.", key); + continue; + } + + param++; + if (!strlen(param)) { + log_error("Invalid config line for %s. " + "Missing value", key); + continue; + } + + param = strstrip(param); + if (!strlen(param)) { + log_error("Invalid config line for %s. " + "Missing value", key); + continue; + } + + value = strdup(param); + break; + } + fclose(f); + if (value) + log_debug(5, "%s=%s", key, value); + } else + log_error("can't open %s configuration file %s", key, pathname); + + return value; +} + +/** + * iscsi_addr_match - check if the addrs are to the same ip + * @address1: pattern + * @address2: address to check + * + * If address1 is blank then it matches any string passed in. + */ +static int iscsi_addr_match(char *address1, char *address2) +{ + struct addrinfo hints1, hints2, *res1, *res2; + int rc; + + if (!strlen(address1)) + return 1; + + if (!strcmp(address1, address2)) + return 1; + + memset(&hints1, 0, sizeof(struct addrinfo)); + hints1.ai_family = AF_UNSPEC; + hints1.ai_socktype = SOCK_STREAM; + + memset(&hints2, 0, sizeof(struct addrinfo)); + hints2.ai_family = AF_UNSPEC; + hints2.ai_socktype = SOCK_STREAM; + + /* + * didn't match so we have to resolve to see if one is a dnsname + * that matches an ip address. + */ + rc = getaddrinfo(address1, NULL, &hints1, &res1); + if (rc) { + log_debug(1, "Match error. Could not resolve %s: %s", address1, + gai_strerror(rc)); + return 0; + + } + + rc = getaddrinfo(address2, NULL, &hints2, &res2); + if (rc) { + log_debug(1, "Match error. Could not resolve %s: %s", address2, + gai_strerror(rc)); + rc = 0; + goto free_res1; + } + + if ((res1->ai_addrlen != res2->ai_addrlen) || + memcmp(res1->ai_addr, res2->ai_addr, res2->ai_addrlen)) + rc = 0; + else + rc = 1; + + freeaddrinfo(res2); +free_res1: + freeaddrinfo(res1); + return rc; +} + +int __iscsi_match_session(node_rec_t *rec, char *targetname, + char *address, int port, struct iface_rec *iface, + unsigned sid) +{ + if (!rec) { + log_debug(6, "no rec info to match"); + return 1; + } + + log_debug(6, "match session [%s,%s,%d][%s %s,%s,%s]:%u", + rec->name, rec->conn[0].address, rec->conn[0].port, + rec->iface.name, rec->iface.transport_name, + rec->iface.hwaddress, rec->iface.ipaddress, + rec->session.sid); + + if (iface) + log_debug(6, "to [%s,%s,%d][%s %s,%s,%s]:%u", + targetname, address, port, iface->name, + iface->transport_name, iface->hwaddress, + iface->ipaddress, sid); + + if (rec->session.sid && sid && rec->session.sid != sid) + return 0; + + if (strlen(rec->name) && strcmp(rec->name, targetname)) + return 0; + + if (!iscsi_addr_match(rec->conn[0].address, address)) + return 0; + + if (rec->conn[0].port != -1 && port != rec->conn[0].port) + return 0; + + if (!iface_match(&rec->iface, iface)) + return 0; + + return 1; +} + +int iscsi_match_session(void *data, struct session_info *info) +{ + return __iscsi_match_session(data, info->targetname, + info->persistent_address, + info->persistent_port, &info->iface, + info->sid); +} + +int iscsi_match_session_count(void *data, struct session_info *info) +{ + /* + * iscsi_sysfs_for_each_session expects: + * 0==match -1==nomatch >0==error + * but iscsi_match_session returns: + * 1==match 0==nomatch + */ + if (iscsi_match_session(data, info)) + return 0; + return -1; +} + +int iscsi_match_target(void *data, struct session_info *info) +{ + return __iscsi_match_session(data, info->targetname, + info->persistent_address, + info->persistent_port, NULL, + MATCH_ANY_SID); +} diff --git a/usr/iscsi_util.h b/usr/iscsi_util.h new file mode 100644 index 0000000..ff725eb --- /dev/null +++ b/usr/iscsi_util.h @@ -0,0 +1,32 @@ +#ifndef ISCSI_UTIL_H +#define ISCSI_UTIL_H + +#include + +struct node_rec; +struct iface_rec; +struct session_info; + +extern int oom_adjust(void); +extern void daemon_init(void); +extern int increase_max_files(void); + +extern char *str_to_ipport(char *str, int *port, int *tgpt); + +extern int iscsi_match_session(void *data, struct session_info *info); +extern int iscsi_match_target(void *data, struct session_info *info); +extern int iscsi_match_session_count(void *data, struct session_info *info); +extern int __iscsi_match_session(struct node_rec *rec, char *targetname, + char *address, int port, + struct iface_rec *iface, + unsigned sid); + +#define MATCH_ANY_SID 0 + +extern char *strstrip(char *s); +extern char *cfg_get_string_param(char *pathname, const char *key); + +struct sockaddr_un; +extern int setup_abstract_addr(struct sockaddr_un *addr, char *unix_sock_name); + +#endif diff --git a/usr/iscsiadm.c b/usr/iscsiadm.c new file mode 100644 index 0000000..2ce1cf5 --- /dev/null +++ b/usr/iscsiadm.c @@ -0,0 +1,4067 @@ +/* + * iSCSI Administration Utility + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * Copyright (C) 2006 Mike Christie + * Copyright (C) 2006 Red Hat, Inc. All rights reserved. + * Copyright (C) 2011 Dell Inc. + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "initiator.h" +#include "discovery.h" +#include "log.h" +#include "mgmt_ipc.h" +#include "idbm.h" +#include "iscsi_util.h" +#include "transport.h" +#include "version.h" +#include "iscsi_sysfs.h" +#include "list.h" +#include "iscsi_settings.h" +#include "fw_context.h" +#include "iface.h" +#include "session_info.h" +#include "host.h" +#include "sysdeps.h" +#include "idbm_fields.h" +#include "session_mgmt.h" +#include "iscsid_req.h" +#include +#include "iscsi_err.h" +#include "iscsi_ipc.h" +#include "iscsi_timer.h" +#include "flashnode.h" + +#define _good(rc, rc_val, out) \ + do { \ + rc_val = rc; \ + if (rc_val != LIBISCSI_OK) \ + goto out; \ + } while(0) + +static char program_name[] = "iscsiadm"; +static char config_file[TARGET_NAME_MAXLEN]; +extern struct iscsi_ipc *ipc; + +enum iscsiadm_mode { + MODE_DISCOVERY, + MODE_DISCOVERYDB, + MODE_NODE, + MODE_SESSION, + MODE_HOST, + MODE_IFACE, + MODE_FW, + MODE_PING, + MODE_CHAP, + MODE_FLASHNODE, + MODE_HOST_STATS +}; + +enum iscsiadm_op { + OP_NOOP = 0x0, + OP_NEW = 0x1, + OP_DELETE = 0x2, + OP_UPDATE = 0x4, + OP_SHOW = 0x8, + OP_NONPERSISTENT = 0x10, + OP_APPLY = 0x20, + OP_APPLY_ALL = 0x40, + OP_LOGIN = 0x80, + OP_LOGOUT = 0x100 +}; + +enum _print_node_tree_mode { + _PRINT_MODE_IFACE, + _PRINT_MODE_NODE, +}; + +static struct option const long_options[] = +{ + {"mode", required_argument, NULL, 'm'}, + {"portal", required_argument, NULL, 'p'}, + {"targetname", required_argument, NULL, 'T'}, + {"interface", required_argument, NULL, 'I'}, + {"op", required_argument, NULL, 'o'}, + {"type", required_argument, NULL, 't'}, + {"name", required_argument, NULL, 'n'}, + {"value", required_argument, NULL, 'v'}, + {"host", required_argument, NULL, 'H'}, + {"sid", required_argument, NULL, 'r'}, + {"rescan", no_argument, NULL, 'R'}, + {"print", required_argument, NULL, 'P'}, + {"discover", no_argument, NULL, 'D'}, + {"login", no_argument, NULL, 'l'}, + {"loginall", required_argument, NULL, 'L'}, + {"logout", no_argument, NULL, 'u'}, + {"logoutall", required_argument, NULL, 'U'}, + {"stats", no_argument, NULL, 's'}, + {"killiscsid", required_argument, NULL, 'k'}, + {"debug", required_argument, NULL, 'd'}, + {"show", no_argument, NULL, 'S'}, + {"version", no_argument, NULL, 'V'}, + {"help", no_argument, NULL, 'h'}, + {"submode", required_argument, NULL, 'C'}, + {"ip", required_argument, NULL, 'a'}, + {"packetsize", required_argument, NULL, 'b'}, + {"count", required_argument, NULL, 'c'}, + {"interval", required_argument, NULL, 'i'}, + {"index", required_argument, NULL, 'x'}, + {"portal_type", optional_argument, NULL, 'A'}, + {NULL, 0, NULL, 0}, +}; +static char *short_options = "RlDVhm:a:b:c:C:p:P:T:H:i:I:U:k:L:d:r:n:v:o:sSt:ux:A:"; + +static void usage(int status) +{ + if (status != 0) + fprintf(stderr, "Try `%s --help' for more information.\n", + program_name); + else { + printf("\ +iscsiadm -m discoverydb [ -hV ] [ -d debug_level ] [-P printlevel] [ -t type -p ip:port -I ifaceN ... [ -Dl ] ] | [ [ -p ip:port -t type] \ +[ -o operation ] [ -n name ] [ -v value ] [ -lD ] ] \n\ +iscsiadm -m discovery [ -hV ] [ -d debug_level ] [-P printlevel] [ -t type -p ip:port -I ifaceN ... [ -l ] ] | [ [ -p ip:port ] [ -l | -D ] ] \n\ +iscsiadm -m node [ -hV ] [ -d debug_level ] [ -P printlevel ] [ -L all,manual,automatic ] [ -U all,manual,automatic ] [ -S ] [ [ -T targetname -p ip:port -I ifaceN ] [ -l | -u | -R | -s] ] \ +[ [ -o operation ] [ -n name ] [ -v value ] ]\n\ +iscsiadm -m session [ -hV ] [ -d debug_level ] [ -P printlevel] [ -r sessionid | sysfsdir [ -R | -u | -s ] [ -o operation ] [ -n name ] [ -v value ] ]\n\ +iscsiadm -m iface [ -hV ] [ -d debug_level ] [ -P printlevel ] [ -I ifacename | -H hostno|MAC ] [ [ -o operation ] [ -n name ] [ -v value ] ] [ -C ping [ -a ip ] [ -b packetsize ] [ -c count ] [ -i interval ] ]\n\ +iscsiadm -m fw [ -d debug_level ] [ -l ]\n\ +iscsiadm -m host [ -P printlevel ] [ -H hostno|MAC ] [ [ -C chap [ -x chap_tbl_idx ] ] | [ -C flashnode [ -A portal_type ] [ -x flashnode_idx ] ] | [ -C stats ] ] [ [ -o operation ] [ -n name ] [ -v value ] ] \n\ +iscsiadm -k priority\n"); + } + exit(status); +} + +static int +str_to_op(char *str) +{ + int op; + + if (!strcmp("new", str)) + op = OP_NEW; + else if (!strcmp("delete", str)) + op = OP_DELETE; + else if (!strcmp("update", str)) + op = OP_UPDATE; + else if (!strcmp("show", str)) + op = OP_SHOW; + else if (!strcmp("nonpersistent", str)) + op = OP_NONPERSISTENT; + else if (!strcmp("apply", str)) + op = OP_APPLY; + else if (!strcmp("applyall", str)) + op = OP_APPLY_ALL; + else if (!strcmp("login", str)) + op = OP_LOGIN; + else if (!strcmp("logout", str)) + op = OP_LOGOUT; + else + op = OP_NOOP; + + return op; +} + +static int +str_to_mode(char *str) +{ + int mode; + + if (!strcmp("discovery", str)) + mode = MODE_DISCOVERY; + else if (!strcmp("discoverydb", str)) + mode = MODE_DISCOVERYDB; + else if (!strcmp("node", str)) + mode = MODE_NODE; + else if (!strcmp("session", str)) + mode = MODE_SESSION; + else if (!strcmp("iface", str)) + mode = MODE_IFACE; + else if (!strcmp("fw", str)) + mode = MODE_FW; + else if (!strcmp("host", str)) + mode = MODE_HOST; + else + mode = -1; + + return mode; +} + +static int +str_to_submode(char *str) +{ + int sub_mode; + + if (!strcmp("ping", str)) + sub_mode = MODE_PING; + else if (!strcmp("chap", str)) + sub_mode = MODE_CHAP; + else if (!strcmp("flashnode", str)) + sub_mode = MODE_FLASHNODE; + else if (!strcmp("stats", str)) + sub_mode = MODE_HOST_STATS; + + else + sub_mode = -1; + + return sub_mode; +} + +static int +str_to_type(char *str) +{ + int type; + + if (!strcmp("sendtargets", str) || + !strcmp("st", str)) + type = DISCOVERY_TYPE_SENDTARGETS; + else if (!strcmp("slp", str)) + type = DISCOVERY_TYPE_SLP; + else if (!strcmp("isns", str)) + type = DISCOVERY_TYPE_ISNS; + else if (!strcmp("fw", str)) + type = DISCOVERY_TYPE_FW; + else + type = -1; + + return type; +} + +static int +str_to_portal_type(char *str) +{ + int ptype; + + if (!strcmp("ipv4", str)) + ptype = IPV4; + else if (!strcmp("ipv6", str)) + ptype = IPV6; + else + ptype = -1; + + return ptype; +} + +static void kill_iscsid(int priority, int tmo) +{ + iscsiadm_req_t req; + iscsiadm_rsp_t rsp; + int rc; + + /* + * We only support SIGTERM like stoppage of iscsid for now. + * In the future we can do something where we try go finish + * up operations like login, error handling, etc, before + * iscsid is stopped, and we can add different values to indicate + * that the user wants iscsid to log out existing sessions before + * exiting. + */ + if (priority != 0) { + log_error("Invalid iscsid priority %d. Priority must be 0.", + priority); + return; + } + + memset(&req, 0, sizeof(req)); + req.command = MGMT_IPC_IMMEDIATE_STOP; + rc = iscsid_exec_req(&req, &rsp, 0, tmo); + if (rc) { + iscsi_err_print_msg(rc); + log_error("Could not stop iscsid. Trying sending iscsid " + "SIGTERM or SIGKILL signals manually"); + } +} + +static int +match_startup_mode(node_rec_t *rec, char *mode) +{ + if ((!strcmp(mode, "automatic") && + rec->startup == ISCSI_STARTUP_AUTOMATIC) || + (!strcmp(mode, "onboot") && + rec->startup == ISCSI_STARTUP_ONBOOT) || + (!strcmp(mode, "manual") && + rec->startup == ISCSI_STARTUP_MANUAL) || + !strcmp(mode, "all")) + return 0; + + /* support conn or session startup params */ + if ((!strcmp(mode, "automatic") && + rec->conn[0].startup == ISCSI_STARTUP_AUTOMATIC) || + (!strcmp(mode, "onboot") && + rec->conn[0].startup == ISCSI_STARTUP_ONBOOT) || + (!strcmp(mode, "manual") && + rec->conn[0].startup == ISCSI_STARTUP_MANUAL) || + !strcmp(mode, "all")) + return 0; + + return -1; +} + +static int +for_each_session(struct node_rec *rec, iscsi_sysfs_session_op_fn *fn, + int in_parallel) +{ + int err, num_found = 0; + + if (rec && rec->session.info) { + num_found = 1; + err = fn(rec, rec->session.info); + } else { + err = iscsi_sysfs_for_each_session(rec, &num_found, fn, + in_parallel); + } + if (err) + log_error("Could not execute operation on all sessions: %s", + iscsi_err_to_str(err)); + else if (!num_found) { + log_error("No session found."); + err = ISCSI_ERR_NO_OBJS_FOUND; + } + + return err; +} + +static int link_recs(void *data, struct node_rec *rec) +{ + struct list_head *list = data; + struct node_rec *rec_copy; + + rec_copy = calloc(1, sizeof(*rec_copy)); + if (!rec_copy) + return ISCSI_ERR_NOMEM; + memcpy(rec_copy, rec, sizeof(*rec_copy)); + INIT_LIST_HEAD(&rec_copy->list); + list_add_tail(&rec_copy->list, list); + return 0; +} + +static int +__logout_by_startup(void *data, struct list_head *list, + struct session_info *info) +{ + char *mode = data; + node_rec_t rec; + + memset(&rec, 0, sizeof(node_rec_t)); + if (idbm_rec_read(&rec, info->targetname, info->tpgt, + info->persistent_address, + info->persistent_port, &info->iface, false)) { + /* + * this is due to a HW driver or some other driver + * not hooked in + */ + log_debug(7, "could not read data for [%s,%s.%d]", + info->targetname, info->persistent_address, + info->persistent_port); + return -1; + } + + /* multiple drivers could be connected to the same portal */ + if (strcmp(rec.iface.transport_name, info->iface.transport_name)) + return -1; + /* + * we always skip on boot because if the user killed this on + * they would not be able to do anything + */ + if (rec.startup == ISCSI_STARTUP_ONBOOT) + return -1; + + if (match_startup_mode(&rec, mode)) + return -1; + + return iscsi_logout_portal(info, list); +} + +static int +logout_by_startup(char *mode) +{ + int nr_found; + int rc; + + if (!mode || !(!strcmp(mode, "automatic") || !strcmp(mode, "all") || + !strcmp(mode,"manual"))) { + log_error("Invalid logoutall option %s.", mode); + usage(ISCSI_ERR_INVAL); + return ISCSI_ERR_INVAL; + } + + rc = iscsi_logout_portals(mode, &nr_found, 1, __logout_by_startup); + if (rc == ISCSI_ERR_NO_OBJS_FOUND) + log_error("No matching sessions found"); + return rc; +} + +struct startup_data { + char *mode; + struct list_head all_logins; + struct list_head leading_logins; +}; + +static int link_startup_recs(void *data, struct node_rec *rec) +{ + struct startup_data *startup = data; + struct node_rec *rec_copy; + + if (match_startup_mode(rec, startup->mode)) + return -1; + + rec_copy = calloc(1, sizeof(*rec_copy)); + if (!rec_copy) + return ISCSI_ERR_NOMEM; + memcpy(rec_copy, rec, sizeof(*rec_copy)); + INIT_LIST_HEAD(&rec_copy->list); + + if (rec_copy->leading_login) + list_add_tail(&rec_copy->list, &startup->leading_logins); + else + list_add_tail(&rec_copy->list, &startup->all_logins); + return 0; +} + +static int +__do_leading_login(void *data, struct list_head *list, struct node_rec *rec) +{ + struct iface_rec *pattern_iface = data; + int nr_found; + + /* Skip any records that do not match the pattern iface */ + if (!iface_match(pattern_iface, &rec->iface)) + return -1; + + /* + * If there is an existing session that matcthes the target, + * the leading login is complete. + */ + if (iscsi_sysfs_for_each_session(rec, &nr_found, iscsi_match_target, 0)) { + log_debug(1, "Skipping %s: Already a session for that target", + rec->name); + return -1; + } + + /* No existing session: Attempt a login. */ + return iscsi_login_portal(NULL, list, rec); +} + +static int +login_by_startup(char *mode) +{ + int nr_found = 0, err, rc; + struct startup_data startup; + + if (!mode || !(!strcmp(mode, "automatic") || !strcmp(mode, "all") || + !strcmp(mode,"manual") || !strcmp(mode, "onboot"))) { + log_error("Invalid loginall option %s.", mode); + usage(ISCSI_ERR_INVAL); + return ISCSI_ERR_INVAL; + } + + /* + * Filter all node records that match the given 'mode' into 2 lists: + * Those with leading_login enabled, and those without. + */ + startup.mode = mode; + INIT_LIST_HEAD(&startup.all_logins); + INIT_LIST_HEAD(&startup.leading_logins); + err = idbm_for_each_rec(&nr_found, &startup, link_startup_recs, false); + if (err && (!list_empty(&startup.all_logins) || + !list_empty(&startup.leading_logins))) + /* log msg and try to log into what we found */ + log_error("Could not read all records: %s", + iscsi_err_to_str(err)); + else if (list_empty(&startup.all_logins) && + list_empty(&startup.leading_logins)) { + if (err) { + log_error("Could not read node DB: %s.", + iscsi_err_to_str(err)); + } else { + log_error("No records found"); + err = ISCSI_ERR_NO_OBJS_FOUND; + } + return err; + } + rc = err; + + if (!list_empty(&startup.all_logins)) { + log_debug(1, "Logging into normal (non-leading-login) portals"); + /* Login all regular (non-leading-login) portals first */ + err = iscsi_login_portals(NULL, &nr_found, 1, + &startup.all_logins, iscsi_login_portal); + if (err) + log_error("Could not log into all portals"); + if (err && !rc) + rc = err; + } + + if (!list_empty(&startup.leading_logins)) { + /* + * For each iface in turn, try to login all portals on that + * iface that do not already have a session present. + */ + struct iface_rec *pattern_iface, *tmp_iface; + struct node_rec *rec, *tmp_rec; + LIST_HEAD(iface_list); + int missed_leading_login = 0; + log_debug(1, "Logging into leading-login portals"); + iface_link_ifaces(&iface_list); + list_for_each_entry_safe(pattern_iface, tmp_iface, &iface_list, + list) { + log_debug(1, "Establishing leading-logins via iface %s", + pattern_iface->name); + err = iscsi_login_portals_safe(pattern_iface, &nr_found, + 1, + &startup.leading_logins, + __do_leading_login); + if (err) + log_error("Could not log into all portals on " + "%s, trying next interface", + pattern_iface->name); + + /* + * Note: We always try all iface records in case there + * are targets that are associated with only a subset + * of iface records. __do_leading_login already + * prevents duplicate sessions if an iface has succeeded + * for a particular target. + */ + } + /* + * Double-check that all leading-login portals have at least + * one session + */ + list_for_each_entry_safe(rec, tmp_rec, &startup.leading_logins, + list) { + if (!iscsi_sysfs_for_each_session(rec, &nr_found, + iscsi_match_target, 0)) + missed_leading_login++; + /* + * Cleanup the list, since 'iscsi_login_portals_safe' + * does not + */ + list_del(&rec->list); + free(rec); + } + if (missed_leading_login) { + log_error("Could not login all leading-login portals"); + if (!rc) + rc = ISCSI_ERR_FATAL_LOGIN; + } + } + + return rc; +} + +/** + * iscsi_logout_matched_portal - logout of targets matching the rec info + * @data: record to session with + * @list: list to add logout rec to + * @info: session to match with rec + */ +static int iscsi_logout_matched_portal(void *data, struct list_head *list, + struct session_info *info) +{ + struct node_rec *pattern_rec = data; + struct iscsi_transport *t; + uint32_t host_no; + int rc = 0; + + t = iscsi_sysfs_get_transport_by_sid(info->sid); + if (!t) + return -1; + + if (!iscsi_match_session(pattern_rec, info)) + return -1; + + host_no = iscsi_sysfs_get_host_no_from_sid(info->sid, &rc); + if (rc) { + log_error("could not get host_no for session%d: %s.", + info->sid, iscsi_err_to_str(rc)); + return -1; + } + + if (!iscsi_sysfs_session_user_created(info->sid)) + rc = iscsi_logout_flashnode_sid(t, host_no, info->sid); + else + rc = iscsi_logout_portal(info, list); + + return rc; +} + +static int rec_match_fn(void *data, node_rec_t *rec) +{ + struct rec_op_data *op_data = data; + + if (!__iscsi_match_session(op_data->match_rec, rec->name, + rec->conn[0].address, rec->conn[0].port, + &rec->iface, rec->session.sid)) + return -1; + return op_data->fn(op_data->data, rec); +} + +static int __for_each_matched_rec(int verbose, struct node_rec *rec, + void *data, idbm_iface_op_fn *fn) +{ + struct rec_op_data op_data; + int nr_found = 0, rc; + + memset(&op_data, 0, sizeof(struct rec_op_data)); + op_data.data = data; + op_data.match_rec = rec; + op_data.fn = fn; + + rc = idbm_for_each_rec(&nr_found, &op_data, rec_match_fn, true); + if (rc) { + if (verbose) + log_error("Could not execute operation on all " + "records: %s", iscsi_err_to_str(rc)); + } else if (!nr_found) { + if (verbose) + log_error("No records found"); + rc = ISCSI_ERR_NO_OBJS_FOUND; + } + + return rc; +} + +static int for_each_matched_rec(struct node_rec *rec, void *data, + idbm_iface_op_fn *fn) +{ + return __for_each_matched_rec(1, rec, data, fn); +} + + +static int login_portals(struct node_rec *pattern_rec) +{ + LIST_HEAD(rec_list); + int nr_found, rc, err; + + err = for_each_matched_rec(pattern_rec, &rec_list, link_recs); + if (err == ISCSI_ERR_NO_OBJS_FOUND) + return err; + else if (err && list_empty(&rec_list)) + return err; + + rc = err; + /* if there is an err but some recs then try to login to what we have */ + + err = iscsi_login_portals(pattern_rec, &nr_found, 1, &rec_list, + iscsi_login_portal); + if (err) + log_error("Could not log into all portals"); + + if (err && !rc) + rc = err; + + return rc; +} + +static void print_node_flat(struct iscsi_node *node) +{ + printf("%s,%" PRIu16 " %s\n", + iscsi_node_portal_get(node), + iscsi_node_tpgt_get(node), + iscsi_node_target_name_get(node)); +} + +static void print_nodes_tree(struct iscsi_node **nodes, uint32_t node_count, + enum _print_node_tree_mode print_mode) +{ + unsigned int i; + struct iscsi_node *cur_node = NULL; + struct iscsi_node *prev_node = NULL; + const char *prefix = NULL; + + if (print_mode == _PRINT_MODE_IFACE) + prefix = "\t"; + else + prefix = ""; + + // According to libopeniscsiusr document, nodes are sorted. There + // is no need to create hash table for this. + for (i = 0; i < node_count; ++i) { + cur_node = nodes[i]; + /* + * Print the target line if this is our first pass, or + * if if it does not match the prevous target. Always print + * the Portal line. The original code seemed to want to + * suppres duplicates here, as well, but it evidently + * didn't work that way, so let's not regress output format + */ + if (!prev_node || strcmp(iscsi_node_target_name_get(prev_node), + iscsi_node_target_name_get(cur_node))) + printf("%sTarget: %s\n", prefix, + iscsi_node_target_name_get(cur_node)); + printf("%s\tPortal: %s,%d\n", prefix, + iscsi_node_portal_get(cur_node), + iscsi_node_tpgt_get(cur_node)); + if (print_mode == _PRINT_MODE_NODE) + printf("\t\tIface Name: %s\n", + iscsi_node_iface_name_get(cur_node)); + prev_node = cur_node; + } +} + +static int print_nodes(struct iscsi_context *ctx, int info_level) +{ + struct iscsi_node **nodes = NULL; + uint32_t node_count = 0; + uint32_t i = 0; + int rc = 0; + + if ((info_level != 0) && (info_level != -1) && (info_level != 1)) { + log_error("Invalid info level %d. Try 0 or 1.", info_level); + rc = ISCSI_ERR_INVAL; + goto out; + } + + rc = iscsi_nodes_get(ctx, &nodes, &node_count); + if (rc != LIBISCSI_OK) + goto out; + + if (!node_count) { + log_error("No records found"); + rc = ISCSI_ERR_NO_OBJS_FOUND; + goto out; + } + + if (info_level == 1) + print_nodes_tree(nodes, node_count, _PRINT_MODE_NODE); + else + for (i = 0; i < node_count; ++i) + print_node_flat(nodes[i]); + +out: + iscsi_nodes_free(nodes, node_count); + return rc; +} + +static int print_nodes_config(struct iscsi_context *ctx, bool show_secret, + const char *target_name, const char *address, + int32_t port, const char *iface_name) +{ + int rc = 0; + struct iscsi_node **nodes = NULL; + struct iscsi_node *node = NULL; + uint32_t node_count = 0; + uint32_t i = 0; + bool match = false; + bool has_match = false; + + rc = iscsi_nodes_get(ctx, &nodes, &node_count); + if (rc != LIBISCSI_OK) + return rc; + + for (i = 0; i < node_count; ++i) { + node = nodes[i]; + match = true; + if ((target_name != NULL) && + (strlen(target_name) != 0) && + (strcmp(target_name, + iscsi_node_target_name_get(node)) != 0)) + match = false; + if ((address != NULL) && + (strlen(address) != 0) && + (strcmp(address, iscsi_node_conn_address_get(node)) != 0)) + match = false; + if ((port != -1) && (port != iscsi_node_conn_port_get(node))) + match = false; + if ((iface_name != NULL) && + (strlen(iface_name) != 0) && + (strcmp(iface_name, iscsi_node_iface_name_get(node)) != 0)) + match = false; + + if (match == true) { + iscsi_node_print_config(node, show_secret); + has_match = true; + } + } + + iscsi_nodes_free(nodes, node_count); + + if (has_match == false) { + log_error("No records found"); + rc = ISCSI_ERR_NO_OBJS_FOUND; + } + return rc; +} + +static char *get_config_file(void) +{ + int rc; + iscsiadm_req_t req; + iscsiadm_rsp_t rsp; + + memset(&req, 0, sizeof(req)); + req.command = MGMT_IPC_CONFIG_FILE; + + rc = iscsid_exec_req(&req, &rsp, 1, ISCSID_REQ_TIMEOUT); + if (rc) + return NULL; + + if (rsp.u.config.var[0] != '\0') { + strcpy(config_file, rsp.u.config.var); + return config_file; + } + + return NULL; +} + +static int rescan_portal(void *data, struct session_info *info) +{ + int host_no, err; + + if (!iscsi_match_session(data, info)) + return -1; + + printf("Rescanning session [sid: %d, target: %s, portal: " + "%s,%d]\n", info->sid, info->targetname, + info->persistent_address, info->port); + + host_no = iscsi_sysfs_get_host_no_from_sid(info->sid, &err); + if (err) { + log_error("Could not rescan session sid %d.", info->sid); + return err; + } + /* rescan each device to pick up size changes */ + iscsi_sysfs_for_each_device(NULL, host_no, info->sid, + iscsi_sysfs_rescan_device); + /* now scan for new devices */ + iscsi_sysfs_scan_host(host_no, 0, 1); + return 0; +} + +static int +session_stats(void *data, struct session_info *info) +{ + int rc, i; + iscsiadm_req_t req; + iscsiadm_rsp_t rsp; + + if (!iscsi_match_session(data, info)) + return -1; + + memset(&req, 0, sizeof(req)); + req.command = MGMT_IPC_SESSION_STATS; + req.u.session.sid = info->sid; + + rc = iscsid_exec_req(&req, &rsp, 1, info->iscsid_req_tmo); + if (rc) + return rc; + + printf("Stats for session [sid: %d, target: %s, portal: " + "%s,%d]\n", + info->sid, info->targetname, info->persistent_address, + info->port); + + printf( "iSCSI SNMP:\n" + + "\ttxdata_octets: %lld\n" + "\trxdata_octets: %lld\n" + + "\tnoptx_pdus: %u\n" + "\tscsicmd_pdus: %u\n" + "\ttmfcmd_pdus: %u\n" + "\tlogin_pdus: %u\n" + "\ttext_pdus: %u\n" + "\tdataout_pdus: %u\n" + "\tlogout_pdus: %u\n" + "\tsnack_pdus: %u\n" + + "\tnoprx_pdus: %u\n" + "\tscsirsp_pdus: %u\n" + "\ttmfrsp_pdus: %u\n" + "\ttextrsp_pdus: %u\n" + "\tdatain_pdus: %u\n" + "\tlogoutrsp_pdus: %u\n" + "\tr2t_pdus: %u\n" + "\tasync_pdus: %u\n" + "\trjt_pdus: %u\n" + + "\tdigest_err: %u\n" + "\ttimeout_err: %u\n", + (unsigned long long)rsp.u.getstats.stats.txdata_octets, + (unsigned long long)rsp.u.getstats.stats.rxdata_octets, + + rsp.u.getstats.stats.noptx_pdus, + rsp.u.getstats.stats.scsicmd_pdus, + rsp.u.getstats.stats.tmfcmd_pdus, + rsp.u.getstats.stats.login_pdus, + rsp.u.getstats.stats.text_pdus, + rsp.u.getstats.stats.dataout_pdus, + rsp.u.getstats.stats.logout_pdus, + rsp.u.getstats.stats.snack_pdus, + + rsp.u.getstats.stats.noprx_pdus, + rsp.u.getstats.stats.scsirsp_pdus, + rsp.u.getstats.stats.tmfrsp_pdus, + rsp.u.getstats.stats.textrsp_pdus, + rsp.u.getstats.stats.datain_pdus, + rsp.u.getstats.stats.logoutrsp_pdus, + rsp.u.getstats.stats.r2t_pdus, + rsp.u.getstats.stats.async_pdus, + rsp.u.getstats.stats.rjt_pdus, + + rsp.u.getstats.stats.digest_err, + rsp.u.getstats.stats.timeout_err); + + if (rsp.u.getstats.stats.custom_length) + printf( "iSCSI Extended:\n"); + + for (i = 0; i < rsp.u.getstats.stats.custom_length; i++) { + printf("\t%s: %llu\n", rsp.u.getstats.stats.custom[i].desc, + (unsigned long long)rsp.u.getstats.stats.custom[i].value); + } + + return 0; +} + +static int add_static_rec(int *found, char *targetname, int tpgt, + char *ip, int port, struct iface_rec *iface) +{ + node_rec_t *rec; + discovery_rec_t *drec; + int rc; + + rec = calloc(1, sizeof(*rec)); + if (!rec) { + log_error("Could not allocate memory for node addition"); + rc = ISCSI_ERR_NOMEM; + goto done; + } + + drec = calloc(1, sizeof(*drec)); + if (!drec) { + log_error("Could not allocate memory for node addition"); + rc = ISCSI_ERR_NOMEM; + goto free_rec; + } + drec->type = DISCOVERY_TYPE_STATIC; + + idbm_node_setup_from_conf(rec); + strlcpy(rec->name, targetname, TARGET_NAME_MAXLEN); + rec->tpgt = tpgt; + rec->conn[0].port = port; + strlcpy(rec->conn[0].address, ip, NI_MAXHOST); + + if (iface) { + rc = iface_conf_read(iface); + if (rc) { + log_error("Could not read iface %s. Error %d", + iface->name, rc); + goto free_drec; + } + + iface_copy(&rec->iface, iface); + } + + rc = idbm_add_node(rec, drec, 1); + if (!rc) { + (*found)++; + printf("New iSCSI node [%s:" iface_fmt " %s,%d,%d %s] added\n", + rec->iface.transport_name, iface_str(&rec->iface), + ip, port, tpgt, targetname); + } +free_drec: + free(drec); +free_rec: + free(rec); +done: + return rc; +} + +static int add_static_portal(int *found, void *data, char *targetname, + int tpgt, char *ip, int port, bool ruw_lock) +{ + node_rec_t *rec = data; + + if (strlen(rec->conn[0].address) && + strcmp(rec->conn[0].address, ip)) + return -1; + + if (rec->conn[0].port != -1 && rec->conn[0].port != port) + return -1; + + return add_static_rec(found, targetname, tpgt, ip, port, + &rec->iface); +} + +static int add_static_node(int *found, void *data, + char *targetname, bool ruw_lock) +{ + node_rec_t *rec = data; + + if (!strlen(rec->name)) + goto search; + + if (strcmp(rec->name, targetname)) + return -1; + + if (!strlen(rec->conn[0].address)) + goto search; + + return add_static_rec(found, targetname, rec->tpgt, + rec->conn[0].address, + rec->conn[0].port, &rec->iface); +search: + return idbm_for_each_portal(found, data, add_static_portal, + targetname, false); +} + +static int add_static_recs(struct node_rec *rec) +{ + int rc, nr_found = 0; + + rc = idbm_for_each_node(&nr_found, rec, add_static_node, false); + if (rc) + goto done; + /* success */ + if (nr_found > 0) + return 0; + + /* brand new target */ + if (strlen(rec->name) && strlen(rec->conn[0].address)) { + rc = add_static_rec(&nr_found, rec->name, rec->tpgt, + rec->conn[0].address, rec->conn[0].port, + &rec->iface); + if (!rc) + return 0; + } +done: + log_error("Error while adding record: %s", iscsi_err_to_str(rc)); + return rc; +} + +/* + * start sendtargets discovery process based on the + * particular config + */ +static int +do_offload_sendtargets(discovery_rec_t *drec, int host_no, int do_login) +{ + drec->type = DISCOVERY_TYPE_OFFLOAD_SENDTARGETS; + return discovery_offload_sendtargets(host_no, do_login, drec); +} + +static int delete_node(void *data, struct node_rec *rec) +{ + if (iscsi_check_for_running_session(rec)) { + /* + * We could log out the session for the user, but if + * the session is being used the user may get something + * they were not expecting (FS errors and a read only + * remount). + */ + log_error("This command will remove the record [iface: %s, " + "target: %s, portal: %s,%d], but a session is " + "using it. Logout session then rerun command to " + "remove record.", rec->iface.name, rec->name, + rec->conn[0].address, rec->conn[0].port); + return ISCSI_ERR_SESS_EXISTS; + } + + return idbm_delete_node(rec); +} + +static int delete_stale_rec(void *data, struct node_rec *rec) +{ + struct list_head *new_rec_list = data; + struct node_rec *new_rec; + + list_for_each_entry(new_rec, new_rec_list, list) { + /* + * We could also move this to idbm.c and instead of looping + * over every node just loop over disc to node links. + */ + if (rec->disc_type != new_rec->disc_type || + rec->disc_port != new_rec->disc_port || + strcmp(rec->disc_address, new_rec->disc_address)) + /* + * if we are not from the same discovery source + * ignore it + */ + return -1; + + if (__iscsi_match_session(rec, + new_rec->name, + new_rec->conn[0].address, + new_rec->conn[0].port, + &new_rec->iface, + new_rec->session.sid)) + return -1; + } + /* if there is a error we can continue on */ + return delete_node(NULL, rec); +} + +static int +exec_disc_op_on_recs(discovery_rec_t *drec, struct list_head *rec_list, + int info_level, int do_login, int op) +{ + int rc = 0, err, found = 0; + struct node_rec *new_rec, tmp_rec; + + /* clean up node db */ + if (op & OP_DELETE) + idbm_for_each_rec(&found, rec_list, delete_stale_rec, false); + + if (op & OP_NEW || op & OP_UPDATE) { + /* now add/update records */ + list_for_each_entry(new_rec, rec_list, list) { + rc = idbm_add_node(new_rec, drec, op & OP_UPDATE); + if (rc) + log_error("Could not add/update " + "[%s:" iface_fmt " %s,%d,%d %s]", + new_rec->iface.transport_name, + iface_str(&new_rec->iface), + new_rec->conn[0].address, + new_rec->conn[0].port, + new_rec->tpgt, new_rec->name); + } + } + + memset(&tmp_rec, 0, sizeof(node_rec_t)); + list_for_each_entry(new_rec, rec_list, list) { + switch (info_level) { + case 0: + case -1: + idbm_print_node_flat(NULL, new_rec); + break; + case 1: + idbm_print_node_and_iface_tree(&tmp_rec, new_rec); + } + + } + + if (!do_login) + return 0; + + err = iscsi_login_portals(NULL, &found, 1, rec_list, + iscsi_login_portal); + if (err && !rc) + rc = err; + return rc; +} + +static int +do_software_sendtargets(discovery_rec_t *drec, struct list_head *ifaces, + int info_level, int do_login, int op, int sync_drec) +{ + LIST_HEAD(rec_list); + struct node_rec *rec, *tmp; + int rc; + + /* + * compat: if the user did not pass any op then we do all + * ops for them + */ + if (!op) + op = OP_NEW | OP_DELETE | OP_UPDATE; + + drec->type = DISCOVERY_TYPE_SENDTARGETS; + /* + * we will probably want to know how a specific iface and discovery + * DB lined up, but for now just put all the targets found from + * a discovery portal in one place + */ + if ((!(op & OP_NONPERSISTENT)) && sync_drec) { + rc = idbm_add_discovery(drec); + if (rc) { + log_error("Could not add new discovery record."); + return rc; + } + } + + rc = idbm_bind_ifaces_to_nodes(discovery_sendtargets, drec, ifaces, + &rec_list); + if (rc) { + log_error("Could not perform SendTargets discovery: %s", + iscsi_err_to_str(rc)); + return rc; + } else if (list_empty(&rec_list)) { + log_error("No portals found"); + return ISCSI_ERR_NO_OBJS_FOUND; + } + + rc = exec_disc_op_on_recs(drec, &rec_list, info_level, do_login, op); + + list_for_each_entry_safe(rec, tmp, &rec_list, list) { + list_del(&rec->list); + free(rec); + } + + return rc; +} + +static int do_isns(discovery_rec_t *drec, struct list_head *ifaces, + int info_level, int do_login, int op) +{ + LIST_HEAD(rec_list); + struct node_rec *rec, *tmp; + int rc; + + /* + * compat: if the user did not pass any op then we do all + * ops for them + */ + if (!op) + op = OP_NEW | OP_DELETE | OP_UPDATE; + + + rc = idbm_bind_ifaces_to_nodes(discovery_isns, drec, ifaces, + &rec_list); + if (rc) { + log_error("Could not perform iSNS discovery: %s", + iscsi_err_to_str(rc)); + return rc; + } else if (list_empty(&rec_list)) { + log_error("No portals found"); + return ISCSI_ERR_NO_OBJS_FOUND; + } + + rc = exec_disc_op_on_recs(drec, &rec_list, info_level, do_login, op); + + list_for_each_entry_safe(rec, tmp, &rec_list, list) { + list_del(&rec->list); + free(rec); + } + + return rc; +} + +static int +do_target_discovery(discovery_rec_t *drec, struct list_head *ifaces, + int info_level, int do_login, int op, int sync_drec) +{ + + struct iface_rec *tmp, *iface; + int rc, host_no; + struct iscsi_transport *t; + + if (list_empty(ifaces)) { + ifaces = NULL; + goto sw_discovery; + } + + /* we allow users to mix hw and sw iscsi so we have to sort it out */ + list_for_each_entry_safe(iface, tmp, ifaces, list) { + rc = iface_conf_read(iface); + if (rc) { + log_error("Could not read iface info for %s. " + "Make sure an iface config with the file " + "name and iface.iscsi_ifacename %s is in %s.", + iface->name, iface->name, IFACE_CONFIG_DIR); + list_del(&iface->list); + free(iface); + continue; + } + /* check for transport name first to make sure it is loaded */ + t = iscsi_sysfs_get_transport_by_name(iface->transport_name); + if (!t) { + log_error("Could not load transport %s." + "Dropping interface %s.", + iface->transport_name, iface->name); + list_del(&iface->list); + free(iface); + continue; + } + + host_no = iscsi_sysfs_get_host_no_from_hwinfo(iface, &rc); + if (rc || host_no == -1) { + log_debug(1, "Could not match iface" iface_fmt " to " + "host.", iface_str(iface)); + /* try software iscsi */ + continue; + } + + if (drec->type == DISCOVERY_TYPE_SENDTARGETS) + if (t->caps & CAP_SENDTARGETS_OFFLOAD) { + do_offload_sendtargets(drec, host_no, do_login); + list_del(&iface->list); + free(iface); + } + } + + if (list_empty(ifaces)) + return ISCSI_ERR_NO_OBJS_FOUND; + +sw_discovery: + switch (drec->type) { + case DISCOVERY_TYPE_SENDTARGETS: + return do_software_sendtargets(drec, ifaces, info_level, + do_login, op, sync_drec); + case DISCOVERY_TYPE_ISNS: + return do_isns(drec, ifaces, info_level, do_login, op); + default: + log_debug(1, "Unknown Discovery Type : %d", drec->type); + return ISCSI_ERR_UNKNOWN_DISCOVERY_TYPE; + } +} + + +static int +verify_mode_params(int argc, char **argv, char *allowed, int skip_m) +{ + int ch, longindex; + int ret = 0; + + optind = 0; + + while ((ch = getopt_long(argc, argv, short_options, + long_options, &longindex)) >= 0) { + if (!strchr(allowed, ch)) { + if (ch == 'm' && skip_m) + continue; + ret = ch; + break; + } + } + + return ret; +} + +static void catch_sigint( int signo ) { + log_warning("caught SIGINT, exiting..."); + exit(1); +} + +static int iface_apply_net_config(struct iface_rec *iface, int op) +{ + int rc = ISCSI_ERR; + uint32_t host_no; + int param_count; + int param_used; + int iface_all = 0; + int i; + struct iovec *iovs = NULL; + struct iovec *iov = NULL; + struct iscsi_transport *t = NULL; + int fd; + + log_debug(8, "Calling iscsid, to apply net config for" + "iface.name = %s", iface->name); + + if (op == OP_APPLY_ALL) + iface_all = 1; + + param_count = iface_get_param_count(iface, iface_all); + if (!param_count) { + log_error("Nothing to configure."); + return ISCSI_SUCCESS; + } + + /* + * TODO: create a nicer interface where the caller does not have + * know the packet/hdr details + */ + + /* +2 for event and nlmsghdr */ + param_count += 2; + iovs = calloc((param_count * sizeof(struct iovec)), + sizeof(char)); + if (!iovs) { + log_error("Out of Memory."); + return ISCSI_ERR_NOMEM; + } + + /* param_used gives actual number of iovecs used for netconfig */ + param_used = iface_build_net_config(iface, iface_all, iovs); + if (!param_used) { + log_error("Build netconfig failed."); + goto free_buf; + } + + t = iscsi_sysfs_get_transport_by_name(iface->transport_name); + if (!t) { + log_error("Can't find transport."); + goto free_buf; + } + + host_no = iscsi_sysfs_get_host_no_from_hwinfo(iface, &rc); + if (host_no == -1) { + log_error("Can't find host_no."); + goto free_buf; + } + rc = ISCSI_ERR; + + fd = ipc->ctldev_open(); + if (fd < 0) { + log_error("Netlink open failed."); + goto free_buf; + } + + rc = ipc->set_net_config(t->handle, host_no, iovs, param_count); + if (rc < 0) + log_error("Set net_config failed. errno=%d", errno); + + ipc->ctldev_close(); + +free_buf: + /* start at 2, because 0 is for nlmsghdr and 1 for event */ + iov = iovs + 2; + for (i = 0; i < param_used; i++, iov++) { + if (iov->iov_base) + free(iov->iov_base); + } + + free(iovs); + if (rc) + return ISCSI_ERR; + return ISCSI_SUCCESS; +} + +static int get_host_chap_info(uint32_t host_no) +{ + struct iscsi_transport *t = NULL; + struct iscsi_chap_rec *crec = NULL; + char *req_buf = NULL; + uint32_t valid_chap_entries; + uint32_t num_entries; + uint16_t chap_tbl_idx = 0; + int rc = 0; + int fd, i = 0; + + t = iscsi_sysfs_get_transport_by_hba(host_no); + if (!t) { + log_error("Could not match hostno %d to " + "transport.", host_no); + rc = ISCSI_ERR_TRANS_NOT_FOUND; + goto exit_chap_info; + } + + num_entries = MAX_CHAP_BUF_SZ / sizeof(*crec); + + req_buf = calloc(1, REQ_CHAP_BUF_SZ); + if (!req_buf) { + log_error("Could not allocate memory for CHAP request."); + rc = ISCSI_ERR_NOMEM; + goto exit_chap_info; + } + + fd = ipc->ctldev_open(); + if (fd < 0) { + rc = ISCSI_ERR_INTERNAL; + log_error("Netlink open failed."); + goto exit_chap_info; + } + +get_chap: + memset(req_buf, 0, REQ_CHAP_BUF_SZ); + + rc = ipc->get_chap(t->handle, host_no, chap_tbl_idx, num_entries, + req_buf, &valid_chap_entries); + if (rc < 0) { + log_error("get_chap_info failed. errno=%d", errno); + rc = ISCSI_ERR; + goto exit_chap_info; + } + + crec = (struct iscsi_chap_rec *) (req_buf + + sizeof(struct iscsi_uevent)); + + if (valid_chap_entries) + chap_tbl_idx = + (crec + (valid_chap_entries - 1))->chap_tbl_idx + 1; + + /* print chap info */ + for (i = 0; i < valid_chap_entries; i++) { + idbm_print_host_chap_info(crec); + crec++; + } + + if (valid_chap_entries != num_entries) + goto exit_chap_info; + else + goto get_chap; + + ipc->ctldev_close(); + +exit_chap_info: + if (req_buf) + free(req_buf); + + return rc; +} + +static int fill_host_chap_rec(struct list_head *params, + struct iscsi_chap_rec *crec, recinfo_t *cinfo, + uint16_t chap_tbl_idx, int type, int *param_count) +{ + struct user_param *param; + int rc = 0; + + crec->chap_tbl_idx = chap_tbl_idx; + crec->chap_type = type; + + idbm_recinfo_host_chap(crec, cinfo); + + list_for_each_entry(param, params, list) { + rc = idbm_rec_update_param(cinfo, param->name, param->value, 0); + if (rc) + break; + } + + if (!rc) + *param_count += 3; /* index, type and password_length */ + + return rc; +} + +static int verify_host_chap_params(struct list_head *params, int *type, + int *param_count) +{ + struct user_param *param; + int username = -1; + int password = -1; + int rc = 0; + + list_for_each_entry(param, params, list) { + *param_count += 1; + + if (!strcmp(param->name, HOST_AUTH_USERNAME)) + username = CHAP_TYPE_OUT; + else if (!strcmp(param->name, HOST_AUTH_PASSWORD)) + password = CHAP_TYPE_OUT; + else if (!strcmp(param->name, HOST_AUTH_USERNAME_IN)) + username = CHAP_TYPE_IN; + else if (!strcmp(param->name, HOST_AUTH_PASSWORD_IN)) + password = CHAP_TYPE_IN; + else + continue; + } + + if ((username == CHAP_TYPE_OUT) && (password == CHAP_TYPE_OUT)) { + if (type) + *type = CHAP_TYPE_OUT; + + rc = ISCSI_SUCCESS; + } else if ((username == CHAP_TYPE_IN) && (password == CHAP_TYPE_IN)) { + if (type) + *type = CHAP_TYPE_IN; + + rc = ISCSI_SUCCESS; + } else { + rc = ISCSI_ERR; + } + + return rc; +} + +static int set_host_chap_info(uint32_t host_no, uint64_t chap_index, + struct list_head *params) +{ + struct iscsi_transport *t = NULL; + struct iscsi_chap_rec crec; + recinfo_t *chap_info = NULL; + struct iovec *iovs = NULL; + struct iovec *iov = NULL; + int type; + int param_count = 0; + int param_used; + int rc = 0; + int fd, i = 0; + + if (list_empty(params)) { + log_error("Chap username/password not provided."); + goto exit_set_chap; + } + + chap_info = idbm_recinfo_alloc(MAX_KEYS); + if (!chap_info) { + log_error("Out of Memory."); + rc = ISCSI_ERR_NOMEM; + goto exit_set_chap; + } + + t = iscsi_sysfs_get_transport_by_hba(host_no); + if (!t) { + log_error("Could not match hostno %d to transport.", host_no); + rc = ISCSI_ERR_TRANS_NOT_FOUND; + goto free_info_rec; + } + + rc = verify_host_chap_params(params, &type, ¶m_count); + if (rc) { + log_error("Invalid username/password pair passed. Unable to determine the type of chap entry"); + rc = ISCSI_ERR_INVAL; + goto free_info_rec; + } + + if (param_count > 2) { + log_error("Only one pair of username/password can be passed."); + rc = ISCSI_ERR; + goto free_info_rec; + } + + memset(&crec, 0, sizeof(crec)); + rc = fill_host_chap_rec(params, &crec, chap_info, chap_index, type, + ¶m_count); + if (rc) { + log_error("Unable to fill CHAP record"); + goto free_info_rec; + } + + /* +2 for event and nlmsghdr */ + param_count += 2; + iovs = calloc((param_count * sizeof(struct iovec)), + sizeof(char)); + if (!iovs) { + log_error("Out of Memory."); + rc = ISCSI_ERR_NOMEM; + goto free_info_rec; + } + + /* param_used gives actual number of iovecs used for chap */ + param_used = chap_build_config(&crec, iovs); + if (!param_used) { + log_error("Build chap config failed."); + rc = ISCSI_ERR; + goto free_iovec; + } + + fd = ipc->ctldev_open(); + if (fd < 0) { + rc = ISCSI_ERR_INTERNAL; + log_error("Netlink open failed."); + goto free_iovec; + } + + rc = ipc->set_chap(t->handle, host_no, iovs, param_count); + if (rc < 0) { + log_error("CHAP setting failed"); + if (rc == -EBUSY) { + rc = ISCSI_ERR_BUSY; + log_error("CHAP index %d is in use.", + crec.chap_tbl_idx); + } else { + rc = ISCSI_ERR; + } + + goto free_iovec; + } + + ipc->ctldev_close(); + +free_iovec: + /* start at 2, because 0 is for nlmsghdr and 1 for event */ + iov = iovs + 2; + for (i = 0; i < param_used; i++, iov++) { + if (iov->iov_base) + free(iov->iov_base); + } + + free(iovs); + +free_info_rec: + if (chap_info) + free(chap_info); + +exit_set_chap: + return rc; +} + +static int delete_host_chap_info(uint32_t host_no, uint16_t chap_tbl_idx) +{ + struct iscsi_transport *t = NULL; + int fd, rc = 0; + + if (chap_tbl_idx > MAX_CHAP_ENTRIES) { + log_error("Invalid chap table index."); + goto exit_delete_chap; + } + + t = iscsi_sysfs_get_transport_by_hba(host_no); + if (!t) { + log_error("Could not match hostno %d to " + "transport.", host_no); + rc = ISCSI_ERR_TRANS_NOT_FOUND; + goto exit_delete_chap; + } + + fd = ipc->ctldev_open(); + if (fd < 0) { + log_error("Netlink open failed."); + rc = ISCSI_ERR_INTERNAL; + goto exit_delete_chap; + } + + log_info("Deleting CHAP index: %d", chap_tbl_idx); + rc = ipc->delete_chap(t->handle, host_no, chap_tbl_idx); + if (rc < 0) { + log_error("CHAP Delete failed."); + if (rc == -EBUSY) { + rc = ISCSI_ERR_BUSY; + log_error("CHAP index %d is in use.", chap_tbl_idx); + } else + rc = ISCSI_ERR; + } + + ipc->ctldev_close(); + +exit_delete_chap: + return rc; +} + +static int exec_host_chap_op(int op, int info_level, uint32_t host_no, + uint64_t chap_index, struct list_head *params) +{ + int rc = ISCSI_ERR_INVAL; + + switch (op) { + case OP_SHOW: + rc = get_host_chap_info(host_no); + break; + case OP_NEW: + case OP_UPDATE: + rc = set_host_chap_info(host_no, chap_index, params); + break; + case OP_DELETE: + rc = delete_host_chap_info(host_no, chap_index); + break; + default: + log_error("Invalid operation."); + break; + } + + return rc; +} + +static int get_flashnode_info(uint32_t host_no, uint32_t flashnode_idx) +{ + struct flashnode_rec fnode; + int rc = 0; + + memset(&fnode, 0, sizeof(fnode)); + rc = iscsi_sysfs_get_flashnode_info(&fnode, host_no, flashnode_idx); + if (rc) { + log_error("Could not read info for flashnode %u of host %u, %s", + flashnode_idx, host_no, strerror(rc)); + return rc; + } + + idbm_print_flashnode_info(&fnode); + return rc; +} + +static int list_flashnodes(int info_level, uint32_t host_no) +{ + int rc = 0; + int num_found = 0; + + rc = iscsi_sysfs_for_each_flashnode(NULL, host_no, &num_found, + flashnode_info_print_flat); + + if (!num_found) { + log_error("No flashnodes attached to host %u.", host_no); + rc = ISCSI_ERR_NO_OBJS_FOUND; + } + + return rc; +} + +int iscsi_set_flashnode_params(struct iscsi_transport *t, uint32_t host_no, + uint32_t flashnode_idx, struct list_head *params) +{ + struct flashnode_rec fnode; + recinfo_t *flashnode_info; + struct user_param *param; + struct iovec *iovs = NULL; + struct iovec *iov = NULL; + int fd, rc = 0; + int param_count = 0; + int param_used = 0; + int i; + + flashnode_info = idbm_recinfo_alloc(MAX_KEYS); + if (!flashnode_info) { + log_error("Out of Memory."); + rc = ISCSI_ERR_NOMEM; + goto free_info_rec; + } + + memset(&fnode, 0, sizeof(fnode)); + rc = iscsi_sysfs_get_flashnode_info(&fnode, host_no, flashnode_idx); + if (rc) { + log_error("Could not read info for flashnode %u, %s", + flashnode_idx, strerror(rc)); + goto free_info_rec; + } + + idbm_recinfo_flashnode(&fnode, flashnode_info); + + i = 0; + list_for_each_entry(param, params, list) { + param_count++; + rc = idbm_verify_param(flashnode_info, param->name); + if (rc) + goto free_info_rec; + } + + list_for_each_entry(param, params, list) { + rc = idbm_rec_update_param(flashnode_info, param->name, + param->value, 0); + if (rc) + goto free_info_rec; + } + + /* +2 for event and nlmsghdr */ + param_count += 2; + iovs = calloc((param_count * sizeof(struct iovec)), + sizeof(char)); + if (!iovs) { + log_error("Out of Memory."); + rc = ISCSI_ERR_NOMEM; + goto free_info_rec; + } + + /* param_used gives actual number of iovecs used for flashnode */ + param_used = flashnode_build_config(params, &fnode, iovs); + if (!param_used) { + log_error("Build flashnode config failed."); + rc = ISCSI_ERR; + goto free_iovec; + } + + fd = ipc->ctldev_open(); + if (fd < 0) { + log_error("Netlink open failed."); + rc = ISCSI_ERR_INTERNAL; + goto free_iovec; + } + + log_info("Update flashnode %u.", flashnode_idx); + rc = ipc->set_flash_node_params(t->handle, host_no, flashnode_idx, + iovs, param_count); + if (rc < 0) + rc = ISCSI_ERR; + + + ipc->ctldev_close(); + +free_iovec: + /* start at 2, because 0 is for nlmsghdr and 1 for event */ + iov = iovs + 2; + for (i = 0; i < param_used; i++, iov++) { + if (iov->iov_base) + free(iov->iov_base); + } + + free(iovs); + +free_info_rec: + if (flashnode_info) + free(flashnode_info); + + return rc; +} + +int iscsi_new_flashnode(struct iscsi_transport *t, uint32_t host_no, char *val, + uint32_t *flashnode_idx) +{ + int fd, rc = 0; + + fd = ipc->ctldev_open(); + if (fd < 0) { + log_error("Netlink open failed."); + rc = ISCSI_ERR_INTERNAL; + goto exit_new_flashnode; + } + + log_info("Create new flashnode for host %u.", host_no); + rc = ipc->new_flash_node(t->handle, host_no, val, flashnode_idx); + if (rc < 0) + rc = ISCSI_ERR; + + ipc->ctldev_close(); + +exit_new_flashnode: + return rc; +} + +int iscsi_del_flashnode(struct iscsi_transport *t, uint32_t host_no, + uint32_t flashnode_idx) +{ + int fd, rc = 0; + + fd = ipc->ctldev_open(); + if (fd < 0) { + log_error("Netlink open failed."); + rc = ISCSI_ERR_INTERNAL; + goto exit_del_flashnode; + } + + log_info("Delete flashnode %u.", flashnode_idx); + rc = ipc->del_flash_node(t->handle, host_no, flashnode_idx); + if (rc < 0) + rc = ISCSI_ERR; + + ipc->ctldev_close(); + +exit_del_flashnode: + return rc; +} + +int iscsi_login_flashnode(struct iscsi_transport *t, uint32_t host_no, + uint32_t flashnode_idx) +{ + int fd, rc = 0; + + fd = ipc->ctldev_open(); + if (fd < 0) { + log_error("Netlink open failed."); + rc = ISCSI_ERR_INTERNAL; + goto exit_login_flashnode; + } + + log_info("Login to flashnode %u.", flashnode_idx); + rc = ipc->login_flash_node(t->handle, host_no, flashnode_idx); + if (rc == -EPERM) + rc = ISCSI_ERR_SESS_EXISTS; + else if (rc < 0) + rc = ISCSI_ERR_LOGIN; + + ipc->ctldev_close(); + +exit_login_flashnode: + return rc; +} + +int iscsi_logout_flashnode(struct iscsi_transport *t, uint32_t host_no, + uint32_t flashnode_idx) +{ + int fd, rc = 0; + + fd = ipc->ctldev_open(); + if (fd < 0) { + log_error("Netlink open failed."); + rc = ISCSI_ERR_INTERNAL; + goto exit_logout; + } + + log_info("Logout flashnode %u.", flashnode_idx); + rc = ipc->logout_flash_node(t->handle, host_no, flashnode_idx); + if (rc == -ESRCH) + rc = ISCSI_ERR_SESS_NOT_FOUND; + else if (rc < 0) + rc = ISCSI_ERR_LOGOUT; + + ipc->ctldev_close(); + +exit_logout: + return rc; +} + +static int iscsi_check_session_use_count(uint32_t sid) { + char *config_file; + char *safe_logout; + + config_file = get_config_file(); + if (!config_file) { + log_error("Could not get config file from iscsid"); + return 0; + } + + safe_logout = cfg_get_string_param(config_file, "iscsid.safe_logout"); + if (!safe_logout || strcmp(safe_logout, "Yes")) + return 0; + + return session_in_use(sid); +} + +int iscsi_logout_flashnode_sid(struct iscsi_transport *t, uint32_t host_no, + uint32_t sid) +{ + int fd, rc = 0; + + if (iscsi_check_session_use_count(sid)) { + log_error("Session is actively in use for mounted storage, " + "and iscsid.safe_logout is configured."); + return ISCSI_ERR_BUSY; + } + + fd = ipc->ctldev_open(); + if (fd < 0) { + log_error("Netlink open failed."); + rc = ISCSI_ERR_INTERNAL; + goto exit_logout_sid; + } + + log_info("Logout sid %u.", sid); + rc = ipc->logout_flash_node_sid(t->handle, host_no, sid); + if (rc < 0) { + log_error("Logout of sid %u failed.", sid); + rc = ISCSI_ERR_LOGOUT; + } else { + log_info("Logout of sid %u successful.", sid); + } + + ipc->ctldev_close(); + +exit_logout_sid: + return rc; +} + +static int exec_flashnode_op(int op, int info_level, uint32_t host_no, + uint64_t fnode_idx, int type, + struct list_head *params) +{ + struct iscsi_transport *t = NULL; + int rc = ISCSI_SUCCESS; + char *portal_type; + uint32_t flashnode_idx; + + if (op != OP_SHOW && op != OP_NOOP && op != OP_NEW && + fnode_idx > MAX_FLASHNODE_IDX) { + log_error("Invalid flashnode index"); + rc = ISCSI_ERR_INVAL; + goto exit_flashnode_op; + } + + flashnode_idx = (uint32_t)fnode_idx; + t = iscsi_sysfs_get_transport_by_hba(host_no); + if (!t) { + log_error("Could not match hostno %u to transport.", host_no); + rc = ISCSI_ERR_TRANS_NOT_FOUND; + goto exit_flashnode_op; + } + + switch (op) { + case OP_NOOP: + case OP_SHOW: + if (fnode_idx > MAX_FLASHNODE_IDX) + rc = list_flashnodes(info_level, host_no); + else + rc = get_flashnode_info(host_no, flashnode_idx); + break; + case OP_NEW: + if (type == IPV4) { + portal_type = "ipv4"; + } else if (type == IPV6) { + portal_type = "ipv6"; + } else { + log_error("Invalid type mentioned for flashnode"); + rc = ISCSI_ERR_INVAL; + goto exit_flashnode_op; + } + rc = iscsi_new_flashnode(t, host_no, portal_type, + &flashnode_idx); + if (!rc) + log_info("New flashnode for host %u added at index %u.", + host_no, flashnode_idx); + else + log_error("Creation of flashnode for host %u failed.", + host_no); + break; + case OP_DELETE: + rc = iscsi_del_flashnode(t, host_no, flashnode_idx); + if (!rc) + log_info("Flashnode %u of host %u deleted.", + flashnode_idx, host_no); + else + log_error("Deletion of flashnode %u of host %u failed.", + flashnode_idx, host_no); + break; + case OP_UPDATE: + rc = iscsi_set_flashnode_params(t, host_no, flashnode_idx, + params); + if (!rc) + log_info("Update for flashnode %u of host %u successful.", + flashnode_idx, host_no); + else + log_error("Update for flashnode %u of host %u failed.", + flashnode_idx, host_no); + break; + case OP_LOGIN: + rc = iscsi_login_flashnode(t, host_no, flashnode_idx); + if (!rc) + log_info("Login to flashnode %u of host %u successful.", + flashnode_idx, host_no); + else if (rc == ISCSI_ERR_SESS_EXISTS) + log_info("Flashnode %u of host %u already logged in.", + flashnode_idx, host_no); + else + log_error("Login to flashnode %u of host %u failed.", + flashnode_idx, host_no); + break; + case OP_LOGOUT: + rc = iscsi_logout_flashnode(t, host_no, flashnode_idx); + if (!rc) + log_info("Logout of flashnode %u of host %u successful.", + flashnode_idx, host_no); + else if (rc == ISCSI_ERR_SESS_NOT_FOUND) + log_info("Flashnode %u of host %u not logged in.", + flashnode_idx, host_no); + else + log_error("Logout of flashnode %u of host %u failed.", + flashnode_idx, host_no); + break; + default: + log_error("Invalid operation"); + rc = ISCSI_ERR_INVAL; + break; + } + +exit_flashnode_op: + return rc; +} + +static void print_host_stats(struct iscsi_offload_host_stats *host_stats) +{ + /* MAC */ + printf("Host Statistics:\n" + "\tmactx_frames: %lld\n" + "\tmactx_bytes: %lld\n" + "\tmactx_multicast_frames: %lld\n" + "\tmactx_broadcast_frames: %lld\n" + "\tmactx_pause_frames: %lld\n" + "\tmactx_control_frames: %lld\n" + "\tmactx_deferral: %lld\n" + "\tmactx_excess_deferral: %lld\n" + "\tmactx_late_collision: %lld\n" + "\tmactx_abort: %lld\n" + "\tmactx_single_collision: %lld\n" + "\tmactx_multiple_collision: %lld\n" + "\tmactx_collision: %lld\n" + "\tmactx_frames_dropped: %lld\n" + "\tmactx_jumbo_frames: %lld\n" + "\tmacrx_frames: %lld\n" + "\tmacrx_bytes: %lld\n" + "\tmacrx_unknown_control_frames: %lld\n" + "\tmacrx_pause_frames: %lld\n" + "\tmacrx_control_frames: %lld\n" + "\tmacrx_dribble: %lld\n" + "\tmacrx_frame_length_error: %lld\n" + "\tmacrx_jabber: %lld\n" + "\tmacrx_carrier_sense_error: %lld\n" + "\tmacrx_frame_discarded: %lld\n" + "\tmacrx_frames_dropped: %lld\n" + "\tmac_crc_error: %lld\n" + "\tmac_encoding_error: %lld\n" + "\tmacrx_length_error_large: %lld\n" + "\tmacrx_length_error_small: %lld\n" + "\tmacrx_multicast_frames: %lld\n" + "\tmacrx_broadcast_frames: %lld\n" + /* IP */ + "\tiptx_packets: %lld\n" + "\tiptx_bytes: %lld\n" + "\tiptx_fragments: %lld\n" + "\tiprx_packets: %lld\n" + "\tiprx_bytes: %lld\n" + "\tiprx_fragments: %lld\n" + "\tip_datagram_reassembly: %lld\n" + "\tip_invalid_address_error: %lld\n" + "\tip_error_packets: %lld\n" + "\tip_fragrx_overlap: %lld\n" + "\tip_fragrx_outoforder: %lld\n" + "\tip_datagram_reassembly_timeout: %lld\n" + "\tipv6tx_packets: %lld\n" + "\tipv6tx_bytes: %lld\n" + "\tipv6tx_fragments: %lld\n" + "\tipv6rx_packets: %lld\n" + "\tipv6rx_bytes: %lld\n" + "\tipv6rx_fragments: %lld\n" + "\tipv6_datagram_reassembly: %lld\n" + "\tipv6_invalid_address_error: %lld\n" + "\tipv6_error_packets: %lld\n" + "\tipv6_fragrx_overlap: %lld\n" + "\tipv6_fragrx_outoforder: %lld\n" + "\tipv6_datagram_reassembly_timeout: %lld\n" + /* TCP */ + "\ttcptx_segments: %lld\n" + "\ttcptx_bytes: %lld\n" + "\ttcprx_segments: %lld\n" + "\ttcprx_byte: %lld\n" + "\ttcp_duplicate_ack_retx: %lld\n" + "\ttcp_retx_timer_expired: %lld\n" + "\ttcprx_duplicate_ack: %lld\n" + "\ttcprx_pure_ackr: %lld\n" + "\ttcptx_delayed_ack: %lld\n" + "\ttcptx_pure_ack: %lld\n" + "\ttcprx_segment_error: %lld\n" + "\ttcprx_segment_outoforder: %lld\n" + "\ttcprx_window_probe: %lld\n" + "\ttcprx_window_update: %lld\n" + "\ttcptx_window_probe_persist: %lld\n" + /* ECC */ + "\tecc_error_correction: %lld\n" + /* iSCSI */ + "\tiscsi_pdu_tx: %lld\n" + "\tiscsi_data_bytes_tx: %lld\n" + "\tiscsi_pdu_rx: %lld\n" + "\tiscsi_data_bytes_rx: %lld\n" + "\tiscsi_io_completed: %lld\n" + "\tiscsi_unexpected_io_rx: %lld\n" + "\tiscsi_format_error: %lld\n" + "\tiscsi_hdr_digest_error: %lld\n" + "\tiscsi_data_digest_error: %lld\n" + "\tiscsi_sequence_error: %lld\n", + /* MAC */ + (unsigned long long)host_stats->mactx_frames, + (unsigned long long)host_stats->mactx_bytes, + (unsigned long long)host_stats->mactx_multicast_frames, + (unsigned long long)host_stats->mactx_broadcast_frames, + (unsigned long long)host_stats->mactx_pause_frames, + (unsigned long long)host_stats->mactx_control_frames, + (unsigned long long)host_stats->mactx_deferral, + (unsigned long long)host_stats->mactx_excess_deferral, + (unsigned long long)host_stats->mactx_late_collision, + (unsigned long long)host_stats->mactx_abort, + (unsigned long long)host_stats->mactx_single_collision, + (unsigned long long)host_stats->mactx_multiple_collision, + (unsigned long long)host_stats->mactx_collision, + (unsigned long long)host_stats->mactx_frames_dropped, + (unsigned long long)host_stats->mactx_jumbo_frames, + (unsigned long long)host_stats->macrx_frames, + (unsigned long long)host_stats->macrx_bytes, + (unsigned long long)host_stats->macrx_unknown_control_frames, + (unsigned long long)host_stats->macrx_pause_frames, + (unsigned long long)host_stats->macrx_control_frames, + (unsigned long long)host_stats->macrx_dribble, + (unsigned long long)host_stats->macrx_frame_length_error, + (unsigned long long)host_stats->macrx_jabber, + (unsigned long long)host_stats->macrx_carrier_sense_error, + (unsigned long long)host_stats->macrx_frame_discarded, + (unsigned long long)host_stats->macrx_frames_dropped, + (unsigned long long)host_stats->mac_crc_error, + (unsigned long long)host_stats->mac_encoding_error, + (unsigned long long)host_stats->macrx_length_error_large, + (unsigned long long)host_stats->macrx_length_error_small, + (unsigned long long)host_stats->macrx_multicast_frames, + (unsigned long long)host_stats->macrx_broadcast_frames, + /* IP */ + (unsigned long long)host_stats->iptx_packets, + (unsigned long long)host_stats->iptx_bytes, + (unsigned long long)host_stats->iptx_fragments, + (unsigned long long)host_stats->iprx_packets, + (unsigned long long)host_stats->iprx_bytes, + (unsigned long long)host_stats->iprx_fragments, + (unsigned long long)host_stats->ip_datagram_reassembly, + (unsigned long long)host_stats->ip_invalid_address_error, + (unsigned long long)host_stats->ip_error_packets, + (unsigned long long)host_stats->ip_fragrx_overlap, + (unsigned long long)host_stats->ip_fragrx_outoforder, + (unsigned long long)host_stats->ip_datagram_reassembly_timeout, + (unsigned long long)host_stats->ipv6tx_packets, + (unsigned long long)host_stats->ipv6tx_bytes, + (unsigned long long)host_stats->ipv6tx_fragments, + (unsigned long long)host_stats->ipv6rx_packets, + (unsigned long long)host_stats->ipv6rx_bytes, + (unsigned long long)host_stats->ipv6rx_fragments, + (unsigned long long)host_stats->ipv6_datagram_reassembly, + (unsigned long long)host_stats->ipv6_invalid_address_error, + (unsigned long long)host_stats->ipv6_error_packets, + (unsigned long long)host_stats->ipv6_fragrx_overlap, + (unsigned long long)host_stats->ipv6_fragrx_outoforder, + (unsigned long long)host_stats->ipv6_datagram_reassembly_timeout, + /* TCP */ + (unsigned long long)host_stats->tcptx_segments, + (unsigned long long)host_stats->tcptx_bytes, + (unsigned long long)host_stats->tcprx_segments, + (unsigned long long)host_stats->tcprx_byte, + (unsigned long long)host_stats->tcp_duplicate_ack_retx, + (unsigned long long)host_stats->tcp_retx_timer_expired, + (unsigned long long)host_stats->tcprx_duplicate_ack, + (unsigned long long)host_stats->tcprx_pure_ackr, + (unsigned long long)host_stats->tcptx_delayed_ack, + (unsigned long long)host_stats->tcptx_pure_ack, + (unsigned long long)host_stats->tcprx_segment_error, + (unsigned long long)host_stats->tcprx_segment_outoforder, + (unsigned long long)host_stats->tcprx_window_probe, + (unsigned long long)host_stats->tcprx_window_update, + (unsigned long long)host_stats->tcptx_window_probe_persist, + /* ECC */ + (unsigned long long)host_stats->ecc_error_correction, + /* iSCSI */ + (unsigned long long)host_stats->iscsi_pdu_tx, + (unsigned long long)host_stats->iscsi_data_bytes_tx, + (unsigned long long)host_stats->iscsi_pdu_rx, + (unsigned long long)host_stats->iscsi_data_bytes_rx, + (unsigned long long)host_stats->iscsi_io_completed, + (unsigned long long)host_stats->iscsi_unexpected_io_rx, + (unsigned long long)host_stats->iscsi_format_error, + (unsigned long long)host_stats->iscsi_hdr_digest_error, + (unsigned long long)host_stats->iscsi_data_digest_error, + (unsigned long long)host_stats->iscsi_sequence_error); +} + +static int exec_host_stats_op(int op, int info_level, uint32_t host_no) +{ + struct iscsi_transport *t = NULL; + char *req_buf = NULL; + int rc = ISCSI_SUCCESS; + int fd = 0, buf_size = 0; + + t = iscsi_sysfs_get_transport_by_hba(host_no); + if (!t) { + log_error("Could not match hostno %u to transport.", host_no); + rc = ISCSI_ERR_TRANS_NOT_FOUND; + goto exit_host_stats; + } + + buf_size = sizeof(struct iscsi_offload_host_stats) + + sizeof(struct iscsi_uevent); + req_buf = calloc(1, buf_size); + if (!req_buf) { + log_error("Could not allocate memory for host stats request."); + rc = ISCSI_ERR_NOMEM; + goto exit_host_stats; + } + + fd = ipc->ctldev_open(); + if (fd < 0) { + rc = ISCSI_ERR_INTERNAL; + log_error("Netlink open failed."); + goto exit_host_stats; + } + + rc = ipc->get_host_stats(t->handle, host_no, req_buf); + if (rc < 0) { + log_error("get_host_stats failed. errno=%d", errno); + rc = ISCSI_ERR; + goto exit_host_stats; + } + + print_host_stats((struct iscsi_offload_host_stats *)(req_buf + + sizeof(struct iscsi_uevent))); + + ipc->ctldev_close(); + +exit_host_stats: + free(req_buf); + return rc; +} + +static int verify_iface_params(struct list_head *params, struct node_rec *rec) +{ + struct user_param *param; + + list_for_each_entry(param, params, list) { + if (!strcmp(param->name, IFACE_ISCSINAME)) { + log_error("Can not update " + "iface.iscsi_ifacename. Delete it, " + "and then create a new one."); + return ISCSI_ERR_INVAL; + } + + if (iface_is_bound_by_hwaddr(&rec->iface) && + !strcmp(param->name, IFACE_NETNAME)) { + log_error("Can not update interface binding " + "from hwaddress to net_ifacename. " + "You must delete the interface and " + "create a new one"); + return ISCSI_ERR_INVAL; + } + + if (iface_is_bound_by_netdev(&rec->iface) && + !strcmp(param->name, IFACE_HWADDR)) { + log_error("Can not update interface binding " + "from net_ifacename to hwaddress. " + "You must delete the interface and " + "create a new one"); + return ISCSI_ERR_INVAL; + } + } + return 0; +} + +static void _print_iface_tree(struct iscsi_node **nodes, uint32_t node_count, + const char *iface_name, + struct iscsi_node **matched_nodes) +{ + struct iscsi_node *node = NULL; + uint32_t matched_node_count = 0; + uint32_t i = 0; + + for (i = 0; i < node_count; ++i) { + node = nodes[i]; + if (strcmp(iface_name, iscsi_node_iface_name_get(node)) + == 0) + matched_nodes[matched_node_count++] = node; + } + printf("Iface: %s\n", iface_name); + print_nodes_tree(matched_nodes, matched_node_count, _PRINT_MODE_IFACE); +} + +static int print_iface_tree(struct iscsi_context *ctx, + const char *iface_name) +{ + int rc = 0; + struct iscsi_node **nodes = NULL; + struct iscsi_node **matched_nodes = NULL; + uint32_t node_count = 0; + struct iscsi_iface *iface = NULL; + struct iscsi_iface **ifaces = NULL; + uint32_t iface_count = 0; + uint32_t i = 0; + + _good(iscsi_nodes_get(ctx, &nodes, &node_count), + rc, out); + if (node_count == 0) + goto out; + matched_nodes = calloc(node_count, sizeof(struct iscsi_node *)); + if (matched_nodes == NULL) { + log_error("No memory"); + goto out; + } + + if (iface_name != NULL) { + // Just make sure specified iface exists + _good(iscsi_iface_get(ctx, iface_name, &iface), rc, out); + _print_iface_tree(nodes, node_count, iface_name, + matched_nodes); + } else { + _good(iscsi_ifaces_get(ctx, &ifaces, &iface_count), + rc, out); + for (i = 0; i < iface_count; ++i) + _print_iface_tree(nodes, node_count, + iscsi_iface_name_get(ifaces[i]), + matched_nodes); + } + +out: + free(matched_nodes); + iscsi_ifaces_free(ifaces, iface_count); + iscsi_iface_free(iface); + iscsi_nodes_free(nodes, node_count); + return rc; +} + +static int iface_param_update(struct iface_rec *iface, struct list_head *params) +{ + struct node_rec *rec; + int rc = 0; + + rec = idbm_create_rec(NULL, -1, NULL, -1, iface, 1); + if (!rec) { + rc = ISCSI_ERR_INVAL; + goto update_fail; + } + + if (iscsi_check_for_running_session(rec)) + log_warning("Updating iface while iscsi sessions " + "are using it. You must logout the running " + "sessions then log back in for the " + "new settings to take affect."); + + rc = verify_iface_params(params, rec); + if (rc) + goto update_fail; + + rc = iface_conf_update(params, &rec->iface); + if (rc) + goto update_fail; + + rc = __for_each_matched_rec(0, rec, params, idbm_node_set_param); + if (rc == ISCSI_ERR_NO_OBJS_FOUND) + rc = 0; + else if (rc) + goto update_fail; + + printf("%s updated.\n", iface->name); + free(rec); + return rc; + +update_fail: + log_error("Could not update iface %s: %s", + iface->name, iscsi_err_to_str(rc)); + free(rec); + return rc; +} + +struct iface_param_sync { + struct iface_rec *primary; + struct list_head *params; + int count; +}; + +static int update_sync_params(void *data, struct iface_rec *iface) +{ + struct iface_param_sync *iface_params = data; + struct iface_rec *primary = iface_params->primary; + struct list_head *params = iface_params->params; + + if ((strcmp(primary->transport_name, iface->transport_name)) || + (strcmp(primary->hwaddress, iface->hwaddress)) || + (primary->iface_num != iface->iface_num)) + return 0; + + return iface_param_update(iface, params); +} + +static int split_vlan_params(struct list_head *params, struct list_head *vlan_params) +{ + struct user_param *param, *tmp; + + list_for_each_entry_safe(param, tmp, params, list) { + if (!strncmp(param->name, "iface.vlan", 10)) { + list_move_tail(¶m->list, vlan_params); + } + } + return 0; +} + +static inline void list_splice_tail(struct list_head *list, struct list_head *head) +{ + list->prev->next = head; + list->next->prev = head->prev; + head->prev->next = list->next; + head->prev = list->prev; + INIT_LIST_HEAD(list); +} + +/* TODO: merge iter helpers and clean them up, so we can use them here */ +static int exec_iface_op(struct iscsi_context *ctx, int op, int do_show, + int info_level, struct iface_rec *iface, + uint64_t host_no, struct list_head *params) +{ + struct host_info hinfo; + struct node_rec *rec = NULL; + int rc = 0; + struct iscsi_iface **ifaces = NULL; + struct iscsi_iface *iface_info = NULL; + uint32_t iface_count = 0; + uint32_t i = 0; + + LIST_HEAD(vlan_params); + struct iscsi_transport *t; + switch (op) { + case OP_NEW: + if (!iface) { + log_error("Could not add interface. No interface " + "passed in."); + return EINVAL; + } + + rec = idbm_create_rec(NULL, -1, NULL, -1, iface, 0); + if (rec && iscsi_check_for_running_session(rec)) { + rc = ISCSI_ERR_SESS_EXISTS; + goto new_fail; + } + + iface_setup_defaults(iface); + rc = iface_conf_write(iface); + if (rc) + goto new_fail; + printf("New interface %s added\n", iface->name); + break; +new_fail: + log_error("Could not create new interface %s.", iface->name); + break; + case OP_DELETE: + if (!iface) { + log_error("Could not delete interface. No interface " + "passed in."); + return ISCSI_ERR_INVAL; + } + + rec = idbm_create_rec(NULL, -1, NULL, -1, iface, 1); + if (!rec) { + rc = ISCSI_ERR_INVAL; + goto delete_fail; + } + + /* logout and delete records using it first */ + rc = __for_each_matched_rec(0, rec, NULL, delete_node); + if (rc && rc != ISCSI_ERR_NO_OBJS_FOUND) + goto delete_fail; + + rc = iface_conf_delete(iface); + if (rc) + goto delete_fail; + + printf("%s unbound and deleted.\n", iface->name); + break; +delete_fail: + log_error("Could not delete iface %s: %s", iface->name, + iscsi_err_to_str(rc)); + break; + case OP_UPDATE: + if (!iface || list_empty(params)) { + log_error("Update requires name, value, and iface."); + rc = ISCSI_ERR_INVAL; + break; + } + + rec = idbm_create_rec(NULL, -1, NULL, -1, iface, 1); + if (!rec) { + rc = ISCSI_ERR_INVAL; + break; + } + t = iscsi_sysfs_get_transport_by_name(rec->iface.transport_name); + if (!t) { + log_error("Cound not locate transport for iface %s", iface->name); + rc = ISCSI_ERR_INVAL; + break; + } + if (t->template->sync_vlan_settings) { + /* sync shared vlan settings across ifaces */ + int nr_found = 0; + struct iface_param_sync sync_params = { + .primary = &rec->iface, + .params = &vlan_params, + .count = 0, + }; + split_vlan_params(params, &vlan_params); + iface_for_each_iface(&sync_params, 1, &nr_found, update_sync_params); + } + iface_param_update(&rec->iface, params); + list_splice_tail(&vlan_params, params); + break; + case OP_APPLY: + if (!iface) { + log_error("Apply requires iface."); + rc = ISCSI_ERR_INVAL; + break; + } + rc = iface_conf_read(iface); + if (rc) { + log_error("Could not read iface %s (%d).", + iface->name, rc); + break; + } + + rc = iface_apply_net_config(iface, op); + if (rc) { + log_error("Could not apply net configuration: %s", + iscsi_err_to_str(rc)); + break; + } + printf("%s applied.\n", iface->name); + break; + case OP_APPLY_ALL: + if (host_no > MAX_HOST_NO) { + log_error("Applyall requires a valid host number or MAC" + " passed in with the --host argument."); + rc = ISCSI_ERR_INVAL; + break; + } + + /* + * Need to get other iface info like transport. + */ + memset(&hinfo, 0, sizeof(struct host_info)); + hinfo.host_no = host_no; + if (iscsi_sysfs_get_hostinfo_by_host_no(&hinfo)) { + log_error("Could not match host%lu to ifaces.", host_no); + rc = ISCSI_ERR_INVAL; + break; + } + rc = iface_apply_net_config(&hinfo.iface, op); + if (rc) { + log_error("Could not apply net configuration: %s", + iscsi_err_to_str(rc)); + break; + } + + printf("Applied settings to ifaces attached to host%lu.\n", + host_no); + break; + default: + if ((op != OP_NOOP) && (op != OP_SHOW)) { + rc = ISCSI_ERR_INVAL; + goto out; + } + switch (info_level) { + case 0: + case -1: + if (iface == NULL) { + _good(iscsi_ifaces_get(ctx, &ifaces, + &iface_count), + rc, out); + if (iface_count == 0) { + log_error("No interfaces found."); + rc = ISCSI_ERR_NO_OBJS_FOUND; + goto out; + } + + for (i = 0; i < iface_count; ++i) + iface_print_flat(ifaces[i]); + } else { + _good(iscsi_iface_get(ctx, iface->name, + &iface_info), + rc, out); + iscsi_iface_print_config(iface_info); + } + break; + case 1: + /* + * TODO: we can display how the ifaces are related to + * node records. + * And we can add a scsi_host mode which would display + * how sessions are related to hosts (scsi_host and + * iscsi_sessions are the currently running instance of + * an iface or node record). + */ + /* + * TODO(Gris Ge): Once we have node support from + * libopeniscsiusr, change below codes. + */ + rc = print_iface_tree(ctx, iface ? iface->name : NULL); + break; + default: + log_error("Invalid info level %d. Try 0 - 1.", + info_level); + rc = LIBISCSI_ERR_INVAL; + goto out; + } + } + +out: + iscsi_ifaces_free(ifaces, iface_count); + iscsi_iface_free(iface_info); + + if (rec) + free(rec); + return rc; +} + +static int verify_node_params(struct list_head *params, struct node_rec *rec) +{ + struct user_param *param; + + if (list_empty(params)) { + log_error("update requires name and value"); + return ISCSI_ERR_INVAL; + } + + list_for_each_entry(param, params, list) { + /* compat - old tools used node and iface transport name */ + if (!strncmp(param->name, "iface.", 6) && + strcmp(param->name, "iface.transport_name")) { + log_error("Cannot modify %s. Use iface mode to update " + "this value.", param->name); + return ISCSI_ERR_INVAL; + } + + if (!strcmp(param->name, "node.transport_name")) { + free(param->name); + param->name = strdup("iface.transport_name"); + if (!param->name) { + log_error("Could not allocate memory for " + "param."); + return ISCSI_ERR_NOMEM; + } + } + /* + * tmp hack - we added compat crap above for the transport, + * but want to fix Doran's issue in this release too. However + * his patch is too harsh on many settings and we do not have + * time to update apps so we have this tmp hack until we + * can settle on a good interface that distros can use + * and we can mark stable. + */ + if (!strcmp(param->name, "iface.transport_name")) { + if (iscsi_check_for_running_session(rec)) { + log_warning("Cannot modify node/iface " + "transport name while a session " + "is using it. Log out the session " + "then update record."); + return ISCSI_ERR_SESS_EXISTS; + } + } + } + + return 0; +} + +/* TODO cleanup arguments */ +static int exec_node_op(struct iscsi_context *ctx, int op, int do_login, + int do_logout, int do_show, int do_rescan, int do_stats, + int info_level, struct node_rec *rec, + struct list_head *params) +{ + int rc = 0; + + if (rec) + log_debug(2, "%s: %s:%s node [%s,%s,%d] sid %u", __FUNCTION__, + rec->iface.transport_name, rec->iface.name, + rec->name, rec->conn[0].address, rec->conn[0].port, + rec->session.sid); + + if (op == OP_NEW) { + rc = add_static_recs(rec); + goto out; + } + + if (do_rescan) { + rc = for_each_session(rec, rescan_portal, 1); + goto out; + } + + if (do_stats) { + rc = for_each_session(rec, session_stats, 0); + goto out; + } + + if (do_login && do_logout) { + log_error("Invalid parameters. Both login and logout passed in"); + rc = ISCSI_ERR_INVAL; + goto out; + } + + if ((do_login || do_logout) && op > OP_NOOP) { + log_error("Invalid parameters. Login/logout and op passed in"); + rc = ISCSI_ERR_INVAL; + goto out; + } + + if ((!do_login && !do_logout && op == OP_NOOP) && + ((rec == NULL) || + (!strlen(rec->name) && !strlen(rec->conn[0].address) && + !strlen(rec->iface.name)))) { + rc = print_nodes(ctx, info_level); + goto out; + } + + if (do_login) { + rc = login_portals(rec); + goto out; + } + + if (do_logout) { + int nr_found; + + rc = iscsi_logout_portals(rec, &nr_found, 1, + iscsi_logout_matched_portal); + if (rc == ISCSI_ERR_NO_OBJS_FOUND) + log_error("No matching sessions found"); + goto out; + } + + if (op == OP_NOOP || (!do_login && !do_logout && op == OP_SHOW)) { + rc = print_nodes_config(ctx, do_show ? true : false, + rec->name, rec->conn[0].address, + rec->conn[0].port, rec->iface.name); + goto out; + } + + if (op == OP_UPDATE) { + rc = verify_node_params(params, rec); + if (rc) + goto out; + + rc = for_each_matched_rec(rec, params, idbm_node_set_param); + goto out; + } else if (op == OP_DELETE) { + rc = for_each_matched_rec(rec, NULL, delete_node); + goto out; + } else { + log_error("operation is not supported."); + rc = ISCSI_ERR_INVAL; + goto out; + } +out: + return rc; +} + +static int exec_fw_disc_op(discovery_rec_t *drec, struct list_head *ifaces, + int info_level, int do_login, int op) +{ + LIST_HEAD(targets); + LIST_HEAD(rec_list); + LIST_HEAD(new_ifaces); + struct iface_rec *iface, *tmp_iface; + struct node_rec *rec, *tmp_rec; + int rc = 0; + + /* + * compat: if the user did not pass any op then we do all + * ops for them + */ + if (!op) + op = OP_NEW | OP_DELETE | OP_UPDATE; + + /* + * if a user passed in ifaces then we use them and ignore the ibft + * net info + */ + if (!list_empty(ifaces)) { + list_for_each_entry_safe(iface, tmp_iface, ifaces, list) { + rc = iface_conf_read(iface); + if (rc) { + log_error("Could not read iface info for %s. " + "Make sure an iface config with the " + "file name and iface.iscsi_ifacename " + "%s is in %s.", iface->name, + iface->name, IFACE_CONFIG_DIR); + list_del_init(&iface->list); + free(iface); + continue; + } + } + goto discover_fw_tgts; + } + + /* + * Next, check if we see any offload cards. If we do then + * we make an iface if needed. + * + * Note1: if there is not a offload card we do not setup + * software iscsi binding with the nic used for booting, + * because we do not know if that was intended. + * + * Note2: we assume that the user probably wanted to access + * all targets through all the ifaces instead of being limited + * to what you can export in ibft. + */ + rc = fw_get_targets(&targets); + if (rc) { + log_error("Could not get list of targets from firmware. " + "(err %d)", rc); + return rc; + } + rc = iface_create_ifaces_from_boot_contexts(&new_ifaces, &targets); + if (rc) + goto done; + if (!list_empty(&new_ifaces)) + ifaces = &new_ifaces; + +discover_fw_tgts: + rc = idbm_bind_ifaces_to_nodes(discovery_fw, drec, + ifaces, &rec_list); + if (rc) + log_error("Could not perform fw discovery."); + else + rc = exec_disc_op_on_recs(drec, &rec_list, info_level, + do_login, op); + +done: + fw_free_targets(&targets); + + list_for_each_entry_safe(iface, tmp_iface, &new_ifaces, list) { + list_del(&iface->list); + free(iface); + } + + list_for_each_entry_safe(rec, tmp_rec, &rec_list, list) { + list_del(&rec->list); + free(rec); + } + return rc; +} + +static int exec_fw_op(discovery_rec_t *drec, struct list_head *ifaces, + int info_level, int do_login, int op) +{ + struct boot_context *context; + LIST_HEAD(targets); + LIST_HEAD(rec_list); + struct node_rec *rec; + int rc = 0; + + if (drec) + return exec_fw_disc_op(drec, ifaces, info_level, do_login, op); + + /* The following ops do not interact with the DB */ + rc = fw_get_targets(&targets); + if (rc) { + log_error("Could not get list of targets from firmware. " + "(err %d)", rc); + return rc; + } + + if (do_login) { + list_for_each_entry(context, &targets, list) { + rec = idbm_create_rec_from_boot_context(context); + if (!rec) { + log_error("Could not convert firmware info to " + "node record."); + rc = ISCSI_ERR_NOMEM; + break; + } + + iscsi_login_portal(NULL, NULL, rec); + free(rec); + } + } else { + list_for_each_entry(context, &targets, list) + fw_print_entry(context); + } + + fw_free_targets(&targets); + return rc; +} + +static void setup_drec_defaults(int type, char *ip, int port, + struct discovery_rec *drec) +{ + switch (type) { + case DISCOVERY_TYPE_ISNS: + idbm_isns_defaults(&drec->u.isns); + break; + case DISCOVERY_TYPE_SENDTARGETS: + idbm_sendtargets_defaults(&drec->u.sendtargets); + break; + default: + log_error("Invalid disc type."); + } + strlcpy(drec->address, ip, sizeof(drec->address)); + drec->port = port; + drec->type = type; +} + +/** + * exec_discover - prep, add, read and exec discovery on drec + * @type: discovery type + * @ip: IP address + * @port: port + * @ifaces: list of ifaces to bind to + * @info_level: print level + * @do_login: set to 1 if discovery function should also log into portals found + * @do_discover: set to 1 if discovery was requested + * @op: ops passed in by user + * @drec: discovery rec struct + * + * This function determines what type of op needs to be executed + * and will read and add a drec, and perform discovery if needed. + * + * returns: + * Greater than 0 - error + * 0 - op/discovery completed + * -1 - exec db op + */ +static int exec_discover(int disc_type, char *ip, int port, + struct list_head *ifaces, int info_level, + int do_login, int do_discover, int op, + struct discovery_rec *drec) +{ + int rc; + + if (ip == NULL) { + log_error("Please specify portal as [:]"); + return ISCSI_ERR_INVAL; + } + + if (op & OP_NEW && !do_discover) { + setup_drec_defaults(disc_type, ip, port, drec); + + rc = idbm_add_discovery(drec); + if (rc) { + log_error("Could not add new discovery record."); + return rc; + } else { + printf("New discovery record for [%s,%d] added.\n", ip, + port); + return 0; + } + } + + rc = idbm_discovery_read(drec, disc_type, ip, port); + if (rc) { + if (!do_discover) { + log_error("Discovery record [%s,%d] not found.", + ip, port); + return rc; + } + + /* Just add default rec for user */ + log_debug(1, "Discovery record [%s,%d] not found!", + ip, port); + setup_drec_defaults(disc_type, ip, port, drec); + if (!(op & OP_NONPERSISTENT)) { + rc = idbm_add_discovery(drec); + if (rc) { + log_error("Could not add new discovery " + "record."); + return rc; + } + } + } else if (!do_discover) + return -1; + + rc = 0; + switch (disc_type) { + case DISCOVERY_TYPE_SENDTARGETS: + case DISCOVERY_TYPE_ISNS: + rc = do_target_discovery(drec, ifaces, info_level, do_login, op, + 0); + break; + default: + log_error("Unsupported discovery type."); + break; + } + + return rc; +} + +static int exec_disc2_op(int disc_type, char *ip, int port, + struct list_head *ifaces, int info_level, int do_login, + int do_discover, int op, struct list_head *params, + int do_show) +{ + struct discovery_rec drec; + int rc = 0; + + memset(&drec, 0, sizeof(struct discovery_rec)); + drec.iscsid_req_tmo = -1; + if (disc_type != -1) + drec.type = disc_type; + + switch (disc_type) { + case DISCOVERY_TYPE_SENDTARGETS: + if (port < 0) + port = ISCSI_LISTEN_PORT; + + rc = exec_discover(disc_type, ip, port, ifaces, info_level, + do_login, do_discover, op, &drec); + if (rc < 0) + goto do_db_op; + goto done; + case DISCOVERY_TYPE_SLP: + log_error("SLP discovery is not fully implemented yet."); + rc = ISCSI_ERR_INVAL; + goto done; + case DISCOVERY_TYPE_ISNS: + if (port < 0) + port = ISNS_DEFAULT_PORT; + + rc = exec_discover(disc_type, ip, port, ifaces, info_level, + do_login, do_discover, op, &drec); + if (rc < 0) + goto do_db_op; + goto done; + case DISCOVERY_TYPE_FW: + if (!do_discover) { + log_error("Invalid command. Possibly missing " + "--discover argument."); + rc = ISCSI_ERR_INVAL; + goto done; + } + + drec.type = DISCOVERY_TYPE_FW; + rc = exec_fw_op(&drec, ifaces, info_level, do_login, op); + goto done; + default: + rc = ISCSI_ERR_INVAL; + + if (!ip) { + if (op == OP_NOOP || op == OP_SHOW) { + if (idbm_print_all_discovery(info_level)) + /* successfully found some recs */ + rc = 0; + else + rc = ISCSI_ERR_NO_OBJS_FOUND; + } else + log_error("Invalid operation. Operation not " + "supported."); + } else if (op) + log_error("Invalid command. Possibly missing discovery " + "--type."); + else + log_error("Invalid command. Portal not needed or " + "Possibly missing discovery --type."); + goto done; + } + +do_db_op: + rc = 0; + + if (op == OP_NOOP || op == OP_SHOW) { + if (!idbm_print_discovery_info(&drec, do_show)) { + log_error("No records found"); + rc = ISCSI_ERR_NO_OBJS_FOUND; + } + } else if (op == OP_DELETE) { + rc = idbm_delete_discovery(&drec); + if (rc) + log_error("Unable to delete record!"); + } else if (op == OP_UPDATE) { + if (list_empty(params)) { + log_error("Update requires name and value."); + rc = ISCSI_ERR_INVAL; + goto done; + } + rc = idbm_discovery_set_param(params, &drec); + } else { + log_error("Operation is not supported."); + rc = ISCSI_ERR_INVAL; + goto done; + } +done: + return rc; +} + +static int exec_disc_op(int disc_type, char *ip, int port, + struct list_head *ifaces, int info_level, int do_login, + int do_discover, int op, struct list_head *params, + int do_show) +{ + struct discovery_rec drec; + int rc = 0; + + memset(&drec, 0, sizeof(struct discovery_rec)); + drec.iscsid_req_tmo = -1; + + switch (disc_type) { + case DISCOVERY_TYPE_SENDTARGETS: + drec.type = DISCOVERY_TYPE_SENDTARGETS; + + if (port < 0) + port = ISCSI_LISTEN_PORT; + + if (ip == NULL) { + log_error("Please specify portal as " + "[:]"); + rc = ISCSI_ERR_INVAL; + goto done; + } + + idbm_sendtargets_defaults(&drec.u.sendtargets); + strlcpy(drec.address, ip, sizeof(drec.address)); + drec.port = port; + rc = do_target_discovery(&drec, ifaces, info_level, + do_login, op, 1); + if (rc) + goto done; + break; + case DISCOVERY_TYPE_SLP: + log_error("SLP discovery is not fully implemented yet."); + rc = ISCSI_ERR_INVAL; + break; + case DISCOVERY_TYPE_ISNS: + if (!ip) { + log_error("Please specify portal as " + ":[]"); + rc = ISCSI_ERR_INVAL; + goto done; + } + + strlcpy(drec.address, ip, sizeof(drec.address)); + if (port < 0) + drec.port = ISNS_DEFAULT_PORT; + else + drec.port = port; + + drec.type = DISCOVERY_TYPE_ISNS; + rc = do_target_discovery(&drec, ifaces, info_level, + do_login, op, 0); + if (rc) + goto done; + break; + case DISCOVERY_TYPE_FW: + drec.type = DISCOVERY_TYPE_FW; + rc = exec_fw_op(&drec, ifaces, info_level, do_login, op); + break; + default: + if (ip) { + /* + * We only have sendtargets disc recs in discovery + * mode, so we can hardcode the port check to the + * iscsi default here. + * + * For isns or slp recs then discovery db mode + * must be used. + */ + if (port < 0) + port = ISCSI_LISTEN_PORT; + + if (idbm_discovery_read(&drec, + DISCOVERY_TYPE_SENDTARGETS, + ip, port)) { + log_error("Discovery record [%s,%d] " + "not found!", ip, port); + rc = ISCSI_ERR_INVAL; + goto done; + } + if ((do_discover || do_login) && + drec.type == DISCOVERY_TYPE_SENDTARGETS) { + rc = do_target_discovery(&drec, ifaces, + info_level, do_login, + op, 0); + } else if (op == OP_NOOP || op == OP_SHOW) { + if (!idbm_print_discovery_info(&drec, + do_show)) { + log_error("No records found"); + rc = ISCSI_ERR_NO_OBJS_FOUND; + } + } else if (op == OP_DELETE) { + rc = idbm_delete_discovery(&drec); + if (rc) + log_error("Unable to delete record!"); + } else if (op == OP_UPDATE || op == OP_NEW) { + log_error("Operations new and update for " + "discovery mode is not supported. " + "Use discoverydb mode."); + rc = ISCSI_ERR_INVAL; + goto done; + } else { + log_error("Invalid operation."); + rc = ISCSI_ERR_INVAL; + goto done; + } + } else if (op == OP_NOOP || op == OP_SHOW) { + if (!idbm_print_all_discovery(info_level)) + rc = ISCSI_ERR_NO_OBJS_FOUND; + goto done; + } else { + log_error("Invalid operation."); + rc = ISCSI_ERR_INVAL; + goto done; + } + /* fall through */ + } + +done: + return rc; +} + +static uint64_t parse_host_info(char *optarg, int *rc) +{ + int err = 0; + uint64_t host_no; + + *rc = 0; + if (strstr(optarg, ":")) { + transport_probe_for_offload(); + + host_no = iscsi_sysfs_get_host_no_from_hwaddress(optarg, + &err); + if (err) { + log_error("Could not match MAC to host."); + *rc = ISCSI_ERR_INVAL; + } + } else { + errno = 0; // ensure errors from strtoull are real + host_no = strtoull(optarg, NULL, 10); + if (errno || (host_no > MAX_HOST_NO)) { + if (host_no > MAX_HOST_NO) + errno = ERANGE; + + log_error("Invalid host no %s. %s.", + optarg, strerror(errno)); + *rc = ISCSI_ERR_INVAL; + } + } + return host_no; +} + +static char *iscsi_ping_stat_strs[] = { + /* ISCSI_PING_SUCCESS */ + "success", + /* ISCSI_PING_FW_DISABLED */ + "firmware disabled", + /* ISCSI_PING_IPADDR_INVALID */ + "invalid IP address", + /* ISCSI_PING_LINKLOCAL_IPV6_ADDR_INVALID */ + "invalid link local IPv6 address", + /* ISCSI_PING_TIMEOUT */ + "timed out", + /* ISCSI_PING_INVALID_DEST_ADDR */ + "invalid destination address", + /* ISCSI_PING_OVERSIZE_PACKET */ + "oversized packet", + /* ISCSI_PING_ICMP_ERROR */ + "ICMP error", + /* ISCSI_PING_MAX_REQ_EXCEEDED */ + "Max request exceeded", + /* ISCSI_PING_NO_ARP_RECEIVED */ + "No ARP response received", +}; + +static char *iscsi_ping_stat_to_str(uint32_t status) +{ + if (status < 0 || status > ISCSI_PING_NO_ARP_RECEIVED) { + log_error("Invalid ping status %u", status); + return NULL; + } + + return iscsi_ping_stat_strs[status]; +} + +static int exec_ping_op(struct iface_rec *iface, char *ip, int size, int count, + int interval) +{ + int rc = ISCSI_ERR; + uint32_t iface_type = ISCSI_IFACE_TYPE_IPV4; + struct iscsi_transport *t = NULL; + uint32_t host_no, status = 0; + struct sockaddr_storage addr; + struct host_info hinfo; + int i; + + if (!iface) { + log_error("Ping requires iface."); + rc = ISCSI_ERR_INVAL; + goto ping_exit; + } + + if (!ip) { + log_error("Ping requires destination ipaddress."); + rc = ISCSI_ERR_INVAL; + goto ping_exit; + } + + if (size <= 0) { + log_error("Invalid packet size: %d.", size); + rc = ISCSI_ERR_INVAL; + goto ping_exit; + } + + if (count <= 0) { + log_error("Invalid number of packets to transmit: %d.", count); + rc = ISCSI_ERR_INVAL; + goto ping_exit; + } + + if (interval < 0) { + log_error("Invalid timing interval: %d.", interval); + rc = ISCSI_ERR_INVAL; + goto ping_exit; + } + + rc = iface_conf_read(iface); + if (rc) { + log_error("Could not read iface %s (%d).", iface->name, rc); + goto ping_exit; + } + + + iface_type = iface_get_iptype(iface); + + t = iscsi_sysfs_get_transport_by_name(iface->transport_name); + if (!t) { + log_error("Can't find transport."); + rc = ISCSI_ERR_INVAL; + goto ping_exit; + } + + host_no = iscsi_sysfs_get_host_no_from_hwinfo(iface, &rc); + if (host_no == -1) { + log_error("Can't find host_no."); + rc = ISCSI_ERR_INVAL; + goto ping_exit; + } + + rc = resolve_address(ip, NULL, &addr); + if (rc) { + log_error("Invalid IP address."); + rc = ISCSI_ERR_INVAL; + goto ping_exit; + } + + /* TODO: move this. It is needed by interface for pid */ + srand(time(NULL)); + + for (i = 1; i <= count; i++) { + /* + * To support drivers like bnx2i that do not use + * the iscsi iface to send a ping, we invoke transport + * callout here. + */ + status = 0; + if (t->template->exec_ping) { + if (!strlen(iface->netdev)) { + memset(&hinfo, 0, sizeof(hinfo)); + hinfo.host_no = host_no; + iscsi_sysfs_get_hostinfo_by_host_no(&hinfo); + strcpy(iface->netdev, hinfo.iface.netdev); + } + + rc = iscsi_set_net_config(t, NULL, iface); + if (rc && (rc != ISCSI_ERR_AGAIN)) + goto ping_err; + + rc = t->template->exec_ping(t, iface, size, &addr, + &status); + } else { + rc = ipc->exec_ping(t->handle, host_no, + (struct sockaddr *)&addr, + iface->iface_num, iface_type, + (uint32_t)size, &status); + } + +ping_err: + if (!rc && !status) + printf("Ping %d completed\n", i); + else if (status) + printf("Ping %d failed: %s\n", i, + iscsi_ping_stat_to_str(status)); + else + printf("Ping %d failed: %s\n", i, iscsi_err_to_str(rc)); + + if (i < count) + sleep(interval); + } + +ping_exit: + return rc; +} + +int +main(int argc, char **argv) +{ + char *ip = NULL, *name = NULL, *value = NULL; + char *targetname = NULL, *group_session_mgmt_mode = NULL; + int ch, longindex, mode=-1, port=-1, do_login=0, do_rescan=0; + int rc=0, sid=-1, op=OP_NOOP, type=-1, do_logout=0, do_stats=0; + int do_login_all=0, do_logout_all=0, info_level=-1, num_ifaces = 0; + int tpgt = PORTAL_GROUP_TAG_UNKNOWN, killiscsid=-1, do_show=0; + int packet_size=32, ping_count=1, ping_interval=0; + int do_discover = 0, sub_mode = -1; + int portal_type = -1; + int timeout = ISCSID_REQ_TIMEOUT; + struct sigaction sa_old; + struct sigaction sa_new; + LIST_HEAD(ifaces); + struct iface_rec *iface = NULL, *tmp; + struct node_rec *rec = NULL; + uint64_t host_no = (uint64_t)MAX_HOST_NO + 1; + uint64_t index = ULLONG_MAX; + struct user_param *param; + LIST_HEAD(params); + struct iscsi_context *ctx = NULL; + int librc = LIBISCSI_OK; + struct iscsi_session **ses = NULL; + uint32_t se_count = 0; + struct iscsi_session *se = NULL; + + ctx = iscsi_context_new(); + if (ctx == NULL) { + log_error("No memory"); + goto out; + } + + /* do not allow ctrl-c for now... */ + memset(&sa_old, 0, sizeof(struct sigaction)); + memset(&sa_new, 0, sizeof(struct sigaction)); + + sa_new.sa_handler = catch_sigint; + sigemptyset(&sa_new.sa_mask); + sa_new.sa_flags = 0; + sigaction(SIGINT, &sa_new, &sa_old ); + + umask(0177); + + /* enable stdout logging */ + log_init(program_name, 1024, log_do_log_std, NULL); + sysfs_init(); + + optopt = 0; + while ((ch = getopt_long(argc, argv, short_options, + long_options, &longindex)) >= 0) { + switch (ch) { + case 'k': + killiscsid = atoi(optarg); + if (killiscsid < 0) { + log_error("Invalid killiscsid priority %d " + "Priority must be greater than or " + "equal to zero.", killiscsid); + rc = ISCSI_ERR_INVAL; + goto free_ifaces; + } + break; + case 't': + type = str_to_type(optarg); + break; + case 'o': + op |= str_to_op(optarg); + if (op == OP_NOOP) { + log_error("can not recognize operation: '%s'", + optarg); + rc = ISCSI_ERR_INVAL; + goto free_ifaces; + } + break; + case 'n': + name = optarg; + break; + case 'v': + value = optarg; + break; + case 'H': + host_no = parse_host_info(optarg, &rc); + if (rc) + goto free_ifaces; + break; + case 'r': + sid = iscsi_sysfs_get_sid_from_path(optarg); + if (sid < 0) { + log_error("invalid sid '%s'", + optarg); + rc = ISCSI_ERR_INVAL; + goto free_ifaces; + } + break; + case 'R': + do_rescan = 1; + break; + case 'P': + info_level = atoi(optarg); + break; + case 'D': + do_discover = 1; + break; + case 'l': + do_login = 1; + break; + case 'u': + do_logout = 1; + break; + case 'U': + do_logout_all = 1; + group_session_mgmt_mode= optarg; + break; + case 'L': + do_login_all= 1; + group_session_mgmt_mode= optarg; + break; + case 's': + do_stats = 1; + break; + case 'S': + do_show = 1; + break; + case 'd': + log_level = atoi(optarg); + if (log_level >= 8) + iscsi_context_log_priority_set + (ctx, LIBISCSI_LOG_PRIORITY_DEBUG); + else if (log_level >= 4) + iscsi_context_log_priority_set + (ctx, LIBISCSI_LOG_PRIORITY_INFO); + else if (log_level >= 2) + iscsi_context_log_priority_set + (ctx, LIBISCSI_LOG_PRIORITY_WARNING); + else + iscsi_context_log_priority_set + (ctx, LIBISCSI_LOG_PRIORITY_ERROR); + break; + case 'm': + mode = str_to_mode(optarg); + break; + case 'C': + sub_mode = str_to_submode(optarg); + break; + case 'T': + targetname = optarg; + break; + case 'p': + ip = str_to_ipport(optarg, &port, &tpgt); + break; + case 'a': + ip = optarg; + break; + case 'b': + packet_size = atoi(optarg); + break; + case 'c': + ping_count = atoi(optarg); + break; + case 'i': + ping_interval = atoi(optarg); + break; + case 'I': + iface = iface_alloc(optarg, &rc); + if (rc == ISCSI_ERR_INVAL) { + printf("Invalid iface name %s. Must be from " + "1 to %d characters.\n", + optarg, ISCSI_MAX_IFACE_LEN - 1); + goto free_ifaces; + } else if (!iface || rc) { + printf("Could not add iface %s.", optarg); + rc = ISCSI_ERR_INVAL; + goto free_ifaces; + } + + list_add_tail(&iface->list, &ifaces); + num_ifaces++; + break; + case 'V': + printf("%s version %s\n", program_name, + ISCSI_VERSION_STR); + return 0; + case 'x': + errno = 0; // ensure errors from strtoull are real + index = strtoull(optarg, NULL, 10); + if (errno) { + log_error("Invalid index %s. %s.", + optarg, strerror(errno)); + rc = ISCSI_ERR_INVAL; + goto free_ifaces; + } + break; + case 'A': + portal_type = str_to_portal_type(optarg); + break; + case 'h': + usage(0); + } + + if (name && value) { + param = idbm_alloc_user_param(name, value); + if (!param) { + log_error("Cannot allocate memory for params."); + rc = ISCSI_ERR_NOMEM; + goto free_ifaces; + } + list_add_tail(¶m->list, ¶ms); + name = NULL; + value = NULL; + } + } + + if (optopt) { + log_error("unrecognized character '%c'", optopt); + rc = ISCSI_ERR_INVAL; + goto free_ifaces; + } + + if (killiscsid >= 0) { + kill_iscsid(killiscsid, timeout); + goto free_ifaces; + } + + if (mode < 0) + usage(ISCSI_ERR_INVAL); + + if (mode == MODE_FW) { + if ((rc = verify_mode_params(argc, argv, "dml", 0))) { + log_error("fw mode: option '-%c' is not " + "allowed/supported", rc); + rc = ISCSI_ERR_INVAL; + goto free_ifaces; + } + + rc = exec_fw_op(NULL, NULL, info_level, do_login, op); + goto free_ifaces; + } + + increase_max_files(); + if (idbm_init(get_config_file)) { + log_warning("exiting due to idbm configuration error"); + rc = ISCSI_ERR_IDBM; + goto free_ifaces; + } + + switch (mode) { + case MODE_HOST: + if ((rc = verify_mode_params(argc, argv, "CHdmPotnvxA", 0))) { + log_error("host mode: option '-%c' is not " + "allowed/supported", rc); + rc = ISCSI_ERR_INVAL; + goto out; + } + if (sub_mode != -1) { + switch (sub_mode) { + case MODE_CHAP: + if (!op || (host_no > MAX_HOST_NO)) { + log_error("CHAP mode requires host " + "no and valid operation"); + rc = ISCSI_ERR_INVAL; + break; + } + + if (index == ULLONG_MAX) + index = (uint64_t)MAX_CHAP_ENTRIES + 1; + + rc = exec_host_chap_op(op, info_level, host_no, + index, ¶ms); + break; + case MODE_FLASHNODE: + if (host_no > MAX_HOST_NO) { + log_error("FLASHNODE mode requires host no"); + rc = ISCSI_ERR_INVAL; + break; + } + + if (index == ULLONG_MAX) + index = (uint64_t)MAX_FLASHNODE_IDX + 1; + + rc = exec_flashnode_op(op, info_level, host_no, + index, portal_type, + ¶ms); + break; + case MODE_HOST_STATS: + if (host_no > MAX_HOST_NO) { + log_error("STATS mode requires host no"); + rc = ISCSI_ERR_INVAL; + break; + } + + rc = exec_host_stats_op(op, info_level, + host_no); + break; + + default: + log_error("Invalid Sub Mode"); + break; + } + } else { + librc = iscsi_sessions_get(ctx, &ses, &se_count); + + if (librc != LIBISCSI_OK) { + log_error("Failed to query iSCSI sessions, " + "error %d: %s", librc, + iscsi_strerror(librc)); + /* libopeniscsiusr rc is one-to-one map to iscsiadm + * rc + */ + rc = librc; + goto out; + } + rc = host_info_print(info_level, host_no, ses, + se_count); + } + break; + case MODE_IFACE: + iscsi_default_iface_setup(ctx); + + if ((rc = verify_mode_params(argc, argv, "HIdnvmPoCabci", 0))) { + log_error("iface mode: option '-%c' is not " + "allowed/supported", rc); + rc = ISCSI_ERR_INVAL; + goto out; + } + + if (!list_empty(&ifaces)) { + iface = list_entry(ifaces.next, struct iface_rec, + list); + if (num_ifaces > 1) + log_error("iface mode only accepts one " + "interface. Using the first one " + "%s.", iface->name); + } + + if (sub_mode == MODE_PING) + rc = exec_ping_op(iface, ip, packet_size, ping_count, + ping_interval); + else + rc = exec_iface_op(ctx, op, do_show, info_level, iface, + host_no, ¶ms); + + break; + case MODE_DISCOVERYDB: + if ((rc = verify_mode_params(argc, argv, "DSIPdmntplov", 0))) { + log_error("discovery mode: option '-%c' is not " + "allowed/supported", rc); + rc = ISCSI_ERR_INVAL; + goto out; + } + + rc = exec_disc2_op(type, ip, port, &ifaces, info_level, + do_login, do_discover, op, ¶ms, + do_show); + break; + case MODE_DISCOVERY: + if ((rc = verify_mode_params(argc, argv, "DSIPdmntplov", 0))) { + log_error("discovery mode: option '-%c' is not " + "allowed/supported", rc); + rc = ISCSI_ERR_INVAL; + goto out; + } + + rc = exec_disc_op(type, ip, port, &ifaces, info_level, + do_login, do_discover, op, ¶ms, + do_show); + break; + case MODE_NODE: + if ((rc = verify_mode_params(argc, argv, "RsPIdmlSonvupTUL", + 0))) { + log_error("node mode: option '-%c' is not " + "allowed/supported", rc); + rc = ISCSI_ERR_INVAL; + goto out; + } + + if (do_login_all) { + rc = login_by_startup(group_session_mgmt_mode); + goto out; + } + + if (do_logout_all) { + rc = logout_by_startup(group_session_mgmt_mode); + goto out; + } + + if (!list_empty(&ifaces)) { + iface = list_entry(ifaces.next, struct iface_rec, + list); + if (num_ifaces > 1) + log_error("NODE mode only accepts one " + "interface. Using the first one " + "driver %s hwaddress %s ipaddress " + "%s.", iface->transport_name, + iface->hwaddress, iface->ipaddress); + } + + if (ip && port == -1) + port = ISCSI_LISTEN_PORT; + + rec = idbm_create_rec(targetname, tpgt, ip, port, iface, 1); + if (!rec) { + rc = ISCSI_ERR_NOMEM; + goto out; + } + + rc = exec_node_op(ctx, op, do_login, do_logout, do_show, + do_rescan, do_stats, info_level, rec, + ¶ms); + break; + case MODE_SESSION: + if ((rc = verify_mode_params(argc, argv, + "PiRdrmusonuSv", 1))) { + log_error("session mode: option '-%c' is not " + "allowed or supported", rc); + rc = ISCSI_ERR_INVAL; + goto out; + } + if (sid >= 0) { + char session[64]; + struct session_info *info; + + snprintf(session, 63, "session%d", sid); + session[63] = '\0'; + + info = calloc(1, sizeof(*info)); + if (!info) { + rc = ISCSI_ERR_NOMEM; + goto out; + } + info->iscsid_req_tmo = -1; + + rc = iscsi_sysfs_get_sessioninfo_by_id(info, session); + if (rc) { + log_error("Could not get session info for sid " + "%d", sid); + goto free_info; + } + + /* + * We should be able to go on, but for now + * we only support session mode ops if the module + * is loaded and we support that module. + */ + if (!iscsi_sysfs_get_transport_by_sid(sid)) + goto free_info; + + if (!do_logout && !do_rescan && !do_stats && + op == OP_NOOP && info_level > 0) { + librc = iscsi_session_get + (ctx, sid & UINT32_MAX, &se); + if (librc != LIBISCSI_OK) { + log_error("Failed to query iSCSI " + "session %d, error %d: %s", + sid, librc, + iscsi_strerror(librc)); + rc = ISCSI_ERR_INVAL; + goto out; + } + ses = (struct iscsi_session **) + calloc(1, + sizeof(struct iscsi_session *)); + ses[0] = se; + se_count = 1; + rc = session_info_print(info_level, ses, + se_count, do_show); + goto free_info; + } + + rec = idbm_create_rec(info->targetname, + info->tpgt, + info->persistent_address, + info->persistent_port, + &info->iface, 1); + if (!rec) { + rc = ISCSI_ERR_NOMEM; + goto free_info; + } + rec->session.info = info; + rec->session.sid = sid; + + /* + * A "new" session means to login a multiple of the + * currently-detected session. + */ + if (op == OP_NEW) { + op = OP_NOOP; + do_login = 1; + rec->session.multiple = 1; + } + + /* drop down to node ops */ + rc = exec_node_op(ctx, op, do_login, do_logout, do_show, + do_rescan, do_stats, info_level, + rec, ¶ms); +free_info: + free(info); + goto out; + } else { + if (op == OP_NEW) { + log_error("session mode: Operation 'new' only " + "allowed with specific session IDs"); + rc = ISCSI_ERR_INVAL; + goto out; + } + if (do_logout || do_rescan || do_stats) { + rc = exec_node_op(ctx, op, do_login, do_logout, + do_show, do_rescan, do_stats, + info_level, NULL, ¶ms); + goto out; + } + + librc = iscsi_sessions_get(ctx, &ses, &se_count); + + if (librc != LIBISCSI_OK) { + log_error("Failed to query iSCSI sessions, " + "error %d: %s", librc, + iscsi_strerror(librc)); + /* libopeniscsiusr rc is one-to-one map to iscsiadm + * rc + */ + rc = librc; + goto out; + } + if (se_count == 0) { + log_error("No active sessions."); + rc =ISCSI_ERR_NO_OBJS_FOUND; + goto out; + } + + rc = session_info_print(info_level, ses, se_count, + do_show); + + } + break; + default: + log_error("This mode is not yet supported"); + /* fall through */ + } + +out: + iscsi_context_free(ctx); + if (rec) + free(rec); + iscsi_sessions_free(ses, se_count); + idbm_terminate(); +free_ifaces: + list_for_each_entry_safe(iface, tmp, &ifaces, list) { + list_del(&iface->list); + free(iface); + } + free_transports(); + sysfs_cleanup(); + return rc; +} diff --git a/usr/iscsid.c b/usr/iscsid.c new file mode 100644 index 0000000..8f1c597 --- /dev/null +++ b/usr/iscsid.c @@ -0,0 +1,612 @@ +/* + * iSCSI Initiator Daemon + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * Copyright (C) 2006 Mike Christie + * Copyright (C) 2006 Red Hat, Inc. All rights reserved. + + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef NO_SYSTEMD +#include +#endif + +#include "iscsid.h" +#include "mgmt_ipc.h" +#include "event_poll.h" +#include "iscsi_ipc.h" +#include "log.h" +#include "iscsi_util.h" +#include "initiator.h" +#include "transport.h" +#include "idbm.h" +#include "version.h" +#include "iscsi_sysfs.h" +#include "iface.h" +#include "session_info.h" +#include "sysdeps.h" +#include "discoveryd.h" +#include "iscsid_req.h" +#include "iscsi_err.h" + +/* global config info */ +struct iscsi_daemon_config daemon_config; +struct iscsi_daemon_config *dconfig = &daemon_config; + +static char program_name[] = "iscsid"; +static pid_t log_pid; +static gid_t gid; +static int daemonize = 1; +static int mgmt_ipc_fd; +static int sessions_to_recover = 0; + +static struct option const long_options[] = { + {"config", required_argument, NULL, 'c'}, + {"initiatorname", required_argument, NULL, 'i'}, + {"foreground", no_argument, NULL, 'f'}, + {"debug", required_argument, NULL, 'd'}, + {"uid", required_argument, NULL, 'u'}, + {"gid", required_argument, NULL, 'g'}, + {"no-pid-file", no_argument, NULL, 'n'}, + {"pid", required_argument, NULL, 'p'}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'v'}, + {NULL, 0, NULL, 0}, +}; + +static void usage(int status) +{ + if (status != 0) + fprintf(stderr, "Try `%s --help' for more information.\n", + program_name); + else { + printf("Usage: %s [OPTION]\n", program_name); + printf("\ +Open-iSCSI initiator daemon.\n\ + -c, --config=[path] Execute in the config file (" CONFIG_FILE ").\n\ + -i, --initiatorname=[path] read initiatorname from file (" INITIATOR_NAME_FILE ").\n\ + -f, --foreground make the program run in the foreground\n\ + -d, --debug debuglevel print debugging information\n\ + -u, --uid=uid run as uid, default is current user\n\ + -g, --gid=gid run as gid, default is current user group\n\ + -n, --no-pid-file do not use a pid file\n\ + -p, --pid=pidfile use pid file (default " PID_FILE ").\n\ + -h, --help display this help and exit\n\ + -v, --version display version and exit\n\ +"); + } + exit(status); +} + +static void +setup_rec_from_negotiated_values(node_rec_t *rec, struct session_info *info) +{ + struct iscsi_session_operational_config session_conf; + struct iscsi_conn_operational_config conn_conf; + struct iscsi_auth_config auth_conf; + + idbm_node_setup_from_conf(rec); + strlcpy(rec->name, info->targetname, TARGET_NAME_MAXLEN); + rec->conn[0].port = info->persistent_port; + strlcpy(rec->conn[0].address, info->persistent_address, NI_MAXHOST); + rec->tpgt = info->tpgt; + + iscsi_sysfs_get_negotiated_session_conf(info->sid, &session_conf); + iscsi_sysfs_get_negotiated_conn_conf(info->sid, &conn_conf); + iscsi_sysfs_get_auth_conf(info->sid, &auth_conf); + + if (strlen(auth_conf.username)) + strcpy(rec->session.auth.username, auth_conf.username); + + if (strlen(auth_conf.username_in)) + strcpy(rec->session.auth.username_in, auth_conf.username_in); + + if (strlen((char *)auth_conf.password)) { + strcpy((char *)rec->session.auth.password, + (char *)auth_conf.password); + rec->session.auth.password_length = auth_conf.password_length; + } + + if (strlen((char *)auth_conf.password_in)) { + strcpy((char *)rec->session.auth.password_in, + (char *)auth_conf.password_in); + rec->session.auth.password_in_length = + auth_conf.password_in_length; + } + + if (is_valid_operational_value(conn_conf.HeaderDigest)) { + if (conn_conf.HeaderDigest) + rec->conn[0].iscsi.HeaderDigest = + CONFIG_DIGEST_PREFER_ON; + else + rec->conn[0].iscsi.HeaderDigest = + CONFIG_DIGEST_PREFER_OFF; + } + + if (is_valid_operational_value(conn_conf.DataDigest)) { + if (conn_conf.DataDigest) + rec->conn[0].iscsi.DataDigest = CONFIG_DIGEST_PREFER_ON; + else + rec->conn[0].iscsi.DataDigest = + CONFIG_DIGEST_PREFER_OFF; + } + + if (is_valid_operational_value(conn_conf.MaxRecvDataSegmentLength)) + rec->conn[0].iscsi.MaxRecvDataSegmentLength = + conn_conf.MaxRecvDataSegmentLength; + + if (is_valid_operational_value(conn_conf.MaxXmitDataSegmentLength)) + rec->conn[0].iscsi.MaxXmitDataSegmentLength = + conn_conf.MaxXmitDataSegmentLength; + + if (is_valid_operational_value(session_conf.FirstBurstLength)) + rec->session.iscsi.FirstBurstLength = + session_conf.FirstBurstLength; + + if (is_valid_operational_value(session_conf.MaxBurstLength)) + rec->session.iscsi.MaxBurstLength = + session_conf.MaxBurstLength; + + if (is_valid_operational_value(session_conf.ImmediateData)) { + if (session_conf.ImmediateData) + rec->session.iscsi.ImmediateData = 1; + else + rec->session.iscsi.ImmediateData = 0; + } + + if (is_valid_operational_value(session_conf.InitialR2T)) { + if (session_conf.InitialR2T) + rec->session.iscsi.InitialR2T = 0; + else + rec->session.iscsi.InitialR2T = 1; + } +} + +static int sync_session(void *data, struct session_info *info) +{ + node_rec_t rec, sysfsrec; + iscsiadm_req_t req; + iscsiadm_rsp_t rsp; + struct iscsi_transport *t; + int rc, retries = 0; + + log_debug(7, "sync session [%d][%s,%s.%d][%s]", info->sid, + info->targetname, info->persistent_address, + info->port, info->iface.hwaddress); + + t = iscsi_sysfs_get_transport_by_sid(info->sid); + if (!t) + return 0; + + /* + * Just rescan the device in case this is the first startup. + * (TODO: should do this async and check for state). + */ + if (t->caps & CAP_FW_DB) { + uint32_t host_no; + int err; + + host_no = iscsi_sysfs_get_host_no_from_sid(info->sid, &err); + if (err) { + log_error("Could not get host no from sid %u. Can not " + "sync session: %s", info->sid, + iscsi_err_to_str(err)); + return 0; + } + iscsi_sysfs_scan_host(host_no, 0, idbm_session_autoscan(NULL)); + return 0; + } + + if (!iscsi_sysfs_session_user_created(info->sid)) + return 0; + + memset(&rec, 0, sizeof(node_rec_t)); + /* + * We might get the local ip address for software. We do not + * want to try and bind a session by ip though. + */ + if (!t->template->set_host_ip) + memset(info->iface.ipaddress, 0, sizeof(info->iface.ipaddress)); + + if (idbm_rec_read(&rec, info->targetname, info->tpgt, + info->persistent_address, info->persistent_port, + &info->iface, false)) { + log_warning("Could not read data from db. Using default and " + "currently negotiated values"); + setup_rec_from_negotiated_values(&rec, info); + iface_copy(&rec.iface, &info->iface); + } else { + /* + * we have a valid record and iface so lets merge + * the values from them and sysfs to try and get + * the most uptodate values. + * + * Currenlty that means we will use the CHAP, target, portal + * and iface values from sysfs and use timer, queue depth, + * and segment length values from the record. + */ + memset(&sysfsrec, 0, sizeof(node_rec_t)); + setup_rec_from_negotiated_values(&sysfsrec, info); + /* + * target, portal and iface values have to be the same + * or we would not have found the record, so just copy + * CHAP settings. + */ + memcpy(&rec.session.auth, &sysfsrec.session.auth, + sizeof(struct iscsi_auth_config)); + } + + /* multiple drivers could be connected to the same portal */ + if (!iscsi_match_session(&rec, info)) + return -1; + /* + * We use the initiator name from sysfs because + * the session could have come from our db or ibft or some other + * app. + */ + strcpy(rec.iface.iname, info->iface.iname); + memset(&req, 0, sizeof(req)); + req.command = MGMT_IPC_SESSION_SYNC; + req.u.session.sid = info->sid; + memcpy(&req.u.session.rec, &rec, sizeof(node_rec_t)); + +retry: + rc = iscsid_exec_req(&req, &rsp, 0, info->iscsid_req_tmo); + if (rc == ISCSI_ERR_ISCSID_NOTCONN && retries < 30) { + retries++; + sleep(1); + goto retry; + } + return 0; +} + +static char *iscsid_get_config_file(void) +{ + return daemon_config.config_file; +} + +static void iscsid_shutdown(void) +{ + pid_t pid; + + killpg(gid, SIGTERM); + while ((pid = waitpid(0, NULL, 0) > 0)) + log_debug(7, "cleaned up pid %d", pid); + + log_warning("iscsid shutting down."); + if (daemonize && log_pid >= 0) { + log_debug(1, "daemon stopping"); + log_close(log_pid); + } +} + +static void catch_signal(int signo) +{ + log_debug(1, "pid %d caught signal %d", getpid(), signo); + + /* In foreground mode, treat SIGINT like SIGTERM */ + if (!daemonize && signo == SIGINT) + signo = SIGTERM; + + switch (signo) { + case SIGTERM: + event_loop_exit(NULL); + break; + default: + break; + } +} + +static void missing_iname_warn(char *initiatorname_file) +{ + log_error("Warning: InitiatorName file %s does not exist or does not " + "contain a properly formatted InitiatorName. If using " + "software iscsi (iscsi_tcp or ib_iser) or partial offload " + "(bnx2i or cxgbi iscsi), you may not be able to log " + "into or discover targets. Please create a file %s that " + "contains a sting with the format: InitiatorName=" + "iqn.yyyy-mm.[:identifier].\n\n" + "Example: InitiatorName=iqn.2001-04.com.redhat:fc6.\n" + "If using hardware iscsi like qla4xxx this message can be " + "ignored.", initiatorname_file, initiatorname_file); +} + +/* called right before we enter the event loop */ +static void set_state_to_ready(void) +{ +#ifndef NO_SYSTEMD + if (sessions_to_recover) + sd_notify(0, "READY=1\n" + "RELOADING=1\n" + "STATUS=Syncing existing session(s)\n"); + else + sd_notify(0, "READY=1\n" + "STATUS=Ready to process requests\n"); +#endif +} + +/* called when recovery process has been reaped */ +static void set_state_done_reloading(void) +{ +#ifndef NO_SYSTEMD + sessions_to_recover = 0; + sd_notifyf(0, "READY=1\n" + "STATUS=Ready to process requests\n"); +#endif +} + +int main(int argc, char *argv[]) +{ + struct utsname host_info; /* will use to compound initiator alias */ + char *config_file = CONFIG_FILE; + char *initiatorname_file = INITIATOR_NAME_FILE; + char *pid_file = PID_FILE; + char *safe_logout; + int ch, longindex; + uid_t uid = 0; + struct sigaction sa_old; + struct sigaction sa_new; + int control_fd; + pid_t pid; + + while ((ch = getopt_long(argc, argv, "c:i:fd:nu:g:p:vh", long_options, + &longindex)) >= 0) { + switch (ch) { + case 'c': + config_file = optarg; + break; + case 'i': + initiatorname_file = optarg; + break; + case 'f': + daemonize = 0; + break; + case 'd': + log_level = atoi(optarg); + break; + case 'u': + uid = strtoul(optarg, NULL, 10); + break; + case 'g': + gid = strtoul(optarg, NULL, 10); + break; + case 'n': + pid_file = NULL; + break; + case 'p': + pid_file = optarg; + break; + case 'v': + printf("%s version %s\n", program_name, + ISCSI_VERSION_STR); + exit(0); + case 'h': + usage(0); + break; + default: + usage(1); + break; + } + } + + /* initialize logger */ + log_pid = log_init(program_name, DEFAULT_AREA_SIZE, + daemonize ? log_do_log_daemon : log_do_log_std, NULL); + if (log_pid < 0) + exit(ISCSI_ERR); + + /* do not allow ctrl-c for now... */ + sa_new.sa_handler = catch_signal; + sigemptyset(&sa_new.sa_mask); + sa_new.sa_flags = 0; + sigaction(SIGINT, &sa_new, &sa_old ); + sigaction(SIGPIPE, &sa_new, &sa_old ); + sigaction(SIGTERM, &sa_new, &sa_old ); + + sysfs_init(); + if (idbm_init(iscsid_get_config_file)) { + log_close(log_pid); + exit(ISCSI_ERR); + } + + umask(0177); + + mgmt_ipc_fd = -1; + control_fd = -1; + daemon_config.initiator_name = NULL; + daemon_config.initiator_alias = NULL; + + if ((mgmt_ipc_fd = mgmt_ipc_listen()) < 0) { + log_close(log_pid); + exit(ISCSI_ERR); + } + + if (daemonize) { + char buf[64]; + int fd = -1; + + if (pid_file) { + fd = open(pid_file, O_WRONLY|O_CREAT, 0644); + if (fd < 0) { + log_error("Unable to create pid file"); + log_close(log_pid); + exit(ISCSI_ERR); + } + } + pid = fork(); + if (pid < 0) { + log_error("Starting daemon failed"); + log_close(log_pid); + exit(ISCSI_ERR); + } else if (pid) { + log_info("iSCSI daemon with pid=%d started!", pid); + exit(0); + } + + if (chdir("/") < 0) + log_debug(1, "Unable to chdir to /"); + if (fd > 0) { + if (lockf(fd, F_TLOCK, 0) < 0) { + log_error("Unable to lock pid file"); + log_close(log_pid); + exit(ISCSI_ERR); + } + if (ftruncate(fd, 0) < 0) { + log_error("Unable to truncate pid file"); + log_close(log_pid); + exit(ISCSI_ERR); + } + sprintf(buf, "%d\n", getpid()); + if (write(fd, buf, strlen(buf)) < 0) { + log_error("Unable to write pid file"); + log_close(log_pid); + exit(ISCSI_ERR); + } + } + close(fd); + + if ((control_fd = ipc->ctldev_open()) < 0) { + log_close(log_pid); + exit(ISCSI_ERR); + } + + daemon_init(); + } else { + if ((control_fd = ipc->ctldev_open()) < 0) { + log_close(log_pid); + exit(1); + } + } + + if (gid && setgid(gid) < 0) { + log_error("Unable to setgid to %d", gid); + log_close(log_pid); + exit(ISCSI_ERR); + } + + if ((geteuid() == 0) && (getgroups(0, NULL))) { + if (setgroups(0, NULL) != 0) { + log_error("Unable to drop supplementary group ids"); + log_close(log_pid); + exit(ISCSI_ERR); + } + } + + if (uid && setuid(uid) < 0) { + log_error("Unable to setuid to %d", uid); + log_close(log_pid); + exit(ISCSI_ERR); + } + + memset(&daemon_config, 0, sizeof (daemon_config)); + daemon_config.pid_file = pid_file; + daemon_config.config_file = config_file; + daemon_config.initiator_name = cfg_get_string_param(initiatorname_file, + "InitiatorName"); + if (daemon_config.initiator_name == NULL) + missing_iname_warn(initiatorname_file); + + /* optional InitiatorAlias */ + daemon_config.initiator_alias = + cfg_get_string_param(initiatorname_file, + "InitiatorAlias"); + if (!daemon_config.initiator_alias) { + memset(&host_info, 0, sizeof (host_info)); + if (uname(&host_info) >= 0) { + daemon_config.initiator_alias = + strdup(host_info.nodename); + } + } + + log_debug(1, "InitiatorName=%s", daemon_config.initiator_name ? + daemon_config.initiator_name : "NOT SET"); + log_debug(1, "InitiatorAlias=%s", daemon_config.initiator_alias); + + safe_logout = cfg_get_string_param(config_file, "iscsid.safe_logout"); + if (safe_logout && !strcmp(safe_logout, "Yes")) + daemon_config.safe_logout = 1; + free(safe_logout); + + /* see if we have any stale sessions to recover */ + sessions_to_recover = iscsi_sysfs_count_sessions(); + if (sessions_to_recover) { + + /* + * recover stale sessions in the background + */ + + pid = fork(); + if (pid == 0) { + int nr_found; /* not used */ + /* child */ + /* TODO - test with async support enabled */ + iscsi_sysfs_for_each_session(NULL, &nr_found, sync_session, 0); + exit(0); + } else if (pid < 0) { + log_error("Fork failed error %d: existing sessions" + " will not be synced", errno); + } else { + /* parent */ + log_debug(8, "forked child (pid=%d) to recover %d session(s)", + (int)pid, sessions_to_recover); + reap_track_reload_process(pid, set_state_done_reloading); + } + } + + iscsi_initiator_init(); + increase_max_files(); + discoveryd_start(daemon_config.initiator_name); + + /* oom-killer will not kill us at the night... */ + if (oom_adjust()) + log_debug(1, "can not adjust oom-killer's pardon"); + + /* we don't want our active sessions to be paged out... */ + if (mlockall(MCL_CURRENT | MCL_FUTURE)) { + log_error("failed to mlockall, exiting..."); + log_close(log_pid); + exit(ISCSI_ERR); + } + + set_state_to_ready(); + event_loop(ipc, control_fd, mgmt_ipc_fd); + + idbm_terminate(); + sysfs_cleanup(); + ipc->ctldev_close(); + mgmt_ipc_close(mgmt_ipc_fd); + if (daemon_config.initiator_name) + free(daemon_config.initiator_name); + if (daemon_config.initiator_alias) + free(daemon_config.initiator_alias); + free_initiator(); + iscsid_shutdown(); + return 0; +} diff --git a/usr/iscsid.h b/usr/iscsid.h new file mode 100644 index 0000000..b9f3d54 --- /dev/null +++ b/usr/iscsid.h @@ -0,0 +1,36 @@ +/* + * iSCSI Initiator Daemon + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * maintained by open-iscsi@@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#ifndef ISCSID_H +#define ISCSID_H + +/* IPC API */ +extern struct iscsi_ipc *ipc; + +/* iscsid.c: daemon config */ +struct iscsi_daemon_config { + char *config_file; + char *pid_file; + char *initiator_name; + char *initiator_alias; + int safe_logout; +}; +extern struct iscsi_daemon_config *dconfig; + +#endif /* ISCSID_H */ diff --git a/usr/iscsid_req.c b/usr/iscsid_req.c new file mode 100644 index 0000000..d872eb7 --- /dev/null +++ b/usr/iscsid_req.c @@ -0,0 +1,351 @@ +/* + * iscsid communication helpers + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * Copyright (C) 2006 - 2010 Mike Christie + * Copyright (C) 2006 - 2010 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "initiator.h" +#include "log.h" +#include "mgmt_ipc.h" +#include "iscsi_util.h" +#include "config.h" +#include "iscsi_err.h" +#include "iscsid_req.h" +#include "uip_mgmt_ipc.h" + +static void iscsid_startup(void) +{ + char *startup_cmd; + + startup_cmd = cfg_get_string_param(CONFIG_FILE, "iscsid.startup"); + if (!startup_cmd) { + log_error("iscsid is not running. Could not start it up " + "automatically using the startup command in the " + "/etc/iscsi/iscsid.conf iscsid.startup setting. " + "Please check that the file exists or that your " + "init scripts have started iscsid."); + return; + } + + if (system(startup_cmd) < 0) + log_error("Could not execute '%s' (err %d)", + startup_cmd, errno); + + free(startup_cmd); +} + +#define MAXSLEEP 128 + +static int ipc_connect(int *fd, char *unix_sock_name, int start_iscsid) +{ + int nsec, addr_len; + struct sockaddr_un addr; + + *fd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (*fd < 0) { + log_error("can not create IPC socket (%d)!", errno); + return ISCSI_ERR_ISCSID_NOTCONN; + } + + addr_len = setup_abstract_addr(&addr, unix_sock_name); + + /* + * Trying to connect with exponential backoff + */ + for (nsec = 1; nsec <= MAXSLEEP; nsec <<= 1) { + if (connect(*fd, (struct sockaddr *) &addr, addr_len) == 0) + /* Connection established */ + return ISCSI_SUCCESS; + + /* If iscsid isn't there, there's no sense + * in retrying. */ + if (errno == ECONNREFUSED) { + if (start_iscsid && nsec == 1) + iscsid_startup(); + else + break; + } + + /* + * Delay before trying again + */ + if (nsec <= MAXSLEEP/2) + sleep(nsec); + } + close(*fd); + *fd = -1; + log_error("can not connect to iSCSI daemon (%d)!", errno); + return ISCSI_ERR_ISCSID_NOTCONN; +} + +char iscsid_namespace[64] = ISCSIADM_NAMESPACE; + +void iscsid_set_namespace(pid_t pid) { + if (pid) { + snprintf(iscsid_namespace, 64, ISCSIADM_NAMESPACE "-%d", pid); + } else { + snprintf(iscsid_namespace, 64, ISCSIADM_NAMESPACE); + } +} + +static int iscsid_connect(int *fd, int start_iscsid) +{ + return ipc_connect(fd, iscsid_namespace, start_iscsid); +} + +int iscsid_request(int *fd, iscsiadm_req_t *req, int start_iscsid) +{ + int err; + + err = iscsid_connect(fd, start_iscsid); + if (err) + return err; + + if ((err = write(*fd, req, sizeof(*req))) != sizeof(*req)) { + log_error("got write error (%d/%d) on cmd %d, daemon died?", + err, errno, req->command); + close(*fd); + return ISCSI_ERR_ISCSID_COMM_ERR; + } + return ISCSI_SUCCESS; +} + +int iscsid_response(int fd, iscsiadm_cmd_e cmd, iscsiadm_rsp_t *rsp, + int timeout) +{ + size_t len = sizeof(*rsp); + int iscsi_err = ISCSI_ERR_ISCSID_COMM_ERR; + int err; + int poll_wait = 0; + + if (timeout == -1) { + timeout = ISCSID_REQ_TIMEOUT; + poll_wait = 1; + } + while (len) { + struct pollfd pfd; + + pfd.fd = fd; + pfd.events = POLLIN; + err = poll(&pfd, 1, timeout); + if (!err) { + if (poll_wait) + continue; + return ISCSI_ERR_ISCSID_NOTCONN; + } else if (err < 0) { + if (errno == EINTR) + continue; + log_error("got poll error (%d/%d), daemon died?", + err, errno); + return ISCSI_ERR_ISCSID_COMM_ERR; + } else if (pfd.revents & POLLIN) { + err = recv(fd, rsp, sizeof(*rsp), MSG_WAITALL); + if (err < 0) { + log_error("read error (%d/%d), daemon died?", + err, errno); + break; + } + len -= err; + iscsi_err = rsp->err; + } + } + close(fd); + + if (!iscsi_err && cmd != rsp->command) + iscsi_err = ISCSI_ERR_ISCSID_COMM_ERR; + return iscsi_err; +} + +int iscsid_exec_req(iscsiadm_req_t *req, iscsiadm_rsp_t *rsp, + int start_iscsid, int tmo) +{ + int fd; + int err; + + err = iscsid_request(&fd, req, start_iscsid); + if (err) + return err; + + return iscsid_response(fd, req->command, rsp, tmo); +} + +int iscsid_req_wait(iscsiadm_cmd_e cmd, int fd) +{ + iscsiadm_rsp_t rsp; + + memset(&rsp, 0, sizeof(iscsiadm_rsp_t)); + return iscsid_response(fd, cmd, &rsp, -1); +} + +int iscsid_req_by_rec_async(iscsiadm_cmd_e cmd, node_rec_t *rec, int *fd) +{ + iscsiadm_req_t req; + + memset(&req, 0, sizeof(iscsiadm_req_t)); + req.command = cmd; + memcpy(&req.u.session.rec, rec, sizeof(node_rec_t)); + + return iscsid_request(fd, &req, 1); +} + +int iscsid_req_by_rec(iscsiadm_cmd_e cmd, node_rec_t *rec) +{ + int err, fd; + + err = iscsid_req_by_rec_async(cmd, rec, &fd); + if (err) + return err; + return iscsid_req_wait(cmd, fd); +} + +int iscsid_req_by_sid_async(iscsiadm_cmd_e cmd, int sid, int *fd) +{ + iscsiadm_req_t req; + + memset(&req, 0, sizeof(iscsiadm_req_t)); + req.command = cmd; + req.u.session.sid = sid; + + return iscsid_request(fd, &req, 1); +} + +int iscsid_req_by_sid(iscsiadm_cmd_e cmd, int sid) +{ + int err, fd; + + err = iscsid_req_by_sid_async(cmd, sid, &fd); + if (err) + return err; + return iscsid_req_wait(cmd, fd); +} + +static int uip_connect(int *fd) +{ + return ipc_connect(fd, ISCSID_UIP_NAMESPACE, 0); +} + +int uip_broadcast(void *buf, size_t buf_len, int fd_flags, uint32_t *status) +{ + int err; + int fd; + iscsid_uip_rsp_t rsp; + int flags; + int count; + + err = uip_connect(&fd); + if (err) { + log_warning("uIP daemon is not up"); + return err; + } + + log_debug(3, "connected to uIP daemon"); + + /* Send the data to uIP */ + err = write(fd, buf, buf_len); + if (err != buf_len) { + log_error("got write error (%d/%d), daemon died?", + err, errno); + close(fd); + return ISCSI_ERR_ISCSID_COMM_ERR; + } + + log_debug(3, "send iface config to uIP daemon"); + + /* Set the socket to a non-blocking read, this way if there are + * problems waiting for uIP, iscsid can bailout early */ + flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) + flags = 0; + + if (fd_flags) + flags |= fd_flags; + + err = fcntl(fd, F_SETFL, flags); + if (err) { + log_error("could not set uip broadcast to non-blocking: %d", + errno); + close(fd); + return ISCSI_ERR; + } + +#define MAX_UIP_BROADCAST_READ_TRIES 5 + for (count = 0; count < MAX_UIP_BROADCAST_READ_TRIES; count++) { + /* Wait for the response */ + err = read(fd, &rsp, sizeof(rsp)); + if (err == sizeof(rsp)) { + log_debug(3, "Broadcasted to uIP with length: %ld " + "cmd: 0x%x rsp: 0x%x", buf_len, + rsp.command, rsp.err); + err = 0; + break; + } else if ((err == -1) && (errno == EAGAIN)) { + usleep(1000000); + continue; + } else { + log_error("Could not read response (%d/%d), daemon " + "died?", err, errno); + err = ISCSI_ERR; + break; + } + } + + if (count == MAX_UIP_BROADCAST_READ_TRIES) { + log_error("Could not broadcast to uIP after %d tries", + count); + err = ISCSI_ERR_AGAIN; + } + + if (err) + goto done; + + switch (rsp.command) { + case ISCSID_UIP_IPC_GET_IFACE: + if (rsp.err != ISCSID_UIP_MGMT_IPC_DEVICE_UP) { + log_debug(3, "Device is not ready\n"); + err = ISCSI_ERR_AGAIN; + } + + break; + case ISCSID_UIP_IPC_PING: + *status = rsp.ping_sc; + if (rsp.err == ISCSID_UIP_MGMT_IPC_DEVICE_INITIALIZING) { + log_debug(3, "Device is not ready\n"); + err = ISCSI_ERR_AGAIN; + } else if (*status) { + err = ISCSI_ERR; + } + + break; + default: + err = ISCSI_ERR; + } + +done: + close(fd); + return err; +} diff --git a/usr/iscsid_req.h b/usr/iscsid_req.h new file mode 100644 index 0000000..d580ed2 --- /dev/null +++ b/usr/iscsid_req.h @@ -0,0 +1,45 @@ +/* + * iscsid communication helpers + * + * Copyright (C) 2010 Mike Christie + * Copyright (C) 2010 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#ifndef ISCSID_REQ_H_ +#define ISCSID_REQ_H + +#define ISCSID_REQ_TIMEOUT 1000 + +struct iscsiadm_req; +struct iscsiadm_rsp; +struct node_rec; + +extern char iscsid_namespace[64]; +extern void iscsid_set_namespace(pid_t); + +extern int iscsid_exec_req(struct iscsiadm_req *req, struct iscsiadm_rsp *rsp, + int iscsid_start, int tmo); +extern int iscsid_req_wait(iscsiadm_cmd_e cmd, int fd); +extern int iscsid_req_by_rec_async(iscsiadm_cmd_e cmd, struct node_rec *rec, + int *fd); +extern int iscsid_req_by_rec(iscsiadm_cmd_e cmd, struct node_rec *rec); +extern int iscsid_req_by_sid_async(iscsiadm_cmd_e cmd, int sid, int *fd); +extern int iscsid_req_by_sid(iscsiadm_cmd_e cmd, int sid); + +extern int uip_broadcast(void *buf, size_t buf_len, int fd_flags, + uint32_t *status); + +#endif diff --git a/usr/iscsistart.c b/usr/iscsistart.c new file mode 100644 index 0000000..d3616ea --- /dev/null +++ b/usr/iscsistart.c @@ -0,0 +1,545 @@ +/* + * iSCSI Root Boot Program based on daemon code + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * Copyright (C) 2006 Mike Christie + * Copyright (C) 2006 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "initiator.h" +#include "iscsi_ipc.h" +#include "event_poll.h" +#include "transport.h" +#include "log.h" +#include "iscsi_util.h" +#include "idbm.h" +#include "idbm_fields.h" +#include "version.h" +#include "iscsi_sysfs.h" +#include "iscsi_settings.h" +#include "fw_context.h" +#include "iface.h" +#include "sysdeps.h" +#include "iscsid_req.h" +#include "iscsi_err.h" +#include "iface.h" + +/* global config info */ +/* initiator needs initiator name/alias */ +struct iscsi_daemon_config daemon_config; +struct iscsi_daemon_config *dconfig = &daemon_config; + +static node_rec_t config_rec; +static LIST_HEAD(targets); +static LIST_HEAD(user_params); + +static char program_name[] = "iscsistart"; + +/* used by initiator */ +extern struct iscsi_ipc *ipc; + +static struct option const long_options[] = { + {"initiatorname", required_argument, NULL, 'i'}, + {"targetname", required_argument, NULL, 't'}, + {"tgpt", required_argument, NULL, 'g'}, + {"address", required_argument, NULL, 'a'}, + {"port", required_argument, NULL, 'p'}, + {"username", required_argument, NULL, 'u'}, + {"password", required_argument, NULL, 'w'}, + {"username_in", required_argument, NULL, 'U'}, + {"password_in", required_argument, NULL, 'W'}, + {"debug", required_argument, NULL, 'd'}, + {"fwparam_connect", no_argument, NULL, 'b'}, + {"fwparam_network", no_argument, NULL, 'N'}, + {"fwparam_print", no_argument, NULL, 'f'}, + {"param", required_argument, NULL, 'P'}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'v'}, + {NULL, 0, NULL, 0}, +}; + +static void usage(int status) +{ + if (status != 0) + fprintf(stderr, "Try `%s --help' for more information.\n", + program_name); + else { + printf("Usage: %s [OPTION]\n", program_name); + printf("\ +Open-iSCSI initiator.\n\ + -i, --initiatorname=name set InitiatorName to name (Required)\n\ + -t, --targetname=name set TargetName to name (Required)\n\ + -g, --tgpt=N set target portal group tag to N (Required)\n\ + -a, --address=A.B.C.D set IP address to A.B.C.D (Required)\n\ + -p, --port=N set port to N (Default 3260)\n\ + -u, --username=N set username to N (optional)\n\ + -w, --password=N set password to N (optional\n\ + -U, --username_in=N set incoming username to N (optional)\n\ + -W, --password_in=N set incoming password to N (optional)\n\ + -d, --debug=debuglevel print debugging information \n\ + -b, --fwparam_connect create a session to the target using iBFT or OF\n\ + -N, --fwparam_network bring up the network as specified by iBFT or OF\n\ + -f, --fwparam_print print the iBFT or OF info to STDOUT \n\ + -P, --param=NAME=VALUE set parameter with the name NAME to VALUE\n\ + -h, --help display this help and exit\n\ + -v, --version display version and exit\n\ +"); + } + exit(status); +} + +static int stop_event_loop(void) +{ + iscsiadm_req_t req; + iscsiadm_rsp_t rsp; + int rc; + + memset(&req, 0, sizeof(req)); + req.command = MGMT_IPC_IMMEDIATE_STOP; + rc = iscsid_exec_req(&req, &rsp, 0, -1); + if (rc) { + iscsi_err_print_msg(rc); + log_error("Could not stop event_loop"); + } + return rc; +} + +static int apply_params(struct node_rec *rec) +{ + struct user_param *param; + int rc; + + /* Must init this so we can check if user overrode them */ + rec->session.initial_login_retry_max = -1; + rec->conn[0].timeo.noop_out_interval = -1; + rec->conn[0].timeo.noop_out_timeout = -1; + rec->session.scan = -1; + + list_for_each_entry(param, &user_params, list) { + /* + * user may not have passed in all params that were set by + * ibft/iscsi_boot, so clear out values that might conflict + * with user overrides + */ + if (!strcmp(param->name, IFACE_NETNAME)) { + /* overriding netname so MAC will be for old netdev */ + memset(rec->iface.hwaddress, 0, + sizeof(rec->iface.hwaddress)); + } else if (!strcmp(param->name, IFACE_HWADDR)) { + /* overriding MAC so netdev will be for old MAC */ + memset(rec->iface.netdev, 0, sizeof(rec->iface.netdev)); + } else if (!strcmp(param->name, IFACE_TRANSPORTNAME)) { + /* + * switching drivers so all old binding info is no + * longer valid. Old values were either for offload + * and we are switching to software or the reverse, + * or switching types of cards (bnx2i to cxgb3i). + */ + memset(&rec->iface, 0, sizeof(rec->iface)); + iface_setup_defaults(&rec->iface); + } + } + + rc = idbm_node_set_rec_from_param(&user_params, rec, 0); + if (rc) + return rc; + + /* + * For root boot we could not change this in older versions so + * if user did not override then use the defaults. + * + * Increase to account for boot using static setup. + */ + if (rec->session.initial_login_retry_max == -1) + rec->session.initial_login_retry_max = 30; + /* we used to not be able to answer so turn off */ + if (rec->conn[0].timeo.noop_out_interval == -1) + rec->conn[0].timeo.noop_out_interval = 0; + if (rec->conn[0].timeo.noop_out_timeout == -1) + rec->conn[0].timeo.noop_out_timeout = 0; + if (rec->session.scan == -1) + rec->session.scan = DEF_INITIAL_SCAN; + + return 0; +} + +static int parse_param(char *param_str) +{ + struct user_param *param; + char *name, *value; + + name = param_str; + + value = strchr(param_str, '='); + if (!value) { + log_error("Invalid --param %s. Missing value.", param_str); + return ISCSI_ERR_INVAL; + } + *value = '\0'; + + value++; + if (!strlen(value)) { + log_error("Invalid --param %s. Missing value.", param_str); + return ISCSI_ERR_INVAL; + } + + param = idbm_alloc_user_param(name, value); + if (!param) { + log_error("Could not allocate memory for param."); + return ISCSI_ERR_NOMEM; + } + + list_add(¶m->list, &user_params); + return 0; +} + +static int login_session(struct node_rec *rec) +{ + iscsiadm_req_t req; + iscsiadm_rsp_t rsp; + int rc, msec, err; + struct timespec ts; + + rc = apply_params(rec); + if (rc) + return rc; + + printf("%s: Logging into %s %s:%d,%d\n", program_name, rec->name, + rec->conn[0].address, rec->conn[0].port, + rec->tpgt); + memset(&req, 0, sizeof(req)); + req.command = MGMT_IPC_SESSION_LOGIN; + memcpy(&req.u.session.rec, rec, sizeof(*rec)); + + /* + * Need to handle race where iscsid proc is starting up while we are + * trying to connect. Retry with exponential backoff, start from 50 ms. + */ + for (msec = 50; msec <= 15000; msec <<= 1) { + rc = iscsid_exec_req(&req, &rsp, 0, ISCSID_REQ_TIMEOUT); + if (rc == 0) { + return rc; + } else if (rc == ISCSI_ERR_ISCSID_NOTCONN) { + ts.tv_sec = msec / 1000; + ts.tv_nsec = (msec % 1000) * 1000000L; + + /* On EINTR, retry nanosleep with remaining time. */ + while ((err = nanosleep(&ts, &ts)) < 0 && + errno == EINTR); + if (err < 0) + break; + } else { + break; + } + } + + iscsi_err_print_msg(rc); + return rc; +} + +static int setup_session(void) +{ + struct boot_context *context; + int rc = 0, rc2 = 0; + + if (list_empty(&targets)) + return login_session(&config_rec); + + list_for_each_entry(context, &targets, list) { + struct node_rec *rec; + + rec = idbm_create_rec_from_boot_context(context); + if (!rec) { + log_error("Could not allocate memory. Could " + "not start boot session to " + "%s,%s,%d", context->targetname, + context->target_ipaddr, + context->target_port); + continue; + } + + rc2 = login_session(rec); + if (rc2) + rc = rc2; + free(rec); + } + fw_free_targets(&targets); + return rc; +} + +int session_in_use(int sid) { return 0; } + +static void catch_signal(int signo) +{ + log_warning("pid %d caught signal -%d", getpid(), signo); +} + +static int check_params(char *initiatorname) +{ + if (!initiatorname) { + log_error("InitiatorName not set. Exiting %s", program_name); + return EINVAL; + } + + if (config_rec.tpgt == PORTAL_GROUP_TAG_UNKNOWN) { + log_error("Portal Group not set. Exiting %s", program_name); + return EINVAL; + } + + if (!strlen(config_rec.name)) { + log_error("TargetName not set. Exiting %s", program_name); + return EINVAL; + } + + if (!strlen(config_rec.conn[0].address)) { + log_error("IP Address not set. Exiting %s", program_name); + return EINVAL; + } + + return 0; +} + +#define check_str_param_len(str, max_len, param) \ +do { \ + if (strlen(str) > max_len) { \ + printf("%s: invalid %s %s. Max %s length is %d.\n", \ + program_name, param, str, param, max_len); \ + exit(ISCSI_ERR_INVAL); \ + } \ +} while (0); + +int main(int argc, char *argv[]) +{ + struct utsname host_info; /* will use to compound initiator alias */ + struct iscsi_auth_config *auth; + char *initiatorname = NULL; + int ch, longindex, ret; + struct boot_context *context, boot_context; + struct sigaction sa_old; + struct sigaction sa_new; + int control_fd, mgmt_ipc_fd, err; + pid_t pid; + + idbm_node_setup_defaults(&config_rec); + config_rec.name[0] = '\0'; + config_rec.conn[0].address[0] = '\0'; + auth = &config_rec.session.auth; + + /* do not allow ctrl-c for now... */ + sa_new.sa_handler = catch_signal; + sigemptyset(&sa_new.sa_mask); + sa_new.sa_flags = 0; + sigaction(SIGINT, &sa_new, &sa_old ); + + /* initialize logger */ + log_init(program_name, DEFAULT_AREA_SIZE, log_do_log_std, NULL); + + sysfs_init(); + + while ((ch = getopt_long(argc, argv, "P:i:t:g:a:p:d:u:w:U:W:bNfvh", + long_options, &longindex)) >= 0) { + switch (ch) { + case 'i': + initiatorname = optarg; + break; + case 't': + check_str_param_len(optarg, TARGET_NAME_MAXLEN, + "targetname"); + strlcpy(config_rec.name, optarg, TARGET_NAME_MAXLEN); + break; + case 'g': + config_rec.tpgt = atoi(optarg); + break; + case 'a': + check_str_param_len(optarg, NI_MAXHOST, "address"); + strlcpy(config_rec.conn[0].address, optarg, NI_MAXHOST); + break; + case 'p': + config_rec.conn[0].port = atoi(optarg); + break; + case 'w': + check_str_param_len(optarg, AUTH_STR_MAX_LEN, + "password"); + strlcpy((char *)auth->password, optarg, + AUTH_STR_MAX_LEN); + auth->password_length = strlen((char *)auth->password); + break; + case 'W': + check_str_param_len(optarg, AUTH_STR_MAX_LEN, + "password_in"); + strlcpy((char *)auth->password_in, optarg, + AUTH_STR_MAX_LEN); + auth->password_in_length = + strlen((char *)auth->password_in); + break; + case 'u': + check_str_param_len(optarg, AUTH_STR_MAX_LEN, + "username"); + strlcpy(auth->username, optarg, AUTH_STR_MAX_LEN); + break; + case 'U': + check_str_param_len(optarg, AUTH_STR_MAX_LEN, + "username_in"); + strlcpy(auth->username_in, optarg, AUTH_STR_MAX_LEN); + break; + case 'd': + log_level = atoi(optarg); + break; + case 'b': + memset(&boot_context, 0, sizeof(boot_context)); + ret = fw_get_entry(&boot_context); + if (ret) { + printf("Could not get boot entry.\n"); + exit(ret); + } + + initiatorname = boot_context.initiatorname; + ret = fw_get_targets(&targets); + if (ret || list_empty(&targets)) { + printf("Could not setup fw entries.\n"); + exit(ret); + } + break; + case 'N': + exit(fw_setup_nics()); + case 'f': + ret = fw_get_targets(&targets); + if (ret || list_empty(&targets)) { + printf("Could not get list of targets from " + "firmware.\n"); + exit(ret); + } + + list_for_each_entry(context, &targets, list) + fw_print_entry(context); + + fw_free_targets(&targets); + exit(0); + case 'P': + err = parse_param(optarg); + if (err) + exit(err); + break; + case 'v': + printf("%s version %s\n", program_name, + ISCSI_VERSION_STR); + exit(0); + case 'h': + usage(0); + break; + default: + usage(ISCSI_ERR_INVAL); + break; + } + } + + if (list_empty(&targets) && check_params(initiatorname)) + exit(ISCSI_ERR_INVAL); + + pid = fork(); + if (pid < 0) { + log_error("iscsiboot fork failed"); + exit(ISCSI_ERR_NOMEM); + } else if (pid) { + int status, rc, rc2; + + /* make a special socket path for only this iscsistart instance */ + iscsid_set_namespace(pid); + sleep(1); + + rc = setup_session(); + rc2 = stop_event_loop(); + /* + * something horrible happened. kill child and get + * out of here + */ + if (rc2) + kill(pid, SIGTERM); + + waitpid(pid, &status, WUNTRACED); + if (rc || rc2) + exit(ISCSI_ERR); + + log_debug(1, "iscsi parent done"); + exit(0); + } + + pid = getpid(); + iscsid_set_namespace(pid); + + mgmt_ipc_fd = mgmt_ipc_listen(); + if (mgmt_ipc_fd < 0) { + log_error("Could not setup mgmt ipc"); + exit(ISCSI_ERR_NOMEM); + } + + control_fd = ipc->ctldev_open(); + if (control_fd < 0) + exit(ISCSI_ERR_NOMEM); + + memset(&daemon_config, 0, sizeof (daemon_config)); + daemon_config.initiator_name = initiatorname; + /* optional InitiatorAlias */ + memset(&host_info, 0, sizeof (host_info)); + if (uname(&host_info) >= 0) + daemon_config.initiator_alias = host_info.nodename; + + log_debug(1, "InitiatorName=%s", daemon_config.initiator_name); + log_debug(1, "InitiatorAlias=%s", daemon_config.initiator_alias); + log_debug(1, "TargetName=%s", config_rec.name); + log_debug(1, "TPGT=%d", config_rec.tpgt); + log_debug(1, "IP Address=%s", config_rec.conn[0].address); + + /* log the version, so that we can tell if the daemon and kernel module + * match based on what shows up in the syslog. Tarballs releases + * always install both, but Linux distributors may put the kernel module + * in a different RPM from the daemon and utils, and users may try to + * mix and match in ways that don't work. + */ + log_error("version %s", ISCSI_VERSION_STR); + + /* oom-killer will not kill us at the night... */ + if (oom_adjust()) + log_debug(1, "can not adjust oom-killer's pardon"); + + /* + * Start Main Event Loop + */ + iscsi_initiator_init(); + event_loop(ipc, control_fd, mgmt_ipc_fd); + ipc->ctldev_close(); + mgmt_ipc_close(mgmt_ipc_fd); + free_initiator(); + sysfs_cleanup(); + + log_debug(1, "iscsi child done"); + return 0; +} diff --git a/usr/iser.c b/usr/iser.c new file mode 100644 index 0000000..30459bb --- /dev/null +++ b/usr/iser.c @@ -0,0 +1,22 @@ +/* + * iser helpers + * + * Copyright (C) 2012 Red Hat, Inc. 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 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. + */ +#include "initiator.h" + +void iser_create_conn(struct iscsi_conn *conn) +{ + /* header digests not supported in iser */ + conn->hdrdgst_en = ISCSI_DIGEST_NONE; +} diff --git a/usr/iser.h b/usr/iser.h new file mode 100644 index 0000000..e805cae --- /dev/null +++ b/usr/iser.h @@ -0,0 +1,8 @@ +#ifndef ISER_TRANSPORT +#define ISER_TRANSPORT + +struct iscsi_conn; + +extern void iser_create_conn(struct iscsi_conn *conn); + +#endif diff --git a/usr/kern_err_table.c b/usr/kern_err_table.c new file mode 100644 index 0000000..8af6f05 --- /dev/null +++ b/usr/kern_err_table.c @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2011 Aastha Mehta + * Copyright (C) 2011 Mike Christie + * + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#include +#include +#include +#include "iscsi_if.h" + +#include "kern_err_table.h" + +const char *kern_err_code_to_string(int err) +{ + switch (err){ + case ISCSI_OK: + return "ISCSI_OK: operation successful"; + case ISCSI_ERR_DATASN: + return "ISCSI_ERR_DATASN: Received invalid data sequence " + "number from target"; + case ISCSI_ERR_DATA_OFFSET: + return "ISCSI_ERR_DATA_OFFSET: Seeking offset beyond the size " + "of the iSCSI segment"; + case ISCSI_ERR_MAX_CMDSN: + return "ISCSI_ERR_MAX_CMDSN: Received invalid command sequence " + "number from target"; + case ISCSI_ERR_EXP_CMDSN: + return "ISCSI_ERR_EXP_CMDSN: Received invalid expected command " "sequence number from target"; + case ISCSI_ERR_BAD_OPCODE: + return "ISCSI_ERR_BAD_OPCODE: Received an invalid iSCSI opcode"; + case ISCSI_ERR_DATALEN: + return "ISCSI_ERR_DATALEN: Invalid data length value"; + case ISCSI_ERR_AHSLEN: + return "ISCSI_ERR_AHSLEN: Received an invalid AHS length"; + case ISCSI_ERR_PROTO: + return "ISCSI_ERR_PROTO: iSCSI protocol violation"; + case ISCSI_ERR_LUN: + return "ISCSI_ERR_LUN: LUN mismatch"; + case ISCSI_ERR_BAD_ITT: + return "ISCSI_ERR_BAD_ITT: Received invalid initiator task tag " "from target"; + case ISCSI_ERR_CONN_FAILED: + return "ISCSI_ERR_CONN_FAILED: iSCSI connection failed"; + case ISCSI_ERR_R2TSN: + return "ISCSI_ERR_R2TSN: Received invalid R2T (Ready to " + "Transfer) data sequence number from target"; + case ISCSI_ERR_SESSION_FAILED: + return "ISCSI_ERR_SESSION_FAILED: iSCSI session failed"; + case ISCSI_ERR_HDR_DGST: + return "ISCSI_ERR_HDR_DGST: Header digest mismatch"; + case ISCSI_ERR_DATA_DGST: + return "ISCSI_ERR_DATA_DGST: Data digest mismatch"; + case ISCSI_ERR_PARAM_NOT_FOUND: + return "ISCSI_ERR_PARAM_NOT_FOUND: Parameter not found"; + case ISCSI_ERR_NO_SCSI_CMD: + return "ISCSI_ERR_NO_SCSI_CMD: Could not look up SCSI command"; + case ISCSI_ERR_INVALID_HOST: + return "ISCSI_ERR_INVALID_HOST: iSCSI host is in an invalid " + "state"; + case ISCSI_ERR_XMIT_FAILED: + return "ISCSI_ERR_XMIT_FAILED: Transmission of iSCSI packet " + "failed"; + case ISCSI_ERR_TCP_CONN_CLOSE: + return "ISCSI_ERR_TCP_CONN_CLOSE: TCP connection closed"; + case ISCSI_ERR_SCSI_EH_SESSION_RST: + return "ISCSI_ERR_SCSI_EH_SESSION_RST: Session was dropped as " + "a result of SCSI error recovery"; + case ISCSI_ERR_NOP_TIMEDOUT: + return "ISCSI_ERR_NOP_TIMEDOUT: A NOP has timed out"; + default: + return "Invalid or unknown error code"; + } +} diff --git a/usr/kern_err_table.h b/usr/kern_err_table.h new file mode 100644 index 0000000..592e40d --- /dev/null +++ b/usr/kern_err_table.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2011 Aastha Mehta + * Copyright (C) 2011 Mike Christie + * + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#ifndef __KERN_ERR_TABLE_H__ +#define __KERN_ERR_TABLE_H__ + +extern const char *kern_err_code_to_string(int); +#endif diff --git a/usr/local_strings.c b/usr/local_strings.c new file mode 100644 index 0000000..d0feb6d --- /dev/null +++ b/usr/local_strings.c @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2002 Cisco Systems, Inc. + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "local_strings.h" +#include "log.h" + +int str_init_buffer(struct str_buffer *s, size_t initial_allocation) +{ + if (s) { + memset(s, 0, sizeof (*s)); + s->buffer = NULL; + if (initial_allocation) { + s->buffer = malloc(initial_allocation); + if (s->buffer) { + s->allocated_length = initial_allocation; + memset(s->buffer, 0, initial_allocation); + } + } + s->data_length = 0; + return 1; + } + + return 0; +} + +struct str_buffer *str_alloc_buffer(size_t initial_allocation) +{ + struct str_buffer *s = calloc(1, sizeof (*s)); + + if (s) + str_init_buffer(s, initial_allocation); + + return s; +} + +void str_free_buffer(struct str_buffer *s) +{ + if (s) { + if (s->buffer) { + free(s->buffer); + s->buffer = NULL; + } + s->allocated_length = 0; + s->data_length = 0; + } +} + +int str_enlarge_data(struct str_buffer *s, int length) +{ + void *new_buf; + + if (s) { + s->data_length += length; + if (s->data_length > s->allocated_length) { + log_debug(7, "enlarge buffer from %lu to %lu", + s->allocated_length, s->data_length); + new_buf = realloc(s->buffer, s->data_length); + if (!new_buf) { + /* too big */ + log_error("enlarged buffer %p to %d data " + "bytes, with only %d bytes of buffer " + "space", s, (int)s->data_length, + (int)s->allocated_length); + return ENOMEM; + } + s->buffer = new_buf; + memset(s->buffer + s->allocated_length, 0, + s->data_length - s->allocated_length); + s->allocated_length = s->data_length; + } + } + + return 0; +} + +void str_remove_initial(struct str_buffer *s, int length) +{ + char *remaining; + int amount; + + if (s && length) { + remaining = s->buffer + length; + amount = s->data_length - length; + + if (amount < 0) + amount = 0; + if (amount) + memmove(s->buffer, remaining, amount); + s->data_length = amount; + s->buffer[amount] = '\0'; + } +} + +/* truncate the data length down */ +void str_truncate_buffer(struct str_buffer *s, size_t length) +{ + if (s) { + if (!s->data_length) + return; + if (length <= s->data_length) { + s->data_length = length; + s->buffer[s->data_length] = '\0'; + } else if (length <= s->allocated_length) { + /* clear the data, and declare the + * data length to be larger + */ + memset(s->buffer + s->data_length, 0, + length - s->data_length); + s->data_length = length; + } else { + log_error( + "couldn't truncate data buffer to length %d, " + "only allocated %d", + (int)length, (int)s->allocated_length); + } + } +} + +char *str_buffer_data(struct str_buffer *s) +{ + if (s) + return s->buffer; + else + return NULL; +} + +size_t str_data_length(struct str_buffer * s) +{ + if (s) + return s->data_length; + else + return 0; +} + +size_t str_unused_length(struct str_buffer * s) +{ + if (s) + return s->allocated_length - s->data_length; + else + return 0; +} diff --git a/usr/local_strings.h b/usr/local_strings.h new file mode 100644 index 0000000..22d21e4 --- /dev/null +++ b/usr/local_strings.h @@ -0,0 +1,41 @@ +/* + * iSCSI variable-sized string buffers + * + * Copyright (C) 2002 Cisco Systems, Inc. + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#ifndef STRINGS_H +#define STRINGS_H + +struct str_buffer { + size_t allocated_length; + size_t data_length; /* not including the trailing NUL */ + char *buffer; +}; + +extern int str_init_buffer(struct str_buffer *s, size_t initial_allocation); +extern struct str_buffer *str_alloc_buffer(size_t initial_allocation); +extern void str_free_buffer(struct str_buffer *s); + +extern int str_enlarge_data(struct str_buffer *s, int length); +extern void str_remove_initial(struct str_buffer *s, int length); +extern void str_truncate_buffer(struct str_buffer *s, size_t length); +extern char *str_buffer_data(struct str_buffer *s); +extern size_t str_data_length(struct str_buffer *s); +extern size_t str_unused_length(struct str_buffer *s); + +#endif /* STRINGS_H */ diff --git a/usr/log.c b/usr/log.c new file mode 100644 index 0000000..b730642 --- /dev/null +++ b/usr/log.c @@ -0,0 +1,486 @@ +/* + * Copyright (C) 2002-2003 Ardis Technolgies + * + * Released under the terms of the GNU GPL v2.0. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "iscsi_util.h" +#include "log.h" + +#define SEMKEY 0xA7L +#define LOGDBG 0 + +#if LOGDBG +#define logdbg(file, fmt, args...) fprintf(file, fmt, ##args) +#else +#define logdbg(file, fmt, args...) do {} while (0) +#endif + +char *log_name; +int log_level = 0; + +static int log_stop_daemon = 0; +static void (*log_func)(int prio, void *priv, const char *fmt, va_list ap); +static void *log_func_priv; + +static void free_logarea (void) +{ + int shmid; + + if (!la) + return; + + if (la->semid != -1) + semctl(la->semid, 0, IPC_RMID, la->semarg); + if (la->buff) { + shmdt(la->buff); + shmctl(la->shmid_buff, IPC_RMID, NULL); + la->buff = NULL; + la->shmid_buff = -1; + } + if (la->start) { + shmdt(la->start); + shmctl(la->shmid_msg, IPC_RMID, NULL); + la->start = NULL; + la->shmid_msg = -1; + } + shmid = la->shmid; + shmdt(la); + shmctl(shmid, IPC_RMID, NULL); + la = NULL; +} + +static int logarea_init (int size) +{ + int shmid; + + logdbg(stderr,"enter logarea_init\n"); + + if ((shmid = shmget(IPC_PRIVATE, sizeof(struct logarea), + 0644 | IPC_CREAT | IPC_EXCL)) == -1) { + syslog(LOG_ERR, "shmget logarea failed %d", errno); + return 1; + } + + la = shmat(shmid, NULL, 0); + if (!la) { + syslog(LOG_ERR, "shmat logarea failed %d", errno); + shmctl(shmid, IPC_RMID, NULL); + return 1; + } + la->shmid = shmid; + la->start = NULL; + la->buff = NULL; + la->semid = -1; + + if (size < MAX_MSG_SIZE) + size = DEFAULT_AREA_SIZE; + + if ((shmid = shmget(IPC_PRIVATE, size, + 0644 | IPC_CREAT | IPC_EXCL)) == -1) { + syslog(LOG_ERR, "shmget msg failed %d", errno); + free_logarea(); + return 1; + } + la->shmid_msg = shmid; + + la->start = shmat(la->shmid_msg, NULL, 0); + if (!la->start) { + syslog(LOG_ERR, "shmat msg failed %d", errno); + free_logarea(); + return 1; + } + memset(la->start, 0, size); + + la->empty = 1; + la->end = la->start + size; + la->head = la->start; + la->tail = la->start; + + if ((shmid = shmget(IPC_PRIVATE, MAX_MSG_SIZE + sizeof(struct logmsg), + 0644 | IPC_CREAT | IPC_EXCL)) == -1) { + syslog(LOG_ERR, "shmget logmsg failed %d", errno); + free_logarea(); + return 1; + } + la->buff = shmat(shmid, NULL, 0); + if (!la->buff) { + syslog(LOG_ERR, "shmat logmsgfailed %d", errno); + free_logarea(); + return 1; + } + + if ((la->semid = semget(SEMKEY, 1, 0600 | IPC_CREAT)) < 0) { + syslog(LOG_ERR, "semget failed %d", errno); + free_logarea(); + return 1; + } + + la->semarg.val=1; + if (semctl(la->semid, 0, SETVAL, la->semarg) < 0) { + syslog(LOG_ERR, "semctl failed %d", errno); + free_logarea(); + return 1; + } + + la->shmid_buff = shmid; + la->ops[0].sem_num = 0; + la->ops[0].sem_flg = 0; + + return 0; + +} + +#if LOGDBG +static void dump_logarea (void) +{ + struct logmsg * msg; + + logdbg(stderr, "\n==== area: start addr = %p, end addr = %p ====\n", + la->start, la->end); + logdbg(stderr, "|addr |next |prio|msg\n"); + + for (msg = (struct logmsg *)la->head; (void *)msg != la->tail; + msg = msg->next) + logdbg(stderr, "|%p |%p |%i |%s\n", (void *)msg, msg->next, + msg->prio, (char *)&msg->str); + + logdbg(stderr, "|%p |%p |%i |%s\n", (void *)msg, msg->next, + msg->prio, (char *)&msg->str); + + logdbg(stderr, "\n\n"); +} +#endif + +int log_enqueue (int prio, const char * fmt, va_list ap) +{ + int len, fwd; + char buff[MAX_MSG_SIZE]; + struct logmsg * msg; + struct logmsg * lastmsg; + + lastmsg = (struct logmsg *)la->tail; + + if (!la->empty) { + fwd = sizeof(struct logmsg) + + strlen((char *)&lastmsg->str) * sizeof(char) + 1; + la->tail += fwd; + } + vsnprintf(buff, MAX_MSG_SIZE, fmt, ap); + len = strlen(buff) * sizeof(char) + 1; + + /* not enough space on tail : rewind */ + if (la->head <= la->tail && + (len + sizeof(struct logmsg)) > (la->end - la->tail)) { + logdbg(stderr, "enqueue: rewind tail to %p\n", la->tail); + la->tail = la->start; + + if (la->empty) + la->head = lastmsg = la->tail; + } + + /* not enough space on head : drop msg */ + if (la->head > la->tail && + (len + sizeof(struct logmsg)) > (la->head - la->tail)) { + logdbg(stderr, "enqueue: log area overrun, drop msg\n"); + + if (!la->empty) + la->tail = lastmsg; + + return 1; + } + + /* ok, we can stage the msg in the area */ + la->empty = 0; + msg = (struct logmsg *)la->tail; + msg->prio = prio; + memcpy((void *)&msg->str, buff, len); + lastmsg->next = la->tail; + msg->next = la->head; + + logdbg(stderr, "enqueue: %p, %p, %i, %s\n", (void *)msg, msg->next, + msg->prio, (char *)&msg->str); + +#if LOGDBG + dump_logarea(); +#endif + return 0; +} + +int log_dequeue (void * buff) +{ + struct logmsg * src = (struct logmsg *)la->head; + struct logmsg * dst = (struct logmsg *)buff; + struct logmsg * lst = (struct logmsg *)la->tail; + int len; + + if (la->empty) + return 0; + + len = strlen((char *)&src->str) * sizeof(char) + + sizeof(struct logmsg) + 1; + + dst->prio = src->prio; + memcpy(dst, src, len); + + if (la->tail == la->head) + la->empty = 1; /* purge the last log msg */ + else { + la->head = src->next; + lst->next = la->head; + } + logdbg(stderr, "dequeue: %p, %p, %i, %s\n", + (void *)src, src->next, src->prio, (char *)&src->str); + + memset((void *)src, 0, len); + + return len; +} + +/* + * this one can block under memory pressure + */ +static void log_syslog (void * buff) +{ + struct logmsg * msg = (struct logmsg *)buff; + + syslog(msg->prio, "%s", (char *)&msg->str); +} + +void log_do_log_daemon(int prio, void *priv, const char *fmt, va_list ap) +{ + struct sembuf ops[1]; + + ops[0].sem_num = la->ops[0].sem_num; + ops[0].sem_flg = la->ops[0].sem_flg; + + ops[0].sem_op = -1; + if (semop(la->semid, ops, 1) < 0) { + syslog(LOG_ERR, "semop down failed %d", errno); + return; + } + + log_enqueue(prio, fmt, ap); + + ops[0].sem_op = 1; + if (semop(la->semid, ops, 1) < 0) + syslog(LOG_ERR, "semop up failed"); +} + +void log_do_log_std(int prio, void *priv, const char *fmt, va_list ap) +{ + if (prio == LOG_INFO) { + vfprintf(stdout, fmt, ap); + fprintf(stdout, "\n"); + } else { + fprintf(stderr, "%s: ", log_name); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + fflush(stderr); + } +} + +void log_warning(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + log_func(LOG_WARNING, log_func_priv, fmt, ap); + va_end(ap); +} + +void log_error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + log_func(LOG_ERR, log_func_priv, fmt, ap); + va_end(ap); +} + +void log_debug(int level, const char *fmt, ...) +{ + if (log_level > level) { + va_list ap; + va_start(ap, fmt); + log_func(LOG_DEBUG, log_func_priv, fmt, ap); + va_end(ap); + } +} + +void log_info(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + log_func(LOG_INFO, log_func_priv, fmt, ap); + va_end(ap); +} + +#if 0 /* Unused */ +static void __dump_line(int level, unsigned char *buf, int *cp) +{ + char line[16*3+5], *lp = line; + int i, cnt; + + cnt = *cp; + if (!cnt) + return; + for (i = 0; i < 16; i++) { + if (i < cnt) + lp += sprintf(lp, " %02x", buf[i]); + else + lp += sprintf(lp, " "); + if ((i % 4) == 3) + lp += sprintf(lp, " |"); + if (i >= cnt || !isprint(buf[i])) + buf[i] = ' '; + } + log_debug(level, "%s %.16s |", line, buf); + *cp = 0; +} + +static void __dump_char(int level, unsigned char *buf, int *cp, int ch) +{ + int cnt = (*cp)++; + + buf[cnt] = ch; + if (cnt == 15) + __dump_line(level, buf, cp); +} + +#define dump_line() __dump_line(level, char_buf, &char_cnt) +#define dump_char(ch) __dump_char(level, char_buf, &char_cnt, ch) +#endif /* Unused */ + +static void log_flush(void) +{ + int msglen; + struct sembuf ops[1]; + + ops[0].sem_num = la->ops[0].sem_num; + ops[0].sem_flg = la->ops[0].sem_flg; + + + while (!la->empty) { + ops[0].sem_op = -1; + if (semop(la->semid, ops, 1) < 0) { + syslog(LOG_ERR, "semop down failed %d", errno); + exit(1); + } + msglen = log_dequeue(la->buff); + ops[0].sem_op = 1; + if (semop(la->semid, ops, 1) < 0) { + syslog(LOG_ERR, "semop up failed"); + exit(1); + } + if (msglen) + log_syslog(la->buff); + } +} + +static void catch_signal(int signo) +{ + switch (signo) { + case SIGSEGV: + log_flush(); + break; + case SIGTERM: + log_stop_daemon = 1; + break; + } + + log_debug(1, "pid %d caught signal -%d", getpid(), signo); +} + +static void __log_close(void) +{ + if (log_func == log_do_log_daemon) { + log_flush(); + closelog(); + free_logarea(); + } +} + +int log_init(char *program_name, int size, + void (*func)(int prio, void *priv, const char *fmt, va_list ap), + void *priv) +{ + logdbg(stderr,"enter log_init\n"); + log_name = program_name; + log_func = func; + log_func_priv = priv; + + if (log_func == log_do_log_daemon) { + struct sigaction sa_old; + struct sigaction sa_new; + pid_t pid; + + openlog(log_name, 0, LOG_DAEMON); + setlogmask (LOG_UPTO (LOG_DEBUG)); + + if (logarea_init(size)) { + syslog(LOG_ERR, "logarea init failed"); + return -1; + } + + pid = fork(); + if (pid < 0) { + syslog(LOG_ERR, "starting logger failed"); + exit(1); + } else if (pid) { + syslog(LOG_WARNING, + "iSCSI logger with pid=%d started!", pid); + return pid; + } + + daemon_init(); + + /* flush on daemon's crash */ + sa_new.sa_handler = (void*)catch_signal; + sigemptyset(&sa_new.sa_mask); + sa_new.sa_flags = 0; + sigaction(SIGSEGV, &sa_new, &sa_old ); + sigaction(SIGTERM, &sa_new, &sa_old ); + + while(1) { + log_flush(); + sleep(1); + + if (log_stop_daemon) + break; + } + + __log_close(); + exit(0); + } + + return 0; +} + +void log_close(pid_t pid) +{ + int status; + + if (log_func != log_do_log_daemon || pid < 0) { + __log_close(); + return; + } + + if (pid > 0) { + kill(pid, SIGTERM); + waitpid(pid, &status, 0); + } +} diff --git a/usr/log.h b/usr/log.h new file mode 100644 index 0000000..486a08e --- /dev/null +++ b/usr/log.h @@ -0,0 +1,86 @@ +/* + * iSCSI Safe Logging and Tracing Library + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * maintained by open-iscsi@googlegroups.com + * + * circular buffer code based on log.c from dm-multipath project + * + * heavily based on code from log.c: + * Copyright (C) 2002-2003 Ardis Technolgies , + * licensed under the terms of the GNU GPL v2.0, + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#ifndef LOG_H +#define LOG_H + +#include +#include +#include "iscsid.h" + +union semun { + int val; + struct semid_ds *buf; + unsigned short int *array; + struct seminfo *__buf; +}; +#include + +#define DEFAULT_AREA_SIZE 16384 +#define MAX_MSG_SIZE 256 + +extern int log_level; + +struct logmsg { + short int prio; + void *next; + char *str; +}; + +struct logarea { + int shmid; + int shmid_msg; + int shmid_buff; + int empty; + void *head; + void *tail; + void *start; + void *end; + char *buff; + struct sembuf ops[1]; + int semid; + union semun semarg; +}; + +struct logarea *la; + +extern int log_init(char *program_name, int size, + void (*func)(int prio, void *priv, const char *fmt, va_list ap), + void *priv); +extern void log_close (pid_t pid); +extern void dump_logmsg (void *); +extern void log_info(const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); +extern void log_warning(const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); +extern void log_error(const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); +extern void log_debug(int level, const char *fmt, ...) + __attribute__ ((format (printf, 2, 3))); + +extern void log_do_log_daemon(int prio, void *priv, const char *fmt, va_list ap); +extern void log_do_log_std(int prio, void *priv, const char *fmt, va_list ap); + +#endif /* LOG_H */ diff --git a/usr/login.c b/usr/login.c new file mode 100644 index 0000000..d7dad21 --- /dev/null +++ b/usr/login.c @@ -0,0 +1,1607 @@ +/* + * iSCSI Login Library + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * maintained by open-iscsi@googlegroups.com + * + * heavily based on code from iscsi-login.c: + * Copyright (C) 2001 Cisco Systems, Inc. + * + * 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. + * + * See the file COPYING included with this distribution for more details. + * + * Formation of iSCSI login pdu, processing the login response and other + * functions are defined here + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "initiator.h" +#include "transport.h" +#include "log.h" +#include "iscsi_timer.h" + +/* caller is assumed to be well-behaved and passing NUL terminated strings */ +int +iscsi_add_text(struct iscsi_hdr *pdu, char *data, int max_data_length, + char *param, char *value) +{ + int param_len = strlen(param); + int value_len = strlen(value); + int length = param_len + 1 + value_len + 1; /* param, separator, + * value, and trailing + * NULL + */ + int pdu_length = ntoh24(pdu->dlength); + char *text = data; + char *end = data + max_data_length; + + /* find the end of the current text */ + text += pdu_length; + pdu_length += length; + + if (text + length >= end) { + log_warning("Failed to add login text " + "'%s=%s'", param, value); + return 0; + } + + /* param */ + strncpy(text, param, param_len); + text += param_len; + + /* separator */ + *text++ = ISCSI_TEXT_SEPARATOR; + + /* value */ + strncpy(text, value, value_len); + text += value_len; + + /* NUL */ + *text++ = '\0'; + + /* update the length in the PDU header */ + hton24(pdu->dlength, pdu_length); + + return 1; +} + +static int +iscsi_find_key_value(char *param, char *pdu, char *pdu_end, char **value_start, + char **value_end) +{ + char *str = param; + char *text = pdu; + char *value; + + if (value_start) + *value_start = NULL; + if (value_end) + *value_end = NULL; + + /* make sure they contain the same bytes */ + while (*str) { + if (text >= pdu_end) + return 0; + if (*text == '\0') + return 0; + if (*str != *text) + return 0; + str++; + text++; + } + + if ((text >= pdu_end) || (*text == '\0') + || (*text != ISCSI_TEXT_SEPARATOR)) { + return 0; + } + + /* find the value */ + value = text + 1; + + /* find the end of the value */ + while ((text < pdu_end) && (*text)) + text++; + + if (value_start) + *value_start = value; + if (value_end) + *value_end = text; + + return 1; +} + +static enum iscsi_login_status +get_auth_key_type(struct iscsi_acl *auth_client, char **data, char *end) +{ + char *key; + char *value = NULL; + char *value_end = NULL; + char *text = *data; + + int keytype = AUTH_KEY_TYPE_NONE; + + while (acl_get_next_key_type(&keytype) == AUTH_STATUS_NO_ERROR) { + key = (char *)acl_get_key_name(keytype); + if (key && iscsi_find_key_value(key, text, end, &value, + &value_end)) { + if (acl_recv_key_value(auth_client, keytype, value) != + AUTH_STATUS_NO_ERROR) { + log_error("login negotiation failed, can't " + "accept %s in security stage", text); + return LOGIN_NEGOTIATION_FAILED; + } + text = value_end; + *data = text; + return LOGIN_OK; + } + } + log_error("Login negotiation failed, can't accept %s in security " + "stage", text); + return LOGIN_NEGOTIATION_FAILED; +} + +int +resolve_address(char *host, char *port, struct sockaddr_storage *ss) +{ + struct addrinfo hints, *res; + int rc; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if ((rc = getaddrinfo(host, port, &hints, &res))) { + log_error("Cannot resolve host %s. getaddrinfo error: " + "[%s]", host, gai_strerror(rc)); + return rc; + } + + memcpy(ss, res->ai_addr, res->ai_addrlen); + + freeaddrinfo(res); + + return rc; +} + +/* + * try to reset the session's IP address and port, based on the TargetAddress + * provided + */ +int +iscsi_update_address(iscsi_conn_t *conn, char *address) +{ + char *port, *tag; + char default_port[NI_MAXSERV]; + iscsi_session_t *session = conn->session; + struct sockaddr_storage addr; + + if ((tag = strrchr(address, ','))) { + *tag = '\0'; + tag++; + } + if ((port = strrchr(address, ':'))) { + *port = '\0'; + port++; + } + + if (!port) { + sprintf(default_port, "%d", ISCSI_LISTEN_PORT); + port = default_port; + } + + if (*address == '[') { + char *end_bracket; + + if (!(end_bracket = strchr(address, ']'))) { + log_error("Invalid IPv6 address with opening bracket, " + "but no closing bracket."); + return 0; + } + *end_bracket = '\0'; + address++; + } + + if (resolve_address(address, port, &addr)) { + log_error("cannot resolve host name %s", address); + return 0; + } + + conn->saddr = addr; + if (tag) + session->portal_group_tag = atoi(tag); + return 1; +} + +static enum iscsi_login_status +get_security_text_keys(iscsi_session_t *session, int cid, char **data, + struct iscsi_acl *auth_client, char *end) +{ + char *text = *data; + char *value = NULL; + char *value_end = NULL; + size_t size; + int tag; + enum iscsi_login_status ret; + + /* + * a few keys are possible in Security stage + * which the auth code doesn't care about, but + * which we might want to see, or at least not + * choke on. + */ + if (iscsi_find_key_value("TargetAlias", text, end, &value, + &value_end)) { + size = value_end - value; + session->target_alias = malloc(size + 1); + if (!session->target_alias) { + /* Alias not critical. So just print an error */ + log_error("Login failed to allocate alias"); + *data = value_end; + return LOGIN_OK; + } + memcpy(session->target_alias, value, size); + session->target_alias[size] = '\0'; + text = value_end; + } else if (iscsi_find_key_value("TargetAddress", text, end, &value, + &value_end)) { + /* + * if possible, change the session's + * ip_address and port to the new TargetAddress for + * leading connection + */ + if (iscsi_update_address(&session->conn[cid], value)) { + text = value_end; + } else { + log_error("Login redirection failed, " + "can't handle redirection to %s", value); + return LOGIN_REDIRECTION_FAILED; + } + } else if (iscsi_find_key_value("TargetPortalGroupTag", text, end, + &value, &value_end)) { + /* + * We should have already obtained this + * via discovery, but the value could be stale. + * If the target was reconfigured it will send us + * the updated tpgt. + */ + tag = strtoul(value, NULL, 0); + if (session->portal_group_tag >= 0) { + if (tag != session->portal_group_tag) + log_debug(2, "Portal group tag " + "mismatch, expected %u, " + "received %u. Updating", + session->portal_group_tag, tag); + } + /* we now know the tag */ + session->portal_group_tag = tag; + text = value_end; + } else { + /* + * any key we don't recognize either + * goes to the auth code, or we choke + * on it + */ + ret = get_auth_key_type(auth_client, &text, end); + if (ret != LOGIN_OK) + return ret; + } + *data = text; + return LOGIN_OK; +} + +static enum iscsi_login_status +get_op_params_text_keys(iscsi_session_t *session, int cid, + char **data, char *end) +{ + char *text = *data; + char *value = NULL; + char *value_end = NULL; + size_t size; + iscsi_conn_t *conn = &session->conn[cid]; + + if (iscsi_find_key_value("TargetAlias", text, end, &value, + &value_end)) { + size = value_end - value; + if (session->target_alias && + strlen(session->target_alias) == size && + memcmp(session->target_alias, value, size) == 0) { + *data = value_end; + return LOGIN_OK; + } + free(session->target_alias); + session->target_alias = malloc(size + 1); + if (!session->target_alias) { + /* Alias not critical. So just print an error */ + log_error("Login failed to allocate alias"); + *data = value_end; + return LOGIN_OK; + } + memcpy(session->target_alias, value, size); + session->target_alias[size] = '\0'; + text = value_end; + } else if (iscsi_find_key_value("TargetAddress", text, end, &value, + &value_end)) { + if (iscsi_update_address(conn, value)) + text = value_end; + else { + log_error("Login redirection failed, " + "can't handle redirection to %s", + value); + return LOGIN_REDIRECTION_FAILED; + } + } else if (iscsi_find_key_value("TargetPortalGroupTag", text, end, + &value, &value_end)) { + int tag = strtoul(value, NULL, 0); + /* + * We should have already obtained this + * via discovery, but the value could be stale. + * If the target was reconfigured it will send us + * the updated tpgt. + */ + if (session->portal_group_tag >= 0) { + if (tag != session->portal_group_tag) + log_debug(2, "Portal group tag " + "mismatch, expected %u, " + "received %u. Updating", + session->portal_group_tag, tag); + } + /* we now know the tag */ + session->portal_group_tag = tag; + text = value_end; + } else if (iscsi_find_key_value("InitialR2T", text, end, &value, + &value_end)) { + if (session->type == ISCSI_SESSION_TYPE_NORMAL) { + if (value && (strcmp(value, "Yes") == 0)) + session->initial_r2t_en = 1; + else + session->initial_r2t_en = 0; + } else + session->irrelevant_keys_bitmap |= + IRRELEVANT_INITIALR2T; + text = value_end; + } else if (iscsi_find_key_value("ImmediateData", text, end, &value, + &value_end)) { + if (session->type == ISCSI_SESSION_TYPE_NORMAL) { + if (value && (strcmp(value, "Yes") == 0)) + session->imm_data_en = 1; + else + session->imm_data_en = 0; + } else + session->irrelevant_keys_bitmap |= + IRRELEVANT_IMMEDIATEDATA; + text = value_end; + } else if (iscsi_find_key_value("MaxRecvDataSegmentLength", text, end, + &value, &value_end)) { + if (session->type == ISCSI_SESSION_TYPE_DISCOVERY || + !session->t->template->rdma) { + int tgt_max_xmit; + conn_rec_t *conn_rec = &session->nrec.conn[cid]; + + tgt_max_xmit = strtoul(value, NULL, 0); + /* + * if the rec value is zero it means to use + * what the target gave us. + */ + if (!conn_rec->iscsi.MaxXmitDataSegmentLength || + tgt_max_xmit < conn->max_xmit_dlength) + conn->max_xmit_dlength = tgt_max_xmit; + } + text = value_end; + } else if (iscsi_find_key_value("FirstBurstLength", text, end, &value, + &value_end)) { + if (session->type == ISCSI_SESSION_TYPE_NORMAL) + session->first_burst = strtoul(value, NULL, 0); + else + session->irrelevant_keys_bitmap |= + IRRELEVANT_FIRSTBURSTLENGTH; + text = value_end; + } else if (iscsi_find_key_value("MaxBurstLength", text, end, &value, + &value_end)) { + /* + * we don't really care, since it's a limit on the target's + * R2Ts, but record it anwyay + */ + if (session->type == ISCSI_SESSION_TYPE_NORMAL) + session->max_burst = strtoul(value, NULL, 0); + else + session->irrelevant_keys_bitmap |= + IRRELEVANT_MAXBURSTLENGTH; + text = value_end; + } else if (iscsi_find_key_value("HeaderDigest", text, end, &value, + &value_end)) { + if (strcmp(value, "None") == 0) { + if (conn->hdrdgst_en != ISCSI_DIGEST_CRC32C) + conn->hdrdgst_en = ISCSI_DIGEST_NONE; + else { + log_error("Login negotiation " + "failed, HeaderDigest=CRC32C " + "is required, can't accept " + "%s", text); + return LOGIN_NEGOTIATION_FAILED; + } + } else if (strcmp(value, "CRC32C") == 0) { + if (conn->hdrdgst_en != ISCSI_DIGEST_NONE) + conn->hdrdgst_en = ISCSI_DIGEST_CRC32C; + else { + log_error("Login negotiation " + "failed, HeaderDigest=None is " + "required, can't accept %s", text); + return LOGIN_NEGOTIATION_FAILED; + } + } else { + log_error("Login negotiation failed, " + "can't accept %s", text); + return LOGIN_NEGOTIATION_FAILED; + } + text = value_end; + } else if (iscsi_find_key_value("DataDigest", text, end, &value, + &value_end)) { + if (strcmp(value, "None") == 0) { + if (conn->datadgst_en != ISCSI_DIGEST_CRC32C) + conn->datadgst_en = ISCSI_DIGEST_NONE; + else { + log_error("Login negotiation " + "failed, DataDigest=CRC32C " + "is required, can't accept %s", text); + return LOGIN_NEGOTIATION_FAILED; + } + } else if (strcmp(value, "CRC32C") == 0) { + if (conn->datadgst_en != ISCSI_DIGEST_NONE) + conn->datadgst_en = ISCSI_DIGEST_CRC32C; + else { + log_error("Login negotiation " + "failed, DataDigest=None is " + "required, can't accept %s", text); + return LOGIN_NEGOTIATION_FAILED; + } + } else { + log_error("Login negotiation failed, " + "can't accept %s", text); + return LOGIN_NEGOTIATION_FAILED; + } + text = value_end; + } else if (iscsi_find_key_value("DefaultTime2Wait", text, end, &value, + &value_end)) { + session->def_time2wait = strtoul(value, NULL, 0); + text = value_end; + } else if (iscsi_find_key_value("DefaultTime2Retain", text, end, + &value, &value_end)) { + session->def_time2retain = strtoul(value, NULL, 0); + text = value_end; + } else if (iscsi_find_key_value("OFMarker", text, end, &value, + &value_end)) + /* result function is AND, target must honor our No */ + text = value_end; + else if (iscsi_find_key_value("OFMarkInt", text, end, &value, + &value_end)) + /* we don't do markers, so we don't care */ + text = value_end; + else if (iscsi_find_key_value("IFMarker", text, end, &value, + &value_end)) + /* result function is AND, target must honor our No */ + text = value_end; + else if (iscsi_find_key_value("IFMarkInt", text, end, &value, + &value_end)) + /* we don't do markers, so we don't care */ + text = value_end; + else if (iscsi_find_key_value("DataPDUInOrder", text, end, &value, + &value_end)) { + if (session->type == ISCSI_SESSION_TYPE_NORMAL) { + if (value && strcmp(value, "Yes") == 0) + session->pdu_inorder_en = 1; + else + session->pdu_inorder_en = 0; + } else + session->irrelevant_keys_bitmap |= + IRRELEVANT_DATAPDUINORDER; + text = value_end; + } else if (iscsi_find_key_value ("DataSequenceInOrder", text, end, + &value, &value_end)) { + if (session->type == ISCSI_SESSION_TYPE_NORMAL) + if (value && strcmp(value, "Yes") == 0) + session->dataseq_inorder_en = 1; + else + session->dataseq_inorder_en = 0; + else + session->irrelevant_keys_bitmap |= + IRRELEVANT_DATASEQUENCEINORDER; + text = value_end; + } else if (iscsi_find_key_value("MaxOutstandingR2T", text, end, &value, + &value_end)) { + if (session->type == ISCSI_SESSION_TYPE_NORMAL) + session->max_r2t = strtoul(value, NULL, 0); + else + session->irrelevant_keys_bitmap |= + IRRELEVANT_MAXOUTSTANDINGR2T; + text = value_end; + } else if (iscsi_find_key_value("MaxConnections", text, end, &value, + &value_end)) { + if (session->type == ISCSI_SESSION_TYPE_NORMAL) { + if (strcmp(value, "1")) { + log_error("Login negotiation " + "failed, can't accept Max" + "Connections %s", value); + return LOGIN_NEGOTIATION_FAILED; + } + } else + session->irrelevant_keys_bitmap |= + IRRELEVANT_MAXCONNECTIONS; + text = value_end; + } else if (iscsi_find_key_value("ErrorRecoveryLevel", text, end, + &value, &value_end)) { + if (strcmp(value, "0")) { + log_error("Login negotiation failed, " + "can't accept ErrorRecovery %s", value); + return LOGIN_NEGOTIATION_FAILED; + } + text = value_end; + } else if (iscsi_find_key_value("RDMAExtensions", text, end, + &value, &value_end)) { + if (session->t->template->rdma && + strcmp(value, "Yes") != 0) { + log_error("Login negotiation failed, " + "Target must support RDMAExtensions"); + return LOGIN_NEGOTIATION_FAILED; + } + text = value_end; + } else if (iscsi_find_key_value("InitiatorRecvDataSegmentLength", text, + end, &value, &value_end)) { + if (session->t->template->rdma) { + conn->max_recv_dlength = MIN(conn->max_recv_dlength, + strtoul(value, NULL, 0)); + } + text = value_end; + } else if (iscsi_find_key_value("TargetRecvDataSegmentLength", text, + end, &value, &value_end)) { + if (session->t->template->rdma) { + conn->max_xmit_dlength = MIN(conn->max_xmit_dlength, + strtoul(value, NULL, 0)); + } + text = value_end; + } else if (iscsi_find_key_value ("X-com.cisco.protocol", text, end, + &value, &value_end)) { + if (strcmp(value, "NotUnderstood") && + strcmp(value, "Reject") && + strcmp(value, "Irrelevant") && + strcmp(value, "draft20")) { + /* if we didn't get a compatible protocol, fail */ + log_error("Login version mismatch, " + "can't accept protocol %s", value); + return LOGIN_VERSION_MISMATCH; + } + text = value_end; + } else if (iscsi_find_key_value("X-com.cisco.PingTimeout", text, end, + &value, &value_end)) + /* we don't really care what the target ends up using */ + text = value_end; + else if (iscsi_find_key_value("X-com.cisco.sendAsyncText", text, end, + &value, &value_end)) + /* we don't bother for the target response */ + text = value_end; + else { + log_error("Login negotiation failed, couldn't " + "recognize text %s", text); + return LOGIN_NEGOTIATION_FAILED; + } + *data = text; + return LOGIN_OK; +} + +static enum iscsi_login_status +check_security_stage_status(iscsi_session_t *session, + struct iscsi_acl *auth_client) +{ + int debug_status = 0; + + switch (acl_recv_end(auth_client, session)) { + case AUTH_STATUS_CONTINUE: + /* continue sending PDUs */ + break; + + case AUTH_STATUS_PASS: + break; + + case AUTH_STATUS_NO_ERROR: /* treat this as an error, + * since we should get a + * different code + */ + case AUTH_STATUS_ERROR: + case AUTH_STATUS_FAIL: + default: + if (acl_get_dbg_status(auth_client, &debug_status) != + AUTH_STATUS_NO_ERROR) + log_error("Login authentication failed " + "with target %s, %s", + session->target_name, + acl_dbg_status_to_text(debug_status)); + else + log_error("Login authentication failed " + "with target %s", + session->target_name); + return LOGIN_AUTHENTICATION_FAILED; + } + return LOGIN_OK; +} + +/* + * this assumes the text data is always NULL terminated. The caller can + * always arrange for that by using a slightly larger buffer than the max PDU + * size, and then appending a NULL to the PDU. + */ +static enum iscsi_login_status +iscsi_process_login_response(iscsi_session_t *session, int cid, + struct iscsi_login_rsp *login_rsp, + char *data, int max_data_length) +{ + int transit = login_rsp->flags & ISCSI_FLAG_LOGIN_TRANSIT; + char *text = data; + char *end; + int pdu_current_stage, pdu_next_stage; + enum iscsi_login_status ret; + struct iscsi_acl *auth_client; + iscsi_conn_t *conn = &session->conn[cid]; + + auth_client = (session->auth_buffers && session->num_auth_buffers) ? + (struct iscsi_acl *)session->auth_buffers[0].address : NULL; + + end = text + ntoh24(login_rsp->dlength) + 1; + if (end >= (data + max_data_length)) { + log_error("Login failed, process_login_response " + "buffer too small to guarantee NULL " + "termination"); + return LOGIN_FAILED; + } + + /* guarantee a trailing NUL */ + *end = '\0'; + + /* if the response status was success, sanity check the response */ + if (login_rsp->status_class == ISCSI_STATUS_CLS_SUCCESS) { + /* check the active version */ + if (login_rsp->active_version != ISCSI_DRAFT20_VERSION) { + log_error("Login version mismatch, " + "received incompatible active iSCSI " + "version 0x%02x, expected version " + "0x%02x", + login_rsp->active_version, + ISCSI_DRAFT20_VERSION); + return LOGIN_VERSION_MISMATCH; + } + + /* make sure the current stage matches */ + pdu_current_stage = (login_rsp->flags & + ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) >> 2; + if (pdu_current_stage != conn->current_stage) { + log_error("Received invalid login PDU, " + "current stage mismatch, session %d, " + "response %d", conn->current_stage, + pdu_current_stage); + return LOGIN_INVALID_PDU; + } + + /* + * make sure that we're actually advancing if the T-bit is set + */ + pdu_next_stage = login_rsp->flags & + ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK; + if (transit && (pdu_next_stage <= conn->current_stage)) + return LOGIN_INVALID_PDU; + } + + if (conn->current_stage == ISCSI_SECURITY_NEGOTIATION_STAGE) { + if (acl_recv_begin(auth_client) != AUTH_STATUS_NO_ERROR) { + log_error("Login failed because " + "acl_recv_begin failed"); + return LOGIN_FAILED; + } + + if (acl_recv_transit_bit(auth_client, transit) != + AUTH_STATUS_NO_ERROR) { + log_error("Login failed because " + "acl_recv_transit_bit failed"); + return LOGIN_FAILED; + } + } + + /* scan the text data */ + while (text && (text < end)) { + /* skip any NULs separating each text key=value pair */ + while ((text < end) && (*text == '\0')) + text++; + if (text >= end) + break; + + /* handle keys appropriate for each stage */ + switch (conn->current_stage) { + case ISCSI_SECURITY_NEGOTIATION_STAGE:{ + ret = get_security_text_keys(session, cid, + &text, auth_client, end); + if (ret != LOGIN_OK) + return ret; + break; + } + case ISCSI_OP_PARMS_NEGOTIATION_STAGE:{ + ret = get_op_params_text_keys(session, cid, + &text, end); + if (ret != LOGIN_OK) + return ret; + break; + } + default: + return LOGIN_FAILED; + } + } + + if (conn->current_stage == ISCSI_SECURITY_NEGOTIATION_STAGE) { + ret = check_security_stage_status(session, auth_client); + if (ret != LOGIN_OK) + return ret; + } + /* record some of the PDU fields for later use */ + session->tsih = ntohs(login_rsp->tsih); + session->exp_cmdsn = ntohl(login_rsp->exp_cmdsn); + session->max_cmdsn = ntohl(login_rsp->max_cmdsn); + if (login_rsp->status_class == ISCSI_STATUS_CLS_SUCCESS) + conn->exp_statsn = ntohl(login_rsp->statsn) + 1; + + if (transit) { + /* advance to the next stage */ + conn->partial_response = 0; + conn->current_stage = login_rsp->flags & + ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK; + session->irrelevant_keys_bitmap = 0; + } else + /* + * we got a partial response, don't advance, + * more negotiation to do + */ + conn->partial_response = 1; + + return LOGIN_OK; /* this PDU is ok, though the login process + * may not be done yet + */ +} + +static int +add_params_normal_session(iscsi_session_t *session, struct iscsi_hdr *pdu, + char *data, int max_data_length) +{ + char value[AUTH_STR_MAX_LEN]; + + /* these are only relevant for normal sessions */ + if (!iscsi_add_text(pdu, data, max_data_length, "InitialR2T", + session->initial_r2t_en ? "Yes" : "No")) + return 0; + + if (!iscsi_add_text(pdu, data, max_data_length, + "ImmediateData", + session->imm_data_en ? "Yes" : "No")) + return 0; + + sprintf(value, "%d", session->max_burst); + if (!iscsi_add_text(pdu, data, max_data_length, + "MaxBurstLength", value)) + return 0; + + sprintf(value, "%d",session->first_burst); + if (!iscsi_add_text(pdu, data, max_data_length, + "FirstBurstLength", value)) + return 0; + + /* these we must have */ + sprintf(value, "%d", session->max_r2t); + if (!iscsi_add_text(pdu, data, max_data_length, + "MaxOutstandingR2T", value)) + return 0; + if (!iscsi_add_text(pdu, data, max_data_length, + "MaxConnections", "1")) + return 0; + if (!iscsi_add_text(pdu, data, max_data_length, + "DataPDUInOrder", "Yes")) + return 0; + if (!iscsi_add_text(pdu, data, max_data_length, + "DataSequenceInOrder", "Yes")) + return 0; + return 1; +} + +static int +add_params_transport_specific(iscsi_session_t *session, int cid, + struct iscsi_hdr *pdu, char *data, + int max_data_length) +{ + char value[AUTH_STR_MAX_LEN]; + iscsi_conn_t *conn = &session->conn[cid]; + + if (session->type == ISCSI_SESSION_TYPE_DISCOVERY || + !session->t->template->rdma) { + sprintf(value, "%d", conn->max_recv_dlength); + if (!iscsi_add_text(pdu, data, max_data_length, + "MaxRecvDataSegmentLength", value)) + return 0; + } else { + sprintf(value, "%d", conn->max_recv_dlength); + if (!iscsi_add_text(pdu, data, max_data_length, + "InitiatorRecvDataSegmentLength", + value)) + return 0; + + sprintf(value, "%d", conn->max_xmit_dlength); + if (!iscsi_add_text(pdu, data, max_data_length, + "TargetRecvDataSegmentLength", + value)) + return 0; + + if (!iscsi_add_text(pdu, data, max_data_length, + "RDMAExtensions", "Yes")) + return 0; + } + return 1; +} + +static int +check_irrelevant_keys(iscsi_session_t *session, struct iscsi_hdr *pdu, + char *data, int max_data_length) +{ + /* If you receive irrelevant keys, just check them from the irrelevant + * keys bitmap and respond with the key=Irrelevant text + */ + + if (session->irrelevant_keys_bitmap & IRRELEVANT_MAXCONNECTIONS) + if (!iscsi_add_text(pdu, data, max_data_length, + "MaxConnections", "Irrelevant")) + return 0; + + if (session->irrelevant_keys_bitmap & IRRELEVANT_INITIALR2T) + if (!iscsi_add_text(pdu, data, max_data_length, + "InitialR2T", "Irrelevant")) + return 0; + + if (session->irrelevant_keys_bitmap & IRRELEVANT_IMMEDIATEDATA) + if (!iscsi_add_text(pdu, data, max_data_length, + "ImmediateData", "Irrelevant")) + return 0; + + if (session->irrelevant_keys_bitmap & IRRELEVANT_MAXBURSTLENGTH) + if (!iscsi_add_text(pdu, data, max_data_length, + "MaxBurstLength", "Irrelevant")) + return 0; + + if (session->irrelevant_keys_bitmap & IRRELEVANT_FIRSTBURSTLENGTH) + if (!iscsi_add_text(pdu, data, max_data_length, + "FirstBurstLength", "Irrelevant")) + return 0; + + if (session->irrelevant_keys_bitmap & IRRELEVANT_MAXOUTSTANDINGR2T) + if (!iscsi_add_text(pdu, data, max_data_length, + "MaxOutstandingR2T", "Irrelevant")) + return 0; + + if (session->irrelevant_keys_bitmap & IRRELEVANT_DATAPDUINORDER) + if (!iscsi_add_text(pdu, data, max_data_length, + "DataPDUInOrder", "Irrelevant")) + return 0; + + if (session->irrelevant_keys_bitmap & IRRELEVANT_DATASEQUENCEINORDER ) + if (!iscsi_add_text(pdu, data, max_data_length, + "DataSequenceInOrder", "Irrelevant")) + return 0; + + return 1; +} + +static int +fill_crc_digest_text(iscsi_conn_t *conn, struct iscsi_hdr *pdu, + char *data, int max_data_length) +{ + switch (conn->hdrdgst_en) { + case ISCSI_DIGEST_NONE: + if (!iscsi_add_text(pdu, data, max_data_length, + "HeaderDigest", "None")) + return 0; + break; + case ISCSI_DIGEST_CRC32C: + if (!iscsi_add_text(pdu, data, max_data_length, + "HeaderDigest", "CRC32C")) + return 0; + break; + case ISCSI_DIGEST_CRC32C_NONE: + if (!iscsi_add_text(pdu, data, max_data_length, + "HeaderDigest", "CRC32C,None")) + return 0; + break; + default: + case ISCSI_DIGEST_NONE_CRC32C: + if (!iscsi_add_text(pdu, data, max_data_length, + "HeaderDigest", "None,CRC32C")) + return 0; + break; + } + + switch (conn->datadgst_en) { + case ISCSI_DIGEST_NONE: + if (!iscsi_add_text(pdu, data, max_data_length, + "DataDigest", "None")) + return 0; + break; + case ISCSI_DIGEST_CRC32C: + if (!iscsi_add_text(pdu, data, max_data_length, + "DataDigest", "CRC32C")) + return 0; + break; + case ISCSI_DIGEST_CRC32C_NONE: + if (!iscsi_add_text(pdu, data, max_data_length, + "DataDigest", "CRC32C,None")) + return 0; + break; + default: + case ISCSI_DIGEST_NONE_CRC32C: + if (!iscsi_add_text(pdu, data, max_data_length, + "DataDigest", "None,CRC32C")) + return 0; + break; + } + return 1; +} + +static int +fill_op_params_text(iscsi_session_t *session, int cid, struct iscsi_hdr *pdu, + char *data, int max_data_length, int *transit) +{ + char value[AUTH_STR_MAX_LEN]; + iscsi_conn_t *conn = &session->conn[cid]; + int rdma; + + /* we always try to go from op params to full feature stage */ + conn->current_stage = ISCSI_OP_PARMS_NEGOTIATION_STAGE; + conn->next_stage = ISCSI_FULL_FEATURE_PHASE; + *transit = 1; + + rdma = (session->type == ISCSI_SESSION_TYPE_NORMAL) && + session->t->template->rdma; + + /* + * If we haven't gotten a partial response, then either we shouldn't be + * here, or we just switched to this stage, and need to start offering + * keys. + */ + if (!conn->partial_response) { + /* + * request the desired settings the first time + * we are in this stage + */ + if (!rdma && + !fill_crc_digest_text(conn, pdu, data, max_data_length)) + return 0; + + sprintf(value, "%d", session->def_time2wait); + if (!iscsi_add_text(pdu, data, max_data_length, + "DefaultTime2Wait", value)) + return 0; + + sprintf(value, "%d", session->def_time2retain); + if (!iscsi_add_text(pdu, data, max_data_length, + "DefaultTime2Retain", value)) + return 0; + + if (!iscsi_add_text(pdu, data, max_data_length, + "IFMarker", "No")) + return 0; + + if (!iscsi_add_text(pdu, data, max_data_length, + "OFMarker", "No")) + return 0; + + if (!iscsi_add_text(pdu, data, max_data_length, + "ErrorRecoveryLevel", "0")) + return 0; + + if (session->type == ISCSI_SESSION_TYPE_NORMAL) { + if (!add_params_normal_session(session, pdu, data, + max_data_length)) + return 0; + + if (!add_params_transport_specific(session, cid, + pdu, data, + max_data_length)) + return 0; + } else { + sprintf(value, "%d", conn->max_recv_dlength); + if (!iscsi_add_text(pdu, data, max_data_length, + "MaxRecvDataSegmentLength", value)) + return 0; + } + } else { + if (!check_irrelevant_keys(session, pdu, data, max_data_length)) + return 0; + + if (rdma && + !fill_crc_digest_text(conn, pdu, data, max_data_length)) + return 0; + } + + return 1; +} + +static void +enum_auth_keys(struct iscsi_acl *auth_client, struct iscsi_hdr *pdu, + char *data, int max_data_length, int keytype) +{ + int present = 0, rc; + char *key = (char *)acl_get_key_name(keytype); + int key_length = key ? strlen(key) : 0; + int pdu_length = ntoh24(pdu->dlength); + char *auth_value = data + pdu_length + key_length + 1; + unsigned int max_length = max_data_length - (pdu_length + + key_length + 1); + + /* + * add the key/value pairs the auth code wants to send + * directly to the PDU, since they could in theory be large. + */ + rc = acl_send_key_val(auth_client, keytype, &present, auth_value, + max_length); + if ((rc == AUTH_STATUS_NO_ERROR) && present) { + /* actually fill in the key */ + strncpy(&data[pdu_length], key, key_length); + pdu_length += key_length; + data[pdu_length] = '='; + pdu_length++; + /* + * adjust the PDU's data segment length + * to include the value and trailing NUL + */ + pdu_length += strlen(auth_value) + 1; + hton24(pdu->dlength, pdu_length); + } +} + +static int +fill_security_params_text(iscsi_session_t *session, int cid, struct iscsi_hdr *pdu, + struct iscsi_acl *auth_client, char *data, + int max_data_length, int *transit) +{ + int keytype = AUTH_KEY_TYPE_NONE; + int rc = acl_send_transit_bit(auth_client, transit); + iscsi_conn_t *conn = &session->conn[cid]; + + /* see if we're ready for a stage change */ + if (rc != AUTH_STATUS_NO_ERROR) + return 0; + + if (*transit) { + /* + * discovery sessions can go right to full-feature phase, + * unless they want to non-standard values for the few relevant + * keys, or want to offer vendor-specific keys + */ + if (session->type == ISCSI_SESSION_TYPE_DISCOVERY) + if ((conn->hdrdgst_en != ISCSI_DIGEST_NONE) || + (conn->datadgst_en != ISCSI_DIGEST_NONE) || + (conn->max_recv_dlength != + ISCSI_DEF_MAX_RECV_SEG_LEN)) + conn->next_stage = + ISCSI_OP_PARMS_NEGOTIATION_STAGE; + else + conn->next_stage = ISCSI_FULL_FEATURE_PHASE; + else + conn->next_stage = ISCSI_OP_PARMS_NEGOTIATION_STAGE; + } else + conn->next_stage = ISCSI_SECURITY_NEGOTIATION_STAGE; + + /* enumerate all the keys the auth code might want to send */ + while (acl_get_next_key_type(&keytype) == AUTH_STATUS_NO_ERROR) + enum_auth_keys(auth_client, pdu, data, max_data_length, + keytype); + + return 1; +} + +/** + * iscsi_make_login_pdu - Prepare the login pdu to be sent to iSCSI target. + * @session: session for which login is initiated. + * @pdu: login header + * @data: contains text keys to be negotiated during login + * @max_data_length: data size + * + * Description: + * Based on whether authentication is enabled or not, corresponding text + * keys are filled up in login pdu. + * + **/ +static int +iscsi_make_login_pdu(iscsi_session_t *session, int cid, struct iscsi_hdr *hdr, + char *data, int max_data_length) +{ + int transit = 0; + int ret; + struct iscsi_login *login_hdr = (struct iscsi_login *)hdr; + struct iscsi_acl *auth_client; + iscsi_conn_t *conn = &session->conn[cid]; + + auth_client = (session->auth_buffers && session->num_auth_buffers) ? + (struct iscsi_acl *)session->auth_buffers[0].address : NULL; + + /* initialize the PDU header */ + memset(login_hdr, 0, sizeof(*login_hdr)); + login_hdr->opcode = ISCSI_OP_LOGIN | ISCSI_OP_IMMEDIATE; + login_hdr->cid = 0; + memcpy(login_hdr->isid, session->isid, sizeof(session->isid)); + login_hdr->tsih = 0; + login_hdr->cmdsn = htonl(session->cmdsn); + /* don't increment on immediate */ + login_hdr->min_version = ISCSI_DRAFT20_VERSION; + login_hdr->max_version = ISCSI_DRAFT20_VERSION; + login_hdr->exp_statsn = htonl(conn->exp_statsn); + + /* + * the very first Login PDU has some additional requirements, + * and we need to decide what stage to start in. + */ + if (conn->current_stage == ISCSI_INITIAL_LOGIN_STAGE) { + if (session->initiator_name && session->initiator_name[0]) { + if (!iscsi_add_text(hdr, data, max_data_length, + "InitiatorName", session->initiator_name)) + return 0; + } else { + log_error("InitiatorName is required " + "on the first Login PDU"); + return 0; + } + if (session->initiator_alias && session->initiator_alias[0]) { + if (!iscsi_add_text(hdr, data, max_data_length, + "InitiatorAlias", session->initiator_alias)) + return 0; + } + + if ((session->target_name && session->target_name[0]) && + (session->type == ISCSI_SESSION_TYPE_NORMAL)) { + if (!iscsi_add_text(hdr, data, max_data_length, + "TargetName", session->target_name)) + return 0; + } + + if (!iscsi_add_text(hdr, data, max_data_length, + "SessionType", (session->type == + ISCSI_SESSION_TYPE_DISCOVERY) ? "Discovery" : "Normal")) + return 0; + + if (auth_client) + /* we're prepared to do authentication */ + conn->current_stage = conn->next_stage = + ISCSI_SECURITY_NEGOTIATION_STAGE; + else + /* can't do any authentication, skip that stage */ + conn->current_stage = conn->next_stage = + ISCSI_OP_PARMS_NEGOTIATION_STAGE; + } + + /* fill in text based on the stage */ + switch (conn->current_stage) { + case ISCSI_OP_PARMS_NEGOTIATION_STAGE:{ + ret = fill_op_params_text(session, cid, hdr, data, + max_data_length, &transit); + if (!ret) + return ret; + break; + } + case ISCSI_SECURITY_NEGOTIATION_STAGE:{ + ret = fill_security_params_text(session, cid, hdr, + auth_client, data, max_data_length, + &transit); + if (!ret) + return ret; + break; + } + case ISCSI_FULL_FEATURE_PHASE: + log_error("Can't send login PDUs in full " + "feature phase"); + return 0; + default: + log_error("Can't send login PDUs in unknown " + "stage %d", conn->current_stage); + return 0; + } + + /* fill in the flags */ + login_hdr->flags = 0; + login_hdr->flags |= conn->current_stage << 2; + if (transit) { + /* transit to the next stage */ + login_hdr->flags |= conn->next_stage; + login_hdr->flags |= ISCSI_FLAG_LOGIN_TRANSIT; + } else + /* next == current */ + login_hdr->flags |= conn->current_stage; + + return 1; +} + +static enum iscsi_login_status +check_for_authentication(iscsi_session_t *session, + struct iscsi_acl *auth_client) +{ + enum iscsi_login_status ret = LOGIN_FAILED; + + auth_client = (struct iscsi_acl *)session->auth_buffers[0].address; + + /* prepare for authentication */ + if (acl_init(TYPE_INITIATOR, session->num_auth_buffers, + session->auth_buffers) != AUTH_STATUS_NO_ERROR) { + log_error("Couldn't initialize authentication"); + return LOGIN_FAILED; + } + + if (session->username && + (acl_set_user_name(auth_client, session->username) != + AUTH_STATUS_NO_ERROR)) { + log_error("Couldn't set username"); + goto end; + } + + if (session->password && (acl_set_passwd(auth_client, + session->password, session->password_length) != + AUTH_STATUS_NO_ERROR)) { + log_error("Couldn't set password"); + goto end; + } + + if (acl_set_ip_sec(auth_client, 1) != AUTH_STATUS_NO_ERROR) { + log_error("Couldn't set IPSec"); + goto end; + } + + if (acl_set_auth_rmt(auth_client, session->bidirectional_auth) != + AUTH_STATUS_NO_ERROR) { + log_error("Couldn't set remote authentication"); + goto end; + } + return LOGIN_OK; + + end: + if (auth_client && acl_finish(auth_client) != AUTH_STATUS_NO_ERROR) { + log_error("Login failed, error finishing auth_client"); + if (ret == LOGIN_OK) + ret = LOGIN_FAILED; + } + return ret; +} + +static enum iscsi_login_status +check_status_login_response(iscsi_session_t *session, int cid, + struct iscsi_login_rsp *login_rsp, + char *data, int max_data_length, int *final) +{ + enum iscsi_login_status ret; + + switch (login_rsp->status_class) { + case ISCSI_STATUS_CLS_SUCCESS: + /* process this response and possibly continue sending PDUs */ + ret = iscsi_process_login_response(session, cid, login_rsp, + data, max_data_length); + if (ret != LOGIN_OK) /* pass back whatever + * error we discovered + */ + *final = 1; + break; + case ISCSI_STATUS_CLS_REDIRECT: + /* + * we need to process this response to get the + * TargetAddress of the redirect, but we don't care + * about the return code. + */ + iscsi_process_login_response(session, cid, login_rsp, + data, max_data_length); + ret = LOGIN_REDIRECT; + *final = 1; + break; + case ISCSI_STATUS_CLS_INITIATOR_ERR: + if (login_rsp->status_detail == + ISCSI_LOGIN_STATUS_AUTH_FAILED) { + log_error("Login failed to authenticate " + "with target %s", session->target_name); + } + ret = LOGIN_OK; + *final = 1; + break; + default: + /* + * some sort of error, login terminated unsuccessfully, + * though this function did it's job. + * the caller must check the status_class and + * status_detail and decide what to do next. + */ + ret = LOGIN_OK; + *final = 1; + } + return ret; +} + +int +iscsi_login_begin(iscsi_session_t *session, iscsi_login_context_t *c) +{ + iscsi_conn_t *conn = &session->conn[c->cid]; + + c->auth_client = NULL; + c->login_rsp = (struct iscsi_login_rsp *)&c->pdu; + c->received_pdu = 0; + c->timeout = 0; + c->final = 0; + c->ret = LOGIN_FAILED; + + /* prepare the session of the connection is leading */ + if (c->cid ==0) { + session->cmdsn = 1; + session->exp_cmdsn = 1; + session->max_cmdsn = 1; + } + + conn->current_stage = ISCSI_INITIAL_LOGIN_STAGE; + conn->partial_response = 0; + + if (session->auth_buffers && session->num_auth_buffers) { + c->ret = check_for_authentication(session, c->auth_client); + if (c->ret != LOGIN_OK) + return 1; + } + + return 0; +} + +int +iscsi_login_req(iscsi_session_t *session, iscsi_login_context_t *c) +{ + iscsi_conn_t *conn = &session->conn[c->cid]; + + c->final = 0; + c->timeout = 0; + c->login_rsp = (struct iscsi_login_rsp *)&c->pdu; + c->ret = LOGIN_FAILED; + + memset(c->buffer, 0, c->bufsize); + c->data = c->buffer; + c->max_data_length = c->bufsize; + + /* + * pick the appropriate timeout. If we know the target has + * responded before, and we're in the security stage, we use a + * longer timeout, since the authentication alogorithms can + * take a while, especially if the target has to go talk to a + * tacacs or RADIUS server (which may or may not be + * responding). + */ + if (c->received_pdu && (conn->current_stage == + ISCSI_SECURITY_NEGOTIATION_STAGE)) + c->timeout = conn->auth_timeout; + else + c->timeout = conn->login_timeout; + + /* + * fill in the PDU header and text data based on the login + * stage that we're in + */ + if (!iscsi_make_login_pdu(session, c->cid, &c->pdu, c->data, + c->max_data_length)) { + log_error("login failed, couldn't make a login PDU"); + c->ret = LOGIN_FAILED; + goto done; + } + + /* send a PDU to the target */ + if (!iscsi_io_send_pdu(conn, &c->pdu, ISCSI_DIGEST_NONE, + c->data, ISCSI_DIGEST_NONE, c->timeout)) { + /* + * FIXME: caller might want us to distinguish I/O + * error and timeout. Might want to switch portals on + * timeouts, but not I/O errors. + */ + log_error("Login I/O error, failed to send a PDU"); + c->ret = LOGIN_IO_ERROR; + goto done; + } + return 0; + + done: + if (c->auth_client && acl_finish(c->auth_client) != + AUTH_STATUS_NO_ERROR) { + log_error("Login failed, error finishing c->auth_client"); + if (c->ret == LOGIN_OK) + c->ret = LOGIN_FAILED; + } + return 1; +} + +int +iscsi_login_rsp(iscsi_session_t *session, iscsi_login_context_t *c) +{ + iscsi_conn_t *conn = &session->conn[c->cid]; + int err; + + /* read the target's response into the same buffer */ + err = iscsi_io_recv_pdu(conn, &c->pdu, ISCSI_DIGEST_NONE, c->data, + c->max_data_length, ISCSI_DIGEST_NONE, + c->timeout); + if (err == -EAGAIN) { + goto done; + } else if (err < 0) { + /* + * FIXME: caller might want us to distinguish I/O + * error and timeout. Might want to switch portals on + * timeouts, but not I/O errors. + */ + log_error("Login I/O error, failed to receive a PDU"); + c->ret = LOGIN_IO_ERROR; + goto done; + } + + err = -EIO; + c->received_pdu = 1; + + /* check the PDU response type */ + if (c->pdu.opcode == (ISCSI_OP_LOGIN_RSP | 0xC0)) { + /* + * it's probably a draft 8 login response, + * which we can't deal with + */ + log_error("Received iSCSI draft 8 login " + "response opcode 0x%x, expected draft " + "20 login response 0x%2x", + c->pdu.opcode, ISCSI_OP_LOGIN_RSP); + c->ret = LOGIN_VERSION_MISMATCH; + goto done; + } else if (c->pdu.opcode != ISCSI_OP_LOGIN_RSP) { + c->ret = LOGIN_INVALID_PDU; + goto done; + } + + /* + * give the caller the status class and detail from the last + * login response PDU received + */ + c->status_class = c->login_rsp->status_class; + c->status_detail = c->login_rsp->status_detail; + log_debug(1, "login response status %02d%02d", + c->status_class, c->status_detail); + c->ret = check_status_login_response(session, c->cid, + c->login_rsp, c->data, c->max_data_length, + &c->final); + if (c->final) + goto done; + return 0; + + done: + if (c->auth_client && acl_finish(c->auth_client) != + AUTH_STATUS_NO_ERROR) { + log_error("Login failed, error finishing c->auth_client"); + if (c->ret == LOGIN_OK) + c->ret = LOGIN_FAILED; + } + return err; +} + +/** + * iscsi_login - attempt to login to the target. + * @session: login is initiated over this session + * @buffer: holds login pdu + * @bufsize: size of login pdu + * @status_class: holds either success or failure as status of login + * @status_detail: contains details based on the login status + * + * Description: + * The caller must check the status class to determine if the login + * succeeded. A return of 1 does not mean the login succeeded, it just + * means this function worked, and the status class is valid info. + * This allows the caller to decide whether or not to retry logins, so + * that we don't have any policy logic here. + **/ +enum iscsi_login_status +iscsi_login(iscsi_session_t *session, int cid, char *buffer, size_t bufsize, + uint8_t *status_class, uint8_t *status_detail) +{ + iscsi_conn_t *conn = &session->conn[cid]; + iscsi_login_context_t *c = &conn->login_context; + struct timeval connection_timer; + struct pollfd pfd; + int ret, timeout; + + /* + * assume iscsi_login is only called from discovery, so it is + * safe to always set to zero + */ + conn->exp_statsn = 0; + + c->cid = cid; + c->buffer = buffer; + c->bufsize = bufsize; + + if (iscsi_login_begin(session, c)) + return c->ret; + + do { + if (iscsi_login_req(session, c)) + return c->ret; + + /* + * TODO: merge the poll and req/rsp code with the discovery + * poll and text req/rsp. + */ + iscsi_timer_set(&connection_timer, + session->conn[0].active_timeout); + timeout = iscsi_timer_msecs_until(&connection_timer); + + memset(&pfd, 0, sizeof (pfd)); + pfd.fd = conn->socket_fd; + pfd.events = POLLIN | POLLPRI; + +repoll: + pfd.revents = 0; + ret = poll(&pfd, 1, timeout); + log_debug(7, "%s: Poll return %d", __FUNCTION__, ret); + if (iscsi_timer_expired(&connection_timer)) { + log_warning("Login response timeout. Waited %d " + "seconds and did not get response PDU.", + session->conn[0].active_timeout); + c->ret = LOGIN_FAILED; + return c->ret; + } + + if (ret > 0) { + if (pfd.revents & (POLLIN | POLLPRI)) { + ret = iscsi_login_rsp(session, c); + if (ret == -EAGAIN) + goto repoll; + + if (status_class) + *status_class = c->status_class; + if (status_detail) + *status_detail = c->status_detail; + + if (ret) + return c->ret; + } else if (pfd.revents & POLLHUP) { + log_warning("Login POLLHUP"); + c->ret = LOGIN_FAILED; + return c->ret; + } else if (pfd.revents & POLLNVAL) { + log_warning("Login POLLNVAL"); + c->ret = LOGIN_IO_ERROR; + return c->ret; + } else if (pfd.revents & POLLERR) { + log_warning("Login POLLERR"); + c->ret = LOGIN_IO_ERROR; + return c->ret; + } + + } else if (ret < 0) { + log_error("Login poll error."); + c->ret = LOGIN_FAILED; + return c->ret; + } + } while (conn->current_stage != ISCSI_FULL_FEATURE_PHASE); + + c->ret = LOGIN_OK; + if (c->auth_client && acl_finish(c->auth_client) != + AUTH_STATUS_NO_ERROR) { + log_error("Login failed, error finishing c->auth_client"); + if (c->ret == LOGIN_OK) + c->ret = LOGIN_FAILED; + } + + return c->ret; +} diff --git a/usr/mgmt_ipc.c b/usr/mgmt_ipc.c new file mode 100644 index 0000000..1ffcda9 --- /dev/null +++ b/usr/mgmt_ipc.c @@ -0,0 +1,530 @@ +/* + * iSCSI Administrator Utility Socket Interface + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * Copyright (C) 2006 Mike Christie + * Copyright (C) 2006 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@googlegroups.com + * + * Originally based on: + * (C) 2004 FUJITA Tomonori + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#include +#include +#include +#include +#include + +#include "iscsid.h" +#include "idbm.h" +#include "mgmt_ipc.h" +#include "event_poll.h" +#include "log.h" +#include "transport.h" +#include "sysdeps.h" +#include "iscsi_ipc.h" +#include "iscsi_err.h" +#include "iscsi_util.h" +#include "iscsid_req.h" + +#define PEERUSER_MAX 64 +#define EXTMSG_MAX (64 * 1024) +#define SD_SOCKET_FDS_START 3 + +int +mgmt_ipc_listen(void) +{ + int fd, err, addr_len; + struct sockaddr_un addr; + + /* first check if we have fd handled by systemd */ + fd = mgmt_ipc_systemd(); + if (fd >= 0) + return fd; + + /* manually establish a socket */ + fd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (fd < 0) { + log_error("Can not create IPC socket"); + return fd; + } + + addr_len = setup_abstract_addr(&addr, iscsid_namespace); + + if ((err = bind(fd, (struct sockaddr *) &addr, addr_len)) < 0 ) { + log_error("Can not bind IPC socket"); + close(fd); + return err; + } + + if ((err = listen(fd, 32)) < 0) { + log_error("Can not listen IPC socket"); + close(fd); + return err; + } + + return fd; +} + +int mgmt_ipc_systemd(void) +{ + const char *env; + + env = getenv("LISTEN_PID"); + + if (!env || (strtoul(env, NULL, 10) != getpid())) + return -EINVAL; + + env = getenv("LISTEN_FDS"); + + if (!env) + return -EINVAL; + + if (strtoul(env, NULL, 10) != 1) { + log_error("Did not receive exactly one IPC socket from systemd"); + return -EINVAL; + } + + return SD_SOCKET_FDS_START; +} + +void +mgmt_ipc_close(int fd) +{ + event_loop_exit(NULL); + if (fd >= 0) + close(fd); +} + +static int +mgmt_ipc_session_login(queue_task_t *qtask) +{ + return session_login_task(&qtask->req.u.session.rec, qtask); +} + +static int +mgmt_ipc_session_getstats(queue_task_t *qtask) +{ + int sid = qtask->req.u.session.sid; + iscsi_session_t *session; + int rc; + + if (!(session = session_find_by_sid(sid))) + return ISCSI_ERR_SESS_NOT_FOUND; + + rc = ipc->get_stats(session->t->handle, + session->id, session->conn[0].id, + (void *)&qtask->rsp.u.getstats, + MGMT_IPC_GETSTATS_BUF_MAX); + if (rc) { + log_error("get_stats(): IPC error %d " + "session [%02d]", rc, sid); + return ISCSI_ERR_INTERNAL; + } + + mgmt_ipc_write_rsp(qtask, ISCSI_SUCCESS); + return ISCSI_SUCCESS; +} + +static int +mgmt_ipc_send_targets(queue_task_t *qtask) +{ + iscsiadm_req_t *req = &qtask->req; + int err; + + err = iscsi_host_send_targets(qtask, req->u.st.host_no, + req->u.st.do_login, + &req->u.st.ss); + mgmt_ipc_write_rsp(qtask, err); + return ISCSI_SUCCESS; +} + +static int +mgmt_ipc_session_logout(queue_task_t *qtask) +{ + return session_logout_task(qtask->req.u.session.sid, qtask); +} + +static int +mgmt_ipc_session_sync(queue_task_t *qtask) +{ + struct ipc_msg_session *session= &qtask->req.u.session; + + return iscsi_sync_session(&session->rec, qtask, session->sid); +} + +static int +mgmt_ipc_cfg_initiatorname(queue_task_t *qtask) +{ + if (dconfig->initiator_name) + strcpy(qtask->rsp.u.config.var, dconfig->initiator_name); + mgmt_ipc_write_rsp(qtask, ISCSI_SUCCESS); + return ISCSI_SUCCESS; +} + +static int +mgmt_ipc_session_info(queue_task_t *qtask) +{ + int sid = qtask->req.u.session.sid; + iscsi_session_t *session; + struct ipc_msg_session_state *info; + + if (!(session = session_find_by_sid(sid))) { + log_debug(1, "session with sid %d not found!", sid); + return ISCSI_ERR_SESS_NOT_FOUND; + } + + info = &qtask->rsp.u.session_state; + info->conn_state = session->conn[0].state; + info->session_state = session->r_stage; + + mgmt_ipc_write_rsp(qtask, ISCSI_SUCCESS); + return ISCSI_SUCCESS; +} + +static int +mgmt_ipc_cfg_initiatoralias(queue_task_t *qtask) +{ + strcpy(qtask->rsp.u.config.var, dconfig->initiator_alias); + mgmt_ipc_write_rsp(qtask, ISCSI_SUCCESS); + return ISCSI_SUCCESS; +} + +static int +mgmt_ipc_cfg_filename(queue_task_t *qtask) +{ + strcpy(qtask->rsp.u.config.var, dconfig->config_file); + mgmt_ipc_write_rsp(qtask, ISCSI_SUCCESS); + return ISCSI_SUCCESS; +} + +static int +mgmt_ipc_conn_add(queue_task_t *qtask) +{ + return ISCSI_ERR; +} + +static int +mgmt_ipc_immediate_stop(queue_task_t *qtask) +{ + event_loop_exit(qtask); + return ISCSI_SUCCESS; +} + +static int +mgmt_ipc_conn_remove(queue_task_t *qtask) +{ + return ISCSI_ERR; +} + +/* + * Parse a list of strings, encoded as a 32bit + * length followed by the string itself (not necessarily + * NUL-terminated). + */ +static int +mgmt_ipc_parse_strings(queue_task_t *qtask, char ***result) +{ + char *data, *endp, **argv = NULL; + unsigned int left, argc; + +again: + data = qtask->payload; + left = qtask->req.payload_len; + endp = NULL; + argc = 0; + + while (left) { + uint32_t len; + + if (left < 4) + return -1; + memcpy(&len, data, 4); + data += 4; + + if (endp) + *endp = '\0'; + + if (len > left) + return -1; + + if (argv) { + argv[argc] = (char *) data; + endp = data + len; + } + data += len; + argc++; + } + + if (endp) + *endp = '\0'; + + if (argv == NULL) { + argv = malloc((argc + 1) * sizeof(char *)); + *result = argv; + goto again; + } + + argv[argc] = NULL; + return argc; +} + +static int +mgmt_ipc_notify_common(queue_task_t *qtask, int (*handler)(int, char **)) +{ + char **argv = NULL; + int argc, err = ISCSI_ERR; + + argc = mgmt_ipc_parse_strings(qtask, &argv); + if (argc > 0) + err = handler(argc, argv); + + if (argv) + free(argv); + mgmt_ipc_write_rsp(qtask, err); + return ISCSI_SUCCESS; +} + +/* Replace these dummies as you implement them + elsewhere */ +static int +iscsi_discovery_add_node(int argc, char **argv) +{ + return ISCSI_SUCCESS; +} + +static int +iscsi_discovery_del_node(int argc, char **argv) +{ + return ISCSI_SUCCESS; +} + +static int +iscsi_discovery_add_portal(int argc, char **argv) +{ + return ISCSI_SUCCESS; +} + +static int +iscsi_discovery_del_portal(int argc, char **argv) +{ + return ISCSI_SUCCESS; +} + +static int +mgmt_ipc_notify_add_node(queue_task_t *qtask) +{ + return mgmt_ipc_notify_common(qtask, iscsi_discovery_add_node); +} + +static int +mgmt_ipc_notify_del_node(queue_task_t *qtask) +{ + return mgmt_ipc_notify_common(qtask, iscsi_discovery_del_node); +} + +static int +mgmt_ipc_notify_add_portal(queue_task_t *qtask) +{ + return mgmt_ipc_notify_common(qtask, iscsi_discovery_add_portal); +} + +static int +mgmt_ipc_notify_del_portal(queue_task_t *qtask) +{ + return mgmt_ipc_notify_common(qtask, iscsi_discovery_del_portal); +} + +static int +mgmt_peeruser(int sock, char *user) +{ + /* Linux style: use getsockopt(SO_PEERCRED) */ + struct ucred peercred; + socklen_t so_len = sizeof(peercred); + struct passwd *pass; + + errno = 0; + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &peercred, + &so_len) != 0 || so_len != sizeof(peercred)) { + /* We didn't get a valid credentials struct. */ + log_error("peeruser_unux: error receiving credentials: %m"); + return 0; + } + + pass = getpwuid(peercred.uid); + if (pass == NULL) { + log_error("peeruser_unix: unknown local user with uid %d", + (int) peercred.uid); + return 0; + } + + strlcpy(user, pass->pw_name, PEERUSER_MAX); + return 1; +} + +static void +mgmt_ipc_destroy_queue_task(queue_task_t *qtask) +{ + if (qtask->mgmt_ipc_fd >= 0) + close(qtask->mgmt_ipc_fd); + if (qtask->payload) + free(qtask->payload); + if (qtask->allocated) + free(qtask); +} + +/* + * Send the IPC response and destroy the queue_task. + * The recovery code uses a qtask which is allocated as + * part of a larger structure, and we don't want it to + * get freed when we come here. This is what qtask->allocated + * is for. + */ +void +mgmt_ipc_write_rsp(queue_task_t *qtask, int err) +{ + if (!qtask) + return; + log_debug(4, "%s: rsp to fd %d", __FUNCTION__, + qtask->mgmt_ipc_fd); + + if (qtask->mgmt_ipc_fd < 0) { + mgmt_ipc_destroy_queue_task(qtask); + return; + } + + qtask->rsp.err = err; + if (write(qtask->mgmt_ipc_fd, &qtask->rsp, sizeof(qtask->rsp)) < 0) + log_error("IPC qtask write failed: %s", strerror(errno)); + mgmt_ipc_destroy_queue_task(qtask); +} + +static int +mgmt_ipc_read_data(int fd, void *ptr, size_t len) +{ + int n; + + while (len) { + n = read(fd, ptr, len); + if (n < 0) { + if (errno == EINTR) + continue; + return -EIO; + } + if (n == 0) { + /* Client closed connection */ + return -EIO; + } + ptr += n; + len -= n; + } + return 0; +} + +static int +mgmt_ipc_read_req(queue_task_t *qtask) +{ + iscsiadm_req_t *req = &qtask->req; + int rc; + + rc = mgmt_ipc_read_data(qtask->mgmt_ipc_fd, req, sizeof(*req)); + if (rc >= 0 && req->payload_len > 0) { + /* Limit what we accept */ + if (req->payload_len > EXTMSG_MAX) + return -EIO; + + /* Remember the allocated pointer in the + * qtask - it will be freed by write_rsp. + * Note: we allocate one byte in excess + * so we can append a NUL byte. */ + qtask->payload = malloc(req->payload_len + 1); + rc = mgmt_ipc_read_data(qtask->mgmt_ipc_fd, + qtask->payload, + req->payload_len); + } + return rc; +} + +static mgmt_ipc_fn_t * mgmt_ipc_functions[__MGMT_IPC_MAX_COMMAND] = { +[MGMT_IPC_SESSION_LOGIN] = mgmt_ipc_session_login, +[MGMT_IPC_SESSION_LOGOUT] = mgmt_ipc_session_logout, +[MGMT_IPC_SESSION_SYNC] = mgmt_ipc_session_sync, +[MGMT_IPC_SESSION_STATS] = mgmt_ipc_session_getstats, +[MGMT_IPC_SEND_TARGETS] = mgmt_ipc_send_targets, +[MGMT_IPC_SESSION_INFO] = mgmt_ipc_session_info, +[MGMT_IPC_CONN_ADD] = mgmt_ipc_conn_add, +[MGMT_IPC_CONN_REMOVE] = mgmt_ipc_conn_remove, +[MGMT_IPC_CONFIG_INAME] = mgmt_ipc_cfg_initiatorname, +[MGMT_IPC_CONFIG_IALIAS] = mgmt_ipc_cfg_initiatoralias, +[MGMT_IPC_CONFIG_FILE] = mgmt_ipc_cfg_filename, +[MGMT_IPC_IMMEDIATE_STOP] = mgmt_ipc_immediate_stop, +[MGMT_IPC_NOTIFY_ADD_NODE] = mgmt_ipc_notify_add_node, +[MGMT_IPC_NOTIFY_DEL_NODE] = mgmt_ipc_notify_del_node, +[MGMT_IPC_NOTIFY_ADD_PORTAL] = mgmt_ipc_notify_add_portal, +[MGMT_IPC_NOTIFY_DEL_PORTAL] = mgmt_ipc_notify_del_portal, +}; + +void mgmt_ipc_handle(int accept_fd) +{ + unsigned int command; + int fd, err; + queue_task_t *qtask = NULL; + mgmt_ipc_fn_t *handler = NULL; + char user[PEERUSER_MAX]; + + qtask = calloc(1, sizeof(queue_task_t)); + if (!qtask) + return; + + if ((fd = accept(accept_fd, NULL, NULL)) < 0) { + free(qtask); + return; + } + + qtask->allocated = 1; + qtask->mgmt_ipc_fd = fd; + + if (!mgmt_peeruser(fd, user) || strncmp(user, "root", PEERUSER_MAX)) { + err = ISCSI_ERR_ACCESS; + goto err; + } + + if (mgmt_ipc_read_req(qtask) < 0) { + mgmt_ipc_destroy_queue_task(qtask); + return; + } + + command = qtask->req.command; + qtask->rsp.command = command; + + if (0 <= command && command < __MGMT_IPC_MAX_COMMAND) + handler = mgmt_ipc_functions[command]; + if (handler != NULL) { + /* If the handler returns OK, this means it + * already sent the reply. */ + err = handler(qtask); + if (err == ISCSI_SUCCESS) + return; + } else { + log_error("unknown request: %s(%d) %u", + __FUNCTION__, __LINE__, command); + err = ISCSI_ERR_INVALID_MGMT_REQ; + } + +err: + /* This will send the response, close the + * connection and free the qtask */ + mgmt_ipc_write_rsp(qtask, err); +} diff --git a/usr/mgmt_ipc.h b/usr/mgmt_ipc.h new file mode 100644 index 0000000..55972ed --- /dev/null +++ b/usr/mgmt_ipc.h @@ -0,0 +1,119 @@ +/* + * iSCSI Daemon/Admin Management IPC + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#ifndef MGMT_IPC_H +#define MGMT_IPC_H + +#include "types.h" +#include "iscsi_if.h" +#include "config.h" + +#define ISCSIADM_NAMESPACE "ISCSIADM_ABSTRACT_NAMESPACE" +#define PEERUSER_MAX 64 + +typedef enum iscsiadm_cmd { + MGMT_IPC_UNKNOWN = 0, + MGMT_IPC_SESSION_LOGIN = 1, + MGMT_IPC_SESSION_LOGOUT = 2, + MGMT_IPC_SESSION_ACTIVESTAT = 4, + MGMT_IPC_CONN_ADD = 5, + MGMT_IPC_CONN_REMOVE = 6, + MGMT_IPC_SESSION_STATS = 7, + MGMT_IPC_CONFIG_INAME = 8, + MGMT_IPC_CONFIG_IALIAS = 9, + MGMT_IPC_CONFIG_FILE = 10, + MGMT_IPC_IMMEDIATE_STOP = 11, + MGMT_IPC_SESSION_SYNC = 12, + MGMT_IPC_SESSION_INFO = 13, + MGMT_IPC_ISNS_DEV_ATTR_QUERY = 14, + MGMT_IPC_SEND_TARGETS = 15, + MGMT_IPC_NOTIFY_ADD_NODE = 16, + MGMT_IPC_NOTIFY_DEL_NODE = 17, + MGMT_IPC_NOTIFY_ADD_PORTAL = 18, + MGMT_IPC_NOTIFY_DEL_PORTAL = 19, + + __MGMT_IPC_MAX_COMMAND +} iscsiadm_cmd_e; + +/* IPC Request */ +typedef struct iscsiadm_req { + iscsiadm_cmd_e command; + uint32_t payload_len; + + union { + /* messages */ + struct ipc_msg_session { + int sid; + node_rec_t rec; + } session; + struct ipc_msg_conn { + int sid; + int cid; + } conn; + struct ipc_msg_send_targets { + int host_no; + int do_login; + struct sockaddr_storage ss; + } st; + struct ipc_msg_set_host_param { + int host_no; + int param; + /* TODO: make this variable len to support */ + char value[IFNAMSIZ + 1]; + + } set_host_param; + } u; +} iscsiadm_req_t; + +/* IPC Response */ +typedef struct iscsiadm_rsp { + iscsiadm_cmd_e command; + int err; /* ISCSI_ERR value */ + + union { +#define MGMT_IPC_GETSTATS_BUF_MAX (sizeof(struct iscsi_uevent) + \ + sizeof(struct iscsi_stats) + \ + sizeof(struct iscsi_stats_custom) * \ + ISCSI_STATS_CUSTOM_MAX) + struct ipc_msg_getstats { + struct iscsi_uevent ev; + struct iscsi_stats stats; + char custom[sizeof(struct iscsi_stats_custom) * + ISCSI_STATS_CUSTOM_MAX]; + } getstats; + struct ipc_msg_config { + char var[VALUE_MAXLEN]; + } config; + struct ipc_msg_session_state { + int session_state; + int conn_state; + } session_state; + } u; +} iscsiadm_rsp_t; + +struct queue_task; +typedef int mgmt_ipc_fn_t(struct queue_task *); + +struct queue_task; +void mgmt_ipc_write_rsp(struct queue_task *qtask, int err); +int mgmt_ipc_listen(void); +int mgmt_ipc_systemd(void); +void mgmt_ipc_close(int fd); +void mgmt_ipc_handle(int accept_fd); + +#endif /* MGMT_IPC_H */ diff --git a/usr/mntcheck.c b/usr/mntcheck.c new file mode 100644 index 0000000..6ae03e0 --- /dev/null +++ b/usr/mntcheck.c @@ -0,0 +1,233 @@ +/* + * Common code for checking sessions for mnt use + * + * Copyright (C) 2014 - 2015 Chris Leech + * Copyright (C) 2014 - 2015 Red Hat, Inc. 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 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#include +#include +#include +#include +#include +#include + +#include "initiator.h" +#include "transport.h" +#include "iscsid.h" +#include "iscsi_ipc.h" +#include "log.h" +#include "iscsi_sysfs.h" +#include "iscsi_settings.h" +#include "iface.h" +#include "host.h" +#include "sysdeps.h" +#include "iscsi_err.h" +#include "iscsi_net_util.h" + +static struct libmnt_table *mtab, *swaps; +static struct libmnt_cache *mntcache; + +static void libmount_cleanup(void) +{ + mnt_free_table(mtab); + mnt_free_table(swaps); + mnt_free_cache(mntcache); + mtab = NULL; + swaps = NULL; + mntcache = NULL; +} + +static int libmount_init(void) +{ + mnt_init_debug(0); + mtab = mnt_new_table(); + swaps = mnt_new_table(); + mntcache = mnt_new_cache(); + if (!mtab || !swaps || !mntcache) { + libmount_cleanup(); + return -ENOMEM; + } + mnt_table_set_cache(mtab, mntcache); + mnt_table_set_cache(swaps, mntcache); + mnt_table_parse_mtab(mtab, NULL); + mnt_table_parse_swaps(swaps, NULL); + return 0; +} + +static int trans_filter(const struct dirent *d) +{ + if (!strcmp(".", d->d_name) || !strcmp("..", d->d_name)) + return 0; + return 1; +} + +static int subdir_filter(const struct dirent *d) +{ + if (!(d->d_type & DT_DIR)) + return 0; + return trans_filter(d); +} + +static int is_partition(const char *path) +{ + char *devtype; + int rc = 0; + + devtype = sysfs_get_uevent_devtype(path); + if (!devtype) + return 0; + if (strcmp(devtype, "partition") == 0) + rc = 1; + free(devtype); + return rc; +} + +static int blockdev_check_mnts(char *syspath) +{ + struct libmnt_fs *fs; + char *devname = NULL; + char *_devname = NULL; + int rc = 0; + + devname = sysfs_get_uevent_devname(syspath); + if (!devname) + goto out; + + _devname = calloc(1, PATH_MAX); + if (!_devname) + goto out; + snprintf(_devname, PATH_MAX, "/dev/%s", devname); + + fs = mnt_table_find_source(mtab, _devname, MNT_ITER_FORWARD); + if (fs) { + rc = 1; + goto out; + } + fs = mnt_table_find_source(swaps, _devname, MNT_ITER_FORWARD); + if (fs) + rc = 1; +out: + free(devname); + free(_devname); + return rc; +} + +static int count_device_users(char *syspath); + +static int blockdev_get_partitions(char *syspath) +{ + struct dirent **parts = NULL; + int n, i; + int count = 0; + + n = scandir(syspath, &parts, subdir_filter, alphasort); + for (i = 0; i < n; i++) { + char *newpath; + + newpath = calloc(1, PATH_MAX); + if (!newpath) + continue; + snprintf(newpath, PATH_MAX, "%s/%s", syspath, parts[i]->d_name); + free(parts[i]); + if (is_partition(newpath)) { + count += count_device_users(newpath); + } + free(newpath); + } + free(parts); + return count; +} + +static int blockdev_get_holders(char *syspath) +{ + char *path = NULL; + struct dirent **holds = NULL; + int n, i; + int count = 0; + + path = calloc(1, PATH_MAX); + if (!path) + return 0; + snprintf(path, PATH_MAX, "%s/holders", syspath); + + n = scandir(path, &holds, trans_filter, alphasort); + for (i = 0; i < n; i++) { + char *newpath; + char *rp; + + newpath = calloc(1, PATH_MAX); + if (!newpath) + continue; + snprintf(newpath, PATH_MAX, "%s/%s", path, holds[i]->d_name); + + free(holds[i]); + rp = realpath(newpath, NULL); + if (rp) + count += count_device_users(rp); + free(newpath); + free(rp); + } + free(path); + free(holds); + return count; +} + +static int count_device_users(char *syspath) +{ + int count = 0; + count += blockdev_check_mnts(syspath); + count += blockdev_get_partitions(syspath); + count += blockdev_get_holders(syspath); + return count; +}; + +static void device_in_use(void *data, int host_no, int target, int lun) +{ + char *syspath = NULL; + char *devname = NULL; + int *count = data; + + devname = iscsi_sysfs_get_blockdev_from_lun(host_no, target, lun); + if (!devname) + goto out; + syspath = calloc(1, PATH_MAX); + if (!syspath) + goto out; + snprintf(syspath, PATH_MAX, "/sys/class/block/%s", devname); + *count += count_device_users(syspath); +out: + free(syspath); + free(devname); +} + +int session_in_use(int sid) +{ + int host_no = -1, err = 0; + int count = 0; + + if (libmount_init()) { + log_error("Failed to initialize libmount, " + "not checking for active mounts on session [%d].", sid); + return 0; + } + + host_no = iscsi_sysfs_get_host_no_from_sid(sid, &err); + if (!err) + iscsi_sysfs_for_each_device(&count, host_no, sid, device_in_use); + + libmount_cleanup(); + return count; +} diff --git a/usr/netlink.c b/usr/netlink.c new file mode 100644 index 0000000..f1aab52 --- /dev/null +++ b/usr/netlink.c @@ -0,0 +1,1801 @@ +/* + * iSCSI Netlink/Linux Interface + * + * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman + * Copyright (C) 2006 Mike Christie + * Copyright (C) 2006 Red Hat, Inc. All rights reserved. + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "types.h" +#include "iscsi_if.h" +#include "log.h" +#include "iscsi_ipc.h" +#include "initiator.h" +#include "iscsi_sysfs.h" +#include "transport.h" +#include "iscsi_netlink.h" +#include "iscsi_err.h" +#include "iscsi_timer.h" + +static int ctrl_fd; +static struct sockaddr_nl src_addr, dest_addr; +static void *xmitbuf = NULL; +static int xmitlen = 0; +static void *recvbuf = NULL; +static int recvlen = 0; +static void *nlm_sendbuf; +static void *nlm_recvbuf; +static void *pdu_sendbuf; +static void *setparam_buf; +static struct iscsi_ipc_ev_clbk *ipc_ev_clbk; + +static int ctldev_handle(void); + +#define NLM_BUF_DEFAULT_MAX (NLMSG_SPACE(ISCSI_DEF_MAX_RECV_SEG_LEN + \ + sizeof(struct iscsi_uevent) + \ + sizeof(struct iscsi_hdr))) + +#define PDU_SENDBUF_DEFAULT_MAX (ISCSI_DEF_MAX_RECV_SEG_LEN + \ + sizeof(struct iscsi_uevent) + \ + sizeof(struct iscsi_hdr)) + +#define NLM_SETPARAM_DEFAULT_MAX (NI_MAXHOST + 1 + sizeof(struct iscsi_uevent)) + +struct iscsi_ping_event { + uint32_t host_no; + uint32_t pid; + int32_t status; + int active; +}; + +struct iscsi_ping_event ping_event; + +struct nlattr *iscsi_nla_alloc(uint16_t type, uint16_t len) +{ + struct nlattr *attr; + + attr = calloc(1, ISCSI_NLA_TOTAL_LEN(len)); + if (!attr) + return NULL; + + attr->nla_len = ISCSI_NLA_LEN(len); + attr->nla_type = type; + return attr; +} + +static int +kread(char *data, int count) +{ + log_debug(7, "in %s %u %u %p %p", __FUNCTION__, recvlen, count, + data, recvbuf); + + memcpy(data, recvbuf + recvlen, count); + recvlen += count; + return count; +} + +static int +nl_read(int ctrl_fd, char *data, int size, int flags) +{ + int rc; + struct iovec iov; + struct msghdr msg; + + log_debug(7, "in %s", __FUNCTION__); + + iov.iov_base = data; + iov.iov_len = size; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name= (void*)&src_addr; + msg.msg_namelen = sizeof(src_addr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + rc = recvmsg(ctrl_fd, &msg, flags); + + return rc; +} + +static int +nlpayload_read(int ctrl_fd, char *data, int count, int flags) +{ + int rc; + struct iovec iov; + struct msghdr msg; + + log_debug(7, "in %s", __FUNCTION__); + + iov.iov_base = nlm_recvbuf; + iov.iov_len = NLMSG_SPACE(count); + + if (iov.iov_len > NLM_BUF_DEFAULT_MAX) { + log_error("Cannot read %lu bytes. nlm_recvbuf too small.", + iov.iov_len); + return -1; + } + memset(iov.iov_base, 0, iov.iov_len); + + memset(&msg, 0, sizeof(msg)); + msg.msg_name= (void*)&src_addr; + msg.msg_namelen = sizeof(src_addr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + /* + * Netlink recvmsg call path: + * + * - transport api callback + * - iscsi_control_conn_error (should succeed) + * - iscsi_unicast_skb (must succeed) + * - netlink_unicast (must succeed) + * - netlink_data_ready (must succeed) + * - netlink_sendskb (must succeed) + * - netlink_recvmsg (must succeed) + * - sock_recvmsg (must succeed) + * - sys_recvmsg (must succeed) + * - sys_socketcall (must succeed) + * - syscall_call (must succeed) + * + * Note1: "must succeed" means succeed unless bug in daemon. + * It also means - no sleep and memory allocation on + * the path. + * + * Note2: "should succeed" means will succeed in most of cases + * because of mempool preallocation. + * + * FIXME: if "Note2" than interface should generate iSCSI error + * level 0 on its own. Interface must always succeed on this. + */ + rc = recvmsg(ctrl_fd, &msg, flags); + + if (data) + memcpy(data, NLMSG_DATA(iov.iov_base), count); + + return rc; +} + +static int +kwritev(enum iscsi_uevent_e type, struct iovec *iovp, int count) +{ + int i, rc; + struct nlmsghdr *nlh; + struct msghdr msg; + int datalen = 0; + + log_debug(7, "in %s", __FUNCTION__); + + for (i = 0; i < count; i++) { + datalen += iovp[i].iov_len; + } + + if (xmitbuf && type != ISCSI_UEVENT_SEND_PDU) { + for (i = 0; i < count; i++) { + memcpy(xmitbuf + xmitlen, + iovp[i].iov_base, iovp[i].iov_len); + xmitlen += iovp[i].iov_len; + } + return datalen; + } + + nlh = nlm_sendbuf; + memset(nlh, 0, NLMSG_SPACE(0)); + + datalen = 0; + for (i = 1; i < count; i++) + datalen += iovp[i].iov_len; + + nlh->nlmsg_len = datalen + sizeof(*nlh); + nlh->nlmsg_pid = getpid(); + nlh->nlmsg_flags = 0; + nlh->nlmsg_type = type; + + iovp[0].iov_base = (void *)nlh; + iovp[0].iov_len = sizeof(*nlh); + + memset(&msg, 0, sizeof(msg)); + msg.msg_name= (void*)&dest_addr; + msg.msg_namelen = sizeof(dest_addr); + msg.msg_iov = iovp; + msg.msg_iovlen = count; + + do { + /* + * Netlink down call path: + * + * - transport api call + * - iscsi_if_recv_msg (must succeed) + * - iscsi_if_rx (must succeed) + * - netlink_data_ready (must succeed) + * - netlink_sendskb (must succeed) + * - netlink_sendmsg (alloc_skb() might fail) + * - sock_sendmsg (must succeed) + * - sys_sendmsg (must succeed) + * - sys_socketcall (must succeed) + * - syscall_call (must succeed) + * + * Note1: "must succeed" means succeed unless bug in daemon. + * It also means - no sleep and memory allocation on + * the path. + * + * Note2: netlink_sendmsg() might fail because of OOM. Since + * we are in user-space, we will sleep until we succeed. + */ + + rc = sendmsg(ctrl_fd, &msg, 0); + if (rc == -ENOMEM) { + log_debug(1, "sendmsg: alloc_skb() failed"); + sleep(1); + } else if (rc < 0) { + log_error("sendmsg: bug? ctrl_fd %d", ctrl_fd); + exit(rc); + } + } while (rc < 0); + + return rc; +} + +/* + * __kipc_call() should never block. Therefore + * Netlink's xmit logic is serialized. This means we do not allocate on + * xmit path. Instead we reuse nlm_sendbuf buffer. + * + * Transport must assure non-blocking operations for: + * + * - session_create() + * - conn_create() + * - conn_bind() + * _ set_param() + * - conn_start() + * - conn_stop() + * + * Its OK to block for cleanup for short period of time in operatations for: + * + * - conn_destroy() + * - session_destroy() + * + * FIXME: interface needs to be extended to allow longer blocking on + * cleanup. (Dima) + */ +static int +__kipc_call(struct iovec *iovp, int count) +{ + int rc, iferr; + struct iscsi_uevent *ev = iovp[1].iov_base; + enum iscsi_uevent_e type = ev->type; + + log_debug(7, "in %s", __FUNCTION__); + + rc = kwritev(type, iovp, count); + + do { + if ((rc = nlpayload_read(ctrl_fd, (void*)ev, + sizeof(*ev), MSG_PEEK)) < 0) { + return rc; + } + if (ev->type != type) { + log_debug(1, "expecting event %d, got %d, handling...", + type, ev->type); + if (ev->type == ISCSI_KEVENT_IF_ERROR) { + if ((rc = nlpayload_read(ctrl_fd, (void*)ev, + sizeof(*ev), 0)) < 0) { + return rc; + } + /* + * iferror is u32, but the kernel returns + * negative errno values for errors. + */ + iferr = ev->iferror; + + if (iferr == -ENOSYS) + /* not fatal so let caller handle log */ + log_debug(1, "Received iferror %d: %s.", + iferr, strerror(-iferr)); + else if (iferr < 0) + log_error("Received iferror %d: %s.", + iferr, strerror(-iferr)); + else + log_error("Received iferror %d.", + iferr); + return ev->iferror; + } + /* + * receive and queue async. event which as of + * today could be: + * - CONN_ERROR + * - RECV_PDU + */ + ctldev_handle(); + } else if (ev->type == ISCSI_UEVENT_GET_STATS) { + /* kget_stats() will read */ + return 0; + } else if (ev->type == ISCSI_UEVENT_GET_CHAP) { + /* kget_chap() will read */ + return 0; + } else if (ev->type == ISCSI_UEVENT_GET_HOST_STATS) { + /* kget_host_stats() will read */ + return 0; + + } else { + if ((rc = nlpayload_read(ctrl_fd, (void*)ev, + sizeof(*ev), 0)) < 0) { + return rc; + } + break; + } + } while (ev->type != type); + + return rc; +} + +static int +ksendtargets(uint64_t transport_handle, uint32_t host_no, struct sockaddr *addr) +{ + int rc, addrlen; + struct iscsi_uevent *ev; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + memset(setparam_buf, 0, NLM_SETPARAM_DEFAULT_MAX); + ev = (struct iscsi_uevent *)setparam_buf; + ev->type = ISCSI_UEVENT_TGT_DSCVR; + ev->transport_handle = transport_handle; + ev->u.tgt_dscvr.type = ISCSI_TGT_DSCVR_SEND_TARGETS; + ev->u.tgt_dscvr.host_no = host_no; + + if (addr->sa_family == PF_INET) + addrlen = sizeof(struct sockaddr_in); + else if (addr->sa_family == PF_INET6) + addrlen = sizeof(struct sockaddr_in6); + else { + log_error("%s unknown addr family %d", + __FUNCTION__, addr->sa_family); + return -EINVAL; + } + memcpy(setparam_buf + sizeof(*ev), addr, addrlen); + + iov[1].iov_base = ev; + iov[1].iov_len = sizeof(*ev) + addrlen; + rc = __kipc_call(iov, 2); + if (rc < 0) { + log_error("sendtargets failed rc%d", rc); + return rc; + } + return 0; +} + +static int +kcreate_session(uint64_t transport_handle, uint64_t ep_handle, + uint32_t initial_cmdsn, uint16_t cmds_max, uint16_t qdepth, + uint32_t *out_sid, uint32_t *hostno) +{ + int rc; + struct iscsi_uevent ev; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + memset(&ev, 0, sizeof(struct iscsi_uevent)); + + if (ep_handle == 0) { + ev.type = ISCSI_UEVENT_CREATE_SESSION; + ev.transport_handle = transport_handle; + ev.u.c_session.initial_cmdsn = initial_cmdsn; + ev.u.c_session.cmds_max = cmds_max; + ev.u.c_session.queue_depth = qdepth; + } else { + ev.type = ISCSI_UEVENT_CREATE_BOUND_SESSION; + ev.transport_handle = transport_handle; + ev.u.c_bound_session.initial_cmdsn = initial_cmdsn; + ev.u.c_bound_session.cmds_max = cmds_max; + ev.u.c_bound_session.queue_depth = qdepth; + ev.u.c_bound_session.ep_handle = ep_handle; + } + + iov[1].iov_base = &ev; + iov[1].iov_len = sizeof(ev); + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + *hostno = ev.r.c_session_ret.host_no; + *out_sid = ev.r.c_session_ret.sid; + + return 0; +} + +static int +kdestroy_session(uint64_t transport_handle, uint32_t sid) +{ + int rc; + struct iscsi_uevent ev; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + memset(&ev, 0, sizeof(struct iscsi_uevent)); + + ev.type = ISCSI_UEVENT_DESTROY_SESSION; + ev.transport_handle = transport_handle; + ev.u.d_session.sid = sid; + + iov[1].iov_base = &ev; + iov[1].iov_len = sizeof(ev); + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + return 0; +} + +static int +kunbind_session(uint64_t transport_handle, uint32_t sid) +{ + int rc; + struct iscsi_uevent ev; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + memset(&ev, 0, sizeof(struct iscsi_uevent)); + + ev.type = ISCSI_UEVENT_UNBIND_SESSION; + ev.transport_handle = transport_handle; + ev.u.d_session.sid = sid; + + iov[1].iov_base = &ev; + iov[1].iov_len = sizeof(ev); + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + return 0; +} + +static int +kcreate_conn(uint64_t transport_handle, uint32_t sid, + uint32_t cid, uint32_t *out_cid) +{ + int rc; + struct iscsi_uevent ev; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + memset(&ev, 0, sizeof(struct iscsi_uevent)); + + ev.type = ISCSI_UEVENT_CREATE_CONN; + ev.transport_handle = transport_handle; + ev.u.c_conn.cid = cid; + ev.u.c_conn.sid = sid; + + iov[1].iov_base = &ev; + iov[1].iov_len = sizeof(ev); + rc = __kipc_call(iov, 2); + if (rc < 0) { + log_debug(7, "returned %d", rc); + return rc; + } + + if ((int)ev.r.c_conn_ret.cid == -1) + return -EIO; + + *out_cid = ev.r.c_conn_ret.cid; + return 0; +} + +static int +kdestroy_conn(uint64_t transport_handle, uint32_t sid, uint32_t cid) +{ + int rc; + struct iscsi_uevent ev; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + memset(&ev, 0, sizeof(struct iscsi_uevent)); + + ev.type = ISCSI_UEVENT_DESTROY_CONN; + ev.transport_handle = transport_handle; + ev.u.d_conn.sid = sid; + ev.u.d_conn.cid = cid; + + iov[1].iov_base = &ev; + iov[1].iov_len = sizeof(ev); + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + return 0; +} + +static int +kbind_conn(uint64_t transport_handle, uint32_t sid, uint32_t cid, + uint64_t transport_eph, int is_leading, int *retcode) +{ + int rc; + struct iscsi_uevent ev; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + memset(&ev, 0, sizeof(struct iscsi_uevent)); + + ev.type = ISCSI_UEVENT_BIND_CONN; + ev.transport_handle = transport_handle; + ev.u.b_conn.sid = sid; + ev.u.b_conn.cid = cid; + ev.u.b_conn.transport_eph = transport_eph; + ev.u.b_conn.is_leading = is_leading; + + iov[1].iov_base = &ev; + iov[1].iov_len = sizeof(ev); + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + *retcode = ev.r.retcode; + + return 0; +} + +static void +ksend_pdu_begin(uint64_t transport_handle, uint32_t sid, uint32_t cid, + int hdr_size, int data_size) +{ + struct iscsi_uevent *ev; + int total_xmitlen = sizeof(*ev) + hdr_size + data_size; + + log_debug(7, "in %s", __FUNCTION__); + + if (xmitbuf) { + log_error("send's begin state machine bug?"); + exit(-EIO); + } + + if (total_xmitlen > PDU_SENDBUF_DEFAULT_MAX) { + log_error("BUG: Cannot send %d bytes.", total_xmitlen); + exit(-EINVAL); + } + + xmitbuf = pdu_sendbuf; + memset(xmitbuf, 0, total_xmitlen); + xmitlen = sizeof(*ev); + ev = xmitbuf; + memset(ev, 0, sizeof(*ev)); + ev->type = ISCSI_UEVENT_SEND_PDU; + ev->transport_handle = transport_handle; + ev->u.send_pdu.sid = sid; + ev->u.send_pdu.cid = cid; + ev->u.send_pdu.hdr_size = hdr_size; + ev->u.send_pdu.data_size = data_size; + + log_debug(3, "send PDU began for hdr %d bytes and data %d bytes", + hdr_size, data_size); +} + +static int +ksend_pdu_end(uint64_t transport_handle, uint32_t sid, uint32_t cid, + int *retcode) +{ + int rc; + struct iscsi_uevent *ev; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + if (!xmitbuf) { + log_error("send's end state machine bug?"); + exit(-EIO); + } + ev = xmitbuf; + if (ev->u.send_pdu.sid != sid || ev->u.send_pdu.cid != cid) { + log_error("send's end state machine corruption?"); + exit(-EIO); + } + + iov[1].iov_base = xmitbuf; + iov[1].iov_len = xmitlen; + + rc = __kipc_call(iov, 2); + if (rc < 0) + goto err; + if (ev->r.retcode) { + *retcode = ev->r.retcode; + goto err; + } + if (ev->type != ISCSI_UEVENT_SEND_PDU) { + log_error("bad event: bug on send_pdu_end?"); + exit(-EIO); + } + + log_debug(3, "send PDU finished for conn %d:%d", + sid, cid); + + xmitbuf = NULL; + return 0; + +err: + xmitbuf = NULL; + xmitlen = 0; + return rc; +} + +static int +kset_host_param(uint64_t transport_handle, uint32_t host_no, + enum iscsi_host_param param, void *value, int type) +{ + struct iscsi_uevent *ev; + char *param_str; + int rc, len; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + memset(setparam_buf, 0, NLM_SETPARAM_DEFAULT_MAX); + ev = (struct iscsi_uevent *)setparam_buf; + ev->type = ISCSI_UEVENT_SET_HOST_PARAM; + ev->transport_handle = transport_handle; + ev->u.set_host_param.host_no = host_no; + ev->u.set_host_param.param = param; + + param_str = setparam_buf + sizeof(*ev); + switch (type) { + case ISCSI_INT: + sprintf(param_str, "%d", *((int *)value)); + break; + case ISCSI_STRING: + if (!strlen(value)) + return 0; + sprintf(param_str, "%s", (char *)value); + break; + default: + log_error("invalid type %d", type); + return -EINVAL; + } + ev->u.set_host_param.len = len = strlen(param_str) + 1; + + iov[1].iov_base = ev; + iov[1].iov_len = sizeof(*ev) + len; + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + return 0; +} + +static int +kset_param(uint64_t transport_handle, uint32_t sid, uint32_t cid, + enum iscsi_param param, void *value, int type) +{ + struct iscsi_uevent *ev; + char *param_str; + int rc, len; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + memset(setparam_buf, 0, NLM_SETPARAM_DEFAULT_MAX); + ev = (struct iscsi_uevent *)setparam_buf; + ev->type = ISCSI_UEVENT_SET_PARAM; + ev->transport_handle = transport_handle; + ev->u.set_param.sid = sid; + ev->u.set_param.cid = cid; + ev->u.set_param.param = param; + + param_str = setparam_buf + sizeof(*ev); + switch (type) { + case ISCSI_INT: + sprintf(param_str, "%d", *((int *)value)); + break; + case ISCSI_UINT: + sprintf(param_str, "%u", *((unsigned int *)value)); + break; + case ISCSI_STRING: + if (!strlen(value)) + return 0; + sprintf(param_str, "%s", (char *)value); + break; + default: + log_error("invalid type %d", type); + return -EINVAL; + } + ev->u.set_param.len = len = strlen(param_str) + 1; + + iov[1].iov_base = ev; + iov[1].iov_len = sizeof(*ev) + len; + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + return 0; +} + +static int +kstop_conn(uint64_t transport_handle, uint32_t sid, uint32_t cid, int flag) +{ + int rc; + struct iscsi_uevent ev; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + memset(&ev, 0, sizeof(struct iscsi_uevent)); + + ev.type = ISCSI_UEVENT_STOP_CONN; + ev.transport_handle = transport_handle; + ev.u.stop_conn.sid = sid; + ev.u.stop_conn.cid = cid; + ev.u.stop_conn.flag = flag; + + iov[1].iov_base = &ev; + iov[1].iov_len = sizeof(ev); + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + return 0; +} + +static int +kstart_conn(uint64_t transport_handle, uint32_t sid, uint32_t cid, + int *retcode) +{ + int rc; + struct iscsi_uevent ev; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + memset(&ev, 0, sizeof(struct iscsi_uevent)); + + ev.type = ISCSI_UEVENT_START_CONN; + ev.transport_handle = transport_handle; + ev.u.start_conn.sid = sid; + ev.u.start_conn.cid = cid; + + iov[1].iov_base = &ev; + iov[1].iov_len = sizeof(ev); + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + *retcode = ev.r.retcode; + return 0; +} + +static int +krecv_pdu_begin(struct iscsi_conn *conn) +{ + int rc; + + log_debug(7, "in %s", __FUNCTION__); + + if (recvbuf) { + log_error("recv's begin state machine bug?"); + return -EIO; + } + + if (!conn->recv_context) { + rc = ipc->ctldev_handle(); + if (rc == -ENXIO) + /* event for some other conn */ + return -EAGAIN; + else if (rc < 0) + /* fatal handling error or conn error */ + return rc; + /* + * Session create/destroy event for another conn + */ + if (!conn->recv_context) + return -EAGAIN; + } + + recvbuf = conn->recv_context->data + sizeof(struct iscsi_uevent); + recvlen = 0; + + log_debug(3, "recv PDU began, pdu handle %p", recvbuf); + return 0; +} + +static int +krecv_pdu_end(struct iscsi_conn *conn) +{ + log_debug(7, "in %s", __FUNCTION__); + + if (!recvbuf) { + log_error("recv's end state machine bug?"); + return -EIO; + } + + log_debug(3, "recv PDU finished for pdu handle 0x%p", + recvbuf); + + ipc_ev_clbk->put_ev_context(conn->recv_context); + conn->recv_context = NULL; + recvbuf = NULL; + return 0; +} + +int +ktransport_ep_connect(iscsi_conn_t *conn, int non_blocking) +{ + int rc, addrlen; + struct iscsi_uevent *ev; + struct sockaddr *dst_addr = (struct sockaddr *)&conn->saddr; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + memset(setparam_buf, 0, NLM_SETPARAM_DEFAULT_MAX); + ev = (struct iscsi_uevent *)setparam_buf; + ev->transport_handle = conn->session->t->handle; + + if (conn->bind_ep) { + ev->type = ISCSI_UEVENT_TRANSPORT_EP_CONNECT_THROUGH_HOST; + ev->u.ep_connect_through_host.non_blocking = non_blocking; + ev->u.ep_connect_through_host.host_no = conn->session->hostno; + } else { + ev->type = ISCSI_UEVENT_TRANSPORT_EP_CONNECT; + ev->u.ep_connect.non_blocking = non_blocking; + } + + if (dst_addr->sa_family == PF_INET) + addrlen = sizeof(struct sockaddr_in); + else if (dst_addr->sa_family == PF_INET6) + addrlen = sizeof(struct sockaddr_in6); + else { + log_error("%s unknown addr family %d", + __FUNCTION__, dst_addr->sa_family); + return -EINVAL; + } + memcpy(setparam_buf + sizeof(*ev), dst_addr, addrlen); + + iov[1].iov_base = ev; + iov[1].iov_len = sizeof(*ev) + addrlen; + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + if (!ev->r.ep_connect_ret.handle) + return -EIO; + + conn->transport_ep_handle = ev->r.ep_connect_ret.handle; + + log_debug(6, "%s got handle %llx", + __FUNCTION__, (unsigned long long)conn->transport_ep_handle); + return 0; +} + +int +ktransport_ep_poll(iscsi_conn_t *conn, int timeout_ms) +{ + int rc; + struct iscsi_uevent ev; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + memset(&ev, 0, sizeof(struct iscsi_uevent)); + + ev.type = ISCSI_UEVENT_TRANSPORT_EP_POLL; + ev.transport_handle = conn->session->t->handle; + ev.u.ep_poll.ep_handle = conn->transport_ep_handle; + ev.u.ep_poll.timeout_ms = timeout_ms; + + iov[1].iov_base = &ev; + iov[1].iov_len = sizeof(ev); + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + return ev.r.retcode; +} + +void +ktransport_ep_disconnect(iscsi_conn_t *conn) +{ + int rc; + struct iscsi_uevent ev; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + if (conn->transport_ep_handle == -1) + return; + + memset(&ev, 0, sizeof(struct iscsi_uevent)); + + ev.type = ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT; + ev.transport_handle = conn->session->t->handle; + ev.u.ep_disconnect.ep_handle = conn->transport_ep_handle; + + iov[1].iov_base = &ev; + iov[1].iov_len = sizeof(ev); + rc = __kipc_call(iov, 2); + if (rc < 0) { + log_error("connection %d:%d transport disconnect failed for " + "ep %" PRIu64 " with error %d.", conn->session->id, + conn->id, conn->transport_ep_handle, rc); + } else + conn->transport_ep_handle = -1; +} + +static int +kget_stats(uint64_t transport_handle, uint32_t sid, uint32_t cid, + char *statsbuf, int statsbuf_max) +{ + int rc; + int ev_size; + struct iscsi_uevent ev; + char nlm_ev[NLMSG_SPACE(sizeof(struct iscsi_uevent))]; + struct nlmsghdr *nlh; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + memset(&ev, 0, sizeof(struct iscsi_uevent)); + + ev.type = ISCSI_UEVENT_GET_STATS; + ev.transport_handle = transport_handle; + ev.u.get_stats.sid = sid; + ev.u.get_stats.cid = cid; + + iov[1].iov_base = &ev; + iov[1].iov_len = sizeof(ev); + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + if ((rc = nl_read(ctrl_fd, nlm_ev, + NLMSG_SPACE(sizeof(struct iscsi_uevent)), MSG_PEEK)) < 0) { + log_error("can not read nlm_ev, error %d", rc); + return rc; + } + nlh = (struct nlmsghdr *)nlm_ev; + ev_size = nlh->nlmsg_len - NLMSG_ALIGN(sizeof(struct nlmsghdr)); + + log_debug(6, "message real length is %d bytes", nlh->nlmsg_len); + + if (ev_size > statsbuf_max) { + log_error("destanation buffer for statistics is " + "not big enough to fit %d bytes", statsbuf_max); + ev_size = statsbuf_max; + } + + if ((rc = nlpayload_read(ctrl_fd, (void*)statsbuf, ev_size, 0)) < 0) { + log_error("can not read from NL socket, error %d", rc); + return rc; + } + + return 0; +} + +static int +kset_net_config(uint64_t transport_handle, uint32_t host_no, + struct iovec *iovs, uint32_t param_count) +{ + struct iscsi_uevent ev; + int rc, ev_len; + struct iovec *iov = iovs + 1; + + log_debug(8, "in %s", __FUNCTION__); + + ev_len = sizeof(ev); + ev.type = ISCSI_UEVENT_SET_IFACE_PARAMS; + ev.transport_handle = transport_handle; + ev.u.set_iface_params.host_no = host_no; + /* first two iovs for nlmsg hdr and ev */ + ev.u.set_iface_params.count = param_count - 2; + + iov->iov_base = &ev; + iov->iov_len = ev_len; + rc = __kipc_call(iovs, param_count); + if (rc < 0) + return rc; + + return 0; +} + +static int krecv_conn_state(struct iscsi_conn *conn, uint32_t *state) +{ + int rc; + + rc = ipc->ctldev_handle(); + if (rc == -ENXIO) { + /* event for some other conn */ + rc = -EAGAIN; + goto exit; + } else if (rc < 0) + /* fatal handling error or conn error */ + goto exit; + + /* unexpected event without a receive context */ + if (!conn->recv_context) + return -EAGAIN; + + *state = *(enum iscsi_conn_state *)conn->recv_context->data; + + ipc_ev_clbk->put_ev_context(conn->recv_context); + conn->recv_context = NULL; + +exit: + return rc; +} + + + + +static int +ksend_ping(uint64_t transport_handle, uint32_t host_no, struct sockaddr *addr, + uint32_t iface_num, uint32_t iface_type, uint32_t pid, uint32_t size) +{ + int rc, addrlen; + struct iscsi_uevent *ev; + struct iovec iov[2]; + + log_debug(8, "in %s", __FUNCTION__); + + memset(setparam_buf, 0, NLM_SETPARAM_DEFAULT_MAX); + ev = (struct iscsi_uevent *)setparam_buf; + ev->type = ISCSI_UEVENT_PING; + ev->transport_handle = transport_handle; + ev->u.iscsi_ping.host_no = host_no; + ev->u.iscsi_ping.iface_num = iface_num; + ev->u.iscsi_ping.iface_type = iface_type; + ev->u.iscsi_ping.payload_size = size; + ev->u.iscsi_ping.pid = pid; + + if (addr->sa_family == PF_INET) + addrlen = sizeof(struct sockaddr_in); + else if (addr->sa_family == PF_INET6) + addrlen = sizeof(struct sockaddr_in6); + else { + log_error("%s unknown addr family %d", + __FUNCTION__, addr->sa_family); + return -EINVAL; + } + memcpy(setparam_buf + sizeof(*ev), addr, addrlen); + + iov[1].iov_base = ev; + iov[1].iov_len = sizeof(*ev) + addrlen; + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + return 0; +} + +static int kexec_ping(uint64_t transport_handle, uint32_t host_no, + struct sockaddr *addr, uint32_t iface_num, + uint32_t iface_type, uint32_t size, uint32_t *status) +{ + struct pollfd pfd; + struct timeval ping_timer; + int timeout, fd, rc; + uint32_t pid; + + *status = 0; + + fd = ipc->ctldev_open(); + if (fd < 0) { + log_error("Could not open netlink socket."); + return ISCSI_ERR; + } + + /* prepare to poll */ + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = fd; + pfd.events = POLLIN | POLLPRI; + + /* get unique ping id */ + pid = rand(); + + rc = ksend_ping(transport_handle, host_no, addr, iface_num, + iface_type, pid, size); + if (rc != 0) { + switch (rc) { + case -ENOSYS: + rc = ISCSI_ERR_OP_NOT_SUPP; + break; + case -EINVAL: + rc = ISCSI_ERR_INVAL; + break; + default: + rc = ISCSI_ERR; + } + goto close_nl; + } + + ping_event.host_no = -1; + ping_event.pid = -1; + ping_event.status = -1; + ping_event.active = -1; + + iscsi_timer_set(&ping_timer, 30); + + timeout = iscsi_timer_msecs_until(&ping_timer); + + while (1) { + pfd.revents = 0; + rc = poll(&pfd, 1, timeout); + + if (iscsi_timer_expired(&ping_timer)) { + rc = ISCSI_ERR_TRANS_TIMEOUT; + break; + } + + if (rc > 0) { + if (pfd.revents & (POLLIN | POLLPRI)) { + timeout = iscsi_timer_msecs_until(&ping_timer); + rc = ipc->ctldev_handle(); + + if (ping_event.active != 1) + continue; + + if (pid != ping_event.pid) + continue; + + rc = 0; + *status = ping_event.status; + break; + } + + if (pfd.revents & POLLHUP) { + rc = ISCSI_ERR_TRANS; + break; + } + + if (pfd.revents & POLLNVAL) { + rc = ISCSI_ERR_INTERNAL; + break; + } + + if (pfd.revents & POLLERR) { + rc = ISCSI_ERR_INTERNAL; + break; + } + } else if (rc < 0) { + rc = ISCSI_ERR_INTERNAL; + break; + } + } + +close_nl: + ipc->ctldev_close(); + return rc; +} + +static int kget_chap(uint64_t transport_handle, uint32_t host_no, + uint16_t chap_tbl_idx, uint32_t num_entries, + char *chap_buf, uint32_t *valid_chap_entries) +{ + int rc = 0; + int ev_size; + struct iscsi_uevent ev; + struct iovec iov[2]; + char nlm_ev[NLMSG_SPACE(sizeof(struct iscsi_uevent))]; + struct nlmsghdr *nlh; + + memset(&ev, 0, sizeof(struct iscsi_uevent)); + + ev.type = ISCSI_UEVENT_GET_CHAP; + ev.transport_handle = transport_handle; + ev.u.get_chap.host_no = host_no; + ev.u.get_chap.chap_tbl_idx = chap_tbl_idx; + ev.u.get_chap.num_entries = num_entries; + + iov[1].iov_base = &ev; + iov[1].iov_len = sizeof(ev); + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + if ((rc = nl_read(ctrl_fd, nlm_ev, + NLMSG_SPACE(sizeof(struct iscsi_uevent)), + MSG_PEEK)) < 0) { + log_error("can not read nlm_ev, error %d", rc); + return rc; + } + + nlh = (struct nlmsghdr *)nlm_ev; + ev_size = nlh->nlmsg_len - NLMSG_ALIGN(sizeof(struct nlmsghdr)); + + if ((rc = nlpayload_read(ctrl_fd, (void *)chap_buf, ev_size, 0)) < 0) { + log_error("can not read from NL socket, error %d", rc); + return rc; + } + + *valid_chap_entries = ev.u.get_chap.num_entries; + + return rc; +} + +static int kset_chap(uint64_t transport_handle, uint32_t host_no, + struct iovec *iovs, uint32_t param_count) +{ + int rc; + struct iscsi_uevent ev; + struct iovec *iov = iovs + 1; + + log_debug(8, "in %s", __func__); + + ev.type = ISCSI_UEVENT_SET_CHAP; + ev.transport_handle = transport_handle; + ev.u.set_path.host_no = host_no; + + iov->iov_base = &ev; + iov->iov_len = sizeof(ev); + + rc = __kipc_call(iovs, param_count); + if (rc < 0) + return rc; + + return 0; +} + +static int kdelete_chap(uint64_t transport_handle, uint32_t host_no, + uint16_t chap_tbl_idx) +{ + int rc = 0; + struct iscsi_uevent ev; + struct iovec iov[2]; + + memset(&ev, 0, sizeof(struct iscsi_uevent)); + + ev.type = ISCSI_UEVENT_DELETE_CHAP; + ev.transport_handle = transport_handle; + ev.u.delete_chap.host_no = host_no; + ev.u.delete_chap.chap_tbl_idx = chap_tbl_idx; + + iov[1].iov_base = &ev; + iov[1].iov_len = sizeof(ev); + + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + return rc; +} + +static int +kset_flashnode_params(uint64_t transport_handle, uint32_t host_no, + uint32_t flashnode_idx, struct iovec *iovs, + uint32_t param_count) +{ + struct iscsi_uevent ev; + int rc, ev_len; + struct iovec *iov = iovs + 1; + + log_debug(8, "in %s", __FUNCTION__); + + ev_len = sizeof(ev); + ev.type = ISCSI_UEVENT_SET_FLASHNODE_PARAMS; + ev.transport_handle = transport_handle; + ev.u.set_flashnode.host_no = host_no; + ev.u.set_flashnode.flashnode_idx = flashnode_idx; + /* first two iovs for nlmsg hdr and ev */ + ev.u.set_flashnode.count = param_count - 2; + + iov->iov_base = &ev; + iov->iov_len = ev_len; + rc = __kipc_call(iovs, param_count); + if (rc < 0) + return rc; + + return 0; +} + +static int +knew_flashnode(uint64_t transport_handle, uint32_t host_no, void *value, + uint32_t *flashnode_idx) +{ + struct iscsi_uevent *ev; + char *param_str; + int rc, len; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + memset(setparam_buf, 0, NLM_SETPARAM_DEFAULT_MAX); + ev = (struct iscsi_uevent *)setparam_buf; + ev->type = ISCSI_UEVENT_NEW_FLASHNODE; + ev->transport_handle = transport_handle; + ev->u.new_flashnode.host_no = host_no; + + param_str = setparam_buf + sizeof(*ev); + if (!strlen(value)) + return 0; + sprintf(param_str, "%s", (char *)value); + len = strlen(param_str) + 1; + ev->u.new_flashnode.len = len; + + + iov[1].iov_base = ev; + iov[1].iov_len = sizeof(*ev) + len; + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + *flashnode_idx = ev->r.new_flashnode_ret.flashnode_idx; + return 0; +} + +static int +kdel_flashnode(uint64_t transport_handle, uint32_t host_no, + uint32_t flashnode_idx) +{ + struct iscsi_uevent ev; + int rc; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + memset(&ev, 0, sizeof(struct iscsi_uevent)); + ev.type = ISCSI_UEVENT_DEL_FLASHNODE; + ev.transport_handle = transport_handle; + ev.u.del_flashnode.host_no = host_no; + ev.u.del_flashnode.flashnode_idx = flashnode_idx; + + iov[1].iov_base = &ev; + iov[1].iov_len = sizeof(ev); + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + return 0; +} + +static int +klogin_flashnode(uint64_t transport_handle, uint32_t host_no, + uint32_t flashnode_idx) +{ + struct iscsi_uevent ev; + int rc; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + memset(&ev, 0, sizeof(struct iscsi_uevent)); + ev.type = ISCSI_UEVENT_LOGIN_FLASHNODE; + ev.transport_handle = transport_handle; + ev.u.login_flashnode.host_no = host_no; + ev.u.login_flashnode.flashnode_idx = flashnode_idx; + + iov[1].iov_base = &ev; + iov[1].iov_len = sizeof(ev); + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + return 0; +} + +static int +klogout_flashnode(uint64_t transport_handle, uint32_t host_no, + uint32_t flashnode_idx) +{ + struct iscsi_uevent ev; + int rc; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + memset(&ev, 0, sizeof(struct iscsi_uevent)); + ev.type = ISCSI_UEVENT_LOGOUT_FLASHNODE; + ev.transport_handle = transport_handle; + ev.u.logout_flashnode.host_no = host_no; + ev.u.logout_flashnode.flashnode_idx = flashnode_idx; + + iov[1].iov_base = &ev; + iov[1].iov_len = sizeof(ev); + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + return 0; +} + +static int +klogout_flashnode_sid(uint64_t transport_handle, uint32_t host_no, + uint32_t sid) +{ + struct iscsi_uevent ev; + int rc; + struct iovec iov[2]; + + log_debug(7, "in %s", __FUNCTION__); + + memset(&ev, 0, sizeof(struct iscsi_uevent)); + ev.type = ISCSI_UEVENT_LOGOUT_FLASHNODE_SID; + ev.transport_handle = transport_handle; + ev.u.logout_flashnode_sid.host_no = host_no; + ev.u.logout_flashnode_sid.sid = sid; + + iov[1].iov_base = &ev; + iov[1].iov_len = sizeof(ev); + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + return 0; +} + +static int kget_host_stats(uint64_t transport_handle, uint32_t host_no, + char *host_stats) +{ + int rc = 0; + int ev_size; + struct iscsi_uevent ev; + struct iovec iov[2]; + char nlm_ev[NLMSG_SPACE(sizeof(struct iscsi_uevent))]; + struct nlmsghdr *nlh; + + memset(&ev, 0, sizeof(struct iscsi_uevent)); + + ev.type = ISCSI_UEVENT_GET_HOST_STATS; + ev.transport_handle = transport_handle; + ev.u.get_host_stats.host_no = host_no; + + iov[1].iov_base = &ev; + iov[1].iov_len = sizeof(ev); + rc = __kipc_call(iov, 2); + if (rc < 0) + return rc; + + if ((rc = nl_read(ctrl_fd, nlm_ev, + NLMSG_SPACE(sizeof(struct iscsi_uevent)), + MSG_PEEK)) < 0) { + log_error("can not read nlm_ev, error %d", rc); + return rc; + } + + nlh = (struct nlmsghdr *)nlm_ev; + ev_size = nlh->nlmsg_len - NLMSG_ALIGN(sizeof(struct nlmsghdr)); + + if ((rc = nlpayload_read(ctrl_fd, (void *)host_stats, + ev_size, 0)) < 0) { + log_error("can not read from NL socket, error %d", rc); + return rc; + } + + return rc; +} + + +static void drop_data(struct nlmsghdr *nlh) +{ + int ev_size; + + ev_size = nlh->nlmsg_len - NLMSG_ALIGN(sizeof(struct nlmsghdr)); + nlpayload_read(ctrl_fd, NULL, ev_size, 0); +} + +static int ctldev_handle(void) +{ + int rc, ev_size; + struct iscsi_uevent *ev; + iscsi_session_t *session = NULL; + iscsi_conn_t *conn = NULL; + char nlm_ev[NLMSG_SPACE(sizeof(struct iscsi_uevent))]; + struct nlmsghdr *nlh; + struct iscsi_ev_context *ev_context; + uint32_t sid = 0, cid = 0; + + log_debug(7, "in %s", __FUNCTION__); + + if ((rc = nl_read(ctrl_fd, nlm_ev, + NLMSG_SPACE(sizeof(struct iscsi_uevent)), MSG_PEEK)) < 0) { + log_error("can not read nlm_ev, error %d", rc); + return rc; + } + nlh = (struct nlmsghdr *)nlm_ev; + ev = (struct iscsi_uevent *)NLMSG_DATA(nlm_ev); + + log_debug(7, "%s got event type %u", __FUNCTION__, ev->type); + /* drivers like qla4xxx can be inserted after iscsid is started */ + switch (ev->type) { + case ISCSI_KEVENT_CREATE_SESSION: + /* old kernels sent ISCSI_UEVENT_CREATE_SESSION on creation */ + case ISCSI_UEVENT_CREATE_SESSION: + drop_data(nlh); + if (!ipc_ev_clbk) + return 0; + + if (ipc_ev_clbk->create_session) + ipc_ev_clbk->create_session(ev->r.c_session_ret.host_no, + ev->r.c_session_ret.sid); + return 0; + case ISCSI_KEVENT_DESTROY_SESSION: + drop_data(nlh); + if (!ipc_ev_clbk) + return 0; + + if (ipc_ev_clbk->destroy_session) + ipc_ev_clbk->destroy_session(ev->r.d_session.host_no, + ev->r.d_session.sid); + return 0; + case ISCSI_KEVENT_RECV_PDU: + sid = ev->r.recv_req.sid; + cid = ev->r.recv_req.cid; + break; + case ISCSI_KEVENT_CONN_ERROR: + sid = ev->r.connerror.sid; + cid = ev->r.connerror.cid; + break; + case ISCSI_KEVENT_CONN_LOGIN_STATE: + sid = ev->r.conn_login.sid; + cid = ev->r.conn_login.cid; + break; + case ISCSI_KEVENT_UNBIND_SESSION: + sid = ev->r.unbind_session.sid; + /* session wide event so cid is 0 */ + cid = 0; + break; + case ISCSI_KEVENT_HOST_EVENT: + switch (ev->r.host_event.code) { + case ISCSI_EVENT_LINKUP: + log_warning("Host%u: Link Up.", + ev->r.host_event.host_no); + break; + case ISCSI_EVENT_LINKDOWN: + log_warning("Host%u: Link Down.", + ev->r.host_event.host_no); + break; + default: + log_debug(7, "Host%u: Unknown host event: %u.", + ev->r.host_event.host_no, + ev->r.host_event.code); + } + + drop_data(nlh); + return 0; + case ISCSI_KEVENT_PING_COMP: + ping_event.host_no = ev->r.ping_comp.host_no; + ping_event.pid = ev->r.ping_comp.pid; + ping_event.status = ev->r.ping_comp.status; + ping_event.active = 1; + + drop_data(nlh); + return 0; + default: + if ((ev->type > ISCSI_UEVENT_MAX && ev->type < KEVENT_BASE) || + (ev->type > ISCSI_KEVENT_MAX)) + log_error("Unknown kernel event %d. You may want to " + " upgrade your iscsi tools.", ev->type); + else + /* + * If another app is using the interface we might + * see their + * stuff. Just drop it. + */ + log_debug(7, "Got unknown event %d. Dropping.", + ev->type); + drop_data(nlh); + return 0; + } + + /* verify connection */ + session = session_find_by_sid(sid); + if (!session) { + /* + * this can happen normally when other apps are using the + * nl interface. + */ + log_debug(1, "Could not verify connection %d:%d. Dropping " + "event.", sid, cid); + drop_data(nlh); + return -ENXIO; + } + conn = &session->conn[0]; + + ev_size = nlh->nlmsg_len - NLMSG_ALIGN(sizeof(struct nlmsghdr)); + + ev_context = ipc_ev_clbk->get_ev_context(conn, ev_size); + if (!ev_context) { + log_error("Can not allocate memory for receive context."); + drop_data(nlh); + return -ENOMEM; + } + + log_debug(6, "message real length is %d bytes, recv_handle %p", + nlh->nlmsg_len, ev_context->data); + + if ((rc = nlpayload_read(ctrl_fd, ev_context->data, + ev_size, 0)) < 0) { + ipc_ev_clbk->put_ev_context(ev_context); + log_error("can not read from NL socket, error %d", rc); + /* retry later */ + return rc; + } + + /* + * we sched these events because the handlers could call back + * into ctldev_handle + */ + switch (ev->type) { + case ISCSI_KEVENT_RECV_PDU: + rc = ipc_ev_clbk->sched_ev_context(ev_context, conn, 0, + EV_CONN_RECV_PDU); + break; + case ISCSI_KEVENT_CONN_ERROR: + memcpy(ev_context->data, &ev->r.connerror.error, + sizeof(ev->r.connerror.error)); + rc = ipc_ev_clbk->sched_ev_context(ev_context, conn, 0, + EV_CONN_ERROR); + break; + case ISCSI_KEVENT_CONN_LOGIN_STATE: + memcpy(ev_context->data, &ev->r.conn_login.state, + sizeof(ev->r.conn_login.state)); + rc = ipc_ev_clbk->sched_ev_context(ev_context, conn, 0, + EV_CONN_LOGIN); + break; + case ISCSI_KEVENT_UNBIND_SESSION: + rc = ipc_ev_clbk->sched_ev_context(ev_context, conn, 0, + EV_CONN_STOP); + break; + default: + ipc_ev_clbk->put_ev_context(ev_context); + log_error("unknown kernel event %d", ev->type); + return -EEXIST; + } + + if (rc) + ipc_ev_clbk->put_ev_context(ev_context); + return rc; +} + +static int +ctldev_open(void) +{ + log_debug(7, "in %s", __FUNCTION__); + + nlm_sendbuf = calloc(1, NLM_BUF_DEFAULT_MAX); + if (!nlm_sendbuf) { + log_error("can not allocate nlm_sendbuf"); + return -1; + } + + nlm_recvbuf = calloc(1, NLM_BUF_DEFAULT_MAX); + if (!nlm_recvbuf) { + log_error("can not allocate nlm_recvbuf"); + goto free_nlm_sendbuf; + } + + pdu_sendbuf = calloc(1, PDU_SENDBUF_DEFAULT_MAX); + if (!pdu_sendbuf) { + log_error("can not allocate nlm_sendbuf"); + goto free_nlm_recvbuf; + } + + setparam_buf = calloc(1, NLM_SETPARAM_DEFAULT_MAX); + if (!setparam_buf) { + log_error("can not allocate setparam_buf"); + goto free_pdu_sendbuf; + } + + ctrl_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ISCSI); + if (ctrl_fd < 0) { + log_error("can not create NETLINK_ISCSI socket [%s]", + strerror(errno)); + goto free_setparam_buf; + } + + memset(&src_addr, 0, sizeof(src_addr)); + src_addr.nl_family = AF_NETLINK; + src_addr.nl_pid = getpid(); + src_addr.nl_groups = 1; + if (bind(ctrl_fd, (struct sockaddr *)&src_addr, sizeof(src_addr))) { + log_error("can not bind NETLINK_ISCSI socket [%s]", + strerror(errno)); + goto close_socket; + } + + memset(&dest_addr, 0, sizeof(dest_addr)); + dest_addr.nl_family = AF_NETLINK; + dest_addr.nl_pid = 0; /* kernel */ + dest_addr.nl_groups = 0; /* unicast */ + + log_debug(7, "created NETLINK_ISCSI socket..."); + + return ctrl_fd; + +close_socket: + close(ctrl_fd); +free_setparam_buf: + free(setparam_buf); +free_pdu_sendbuf: + free(pdu_sendbuf); +free_nlm_recvbuf: + free(nlm_recvbuf); +free_nlm_sendbuf: + free(nlm_sendbuf); + return -1; +} + +static void +ctldev_close(void) +{ + log_debug(7, "in %s", __FUNCTION__); + + if (ctrl_fd >= 0) + close(ctrl_fd); + free(setparam_buf); + free(pdu_sendbuf); + free(nlm_recvbuf); + free(nlm_sendbuf); +} + +struct iscsi_ipc nl_ipc = { + .name = "Open-iSCSI Kernel IPC/NETLINK v.1", + .ctldev_bufmax = NLM_BUF_DEFAULT_MAX, + .ctldev_open = ctldev_open, + .ctldev_close = ctldev_close, + .ctldev_handle = ctldev_handle, + .sendtargets = ksendtargets, + .create_session = kcreate_session, + .destroy_session = kdestroy_session, + .unbind_session = kunbind_session, + .create_conn = kcreate_conn, + .destroy_conn = kdestroy_conn, + .bind_conn = kbind_conn, + .set_param = kset_param, + .set_host_param = kset_host_param, + .get_param = NULL, + .start_conn = kstart_conn, + .stop_conn = kstop_conn, + .get_stats = kget_stats, + .writev = kwritev, + .send_pdu_begin = ksend_pdu_begin, + .send_pdu_end = ksend_pdu_end, + .read = kread, + .recv_pdu_begin = krecv_pdu_begin, + .recv_pdu_end = krecv_pdu_end, + .set_net_config = kset_net_config, + .recv_conn_state = krecv_conn_state, + .exec_ping = kexec_ping, + .get_chap = kget_chap, + .set_chap = kset_chap, + .delete_chap = kdelete_chap, + .set_flash_node_params = kset_flashnode_params, + .new_flash_node = knew_flashnode, + .del_flash_node = kdel_flashnode, + .login_flash_node = klogin_flashnode, + .logout_flash_node = klogout_flashnode, + .logout_flash_node_sid = klogout_flashnode_sid, + .get_host_stats = kget_host_stats, +}; +struct iscsi_ipc *ipc = &nl_ipc; + +void ipc_register_ev_callback(struct iscsi_ipc_ev_clbk *ev_clbk) +{ + ipc_ev_clbk = ev_clbk; +} diff --git a/usr/scsi.c b/usr/scsi.c new file mode 100644 index 0000000..14767e2 --- /dev/null +++ b/usr/scsi.c @@ -0,0 +1,83 @@ +/* + * The following is from the linux kernel scsi_error.c + * + * scsi_error.c Copyright (C) 1997 Eric Youngdale + * + * SCSI error/timeout handling + * Initial versions: Eric Youngdale. Based upon conversations with + * Leonard Zubkoff and David Miller at Linux Expo, + * ideas originating from all over the place. + * + * Restructured scsi_unjam_host and associated functions. + * September 04, 2002 Mike Anderson (andmike@us.ibm.com) + * + * Forward port of Russell King's (rmk@arm.linux.org.uk) changes and + * minor cleanups. + * September 30, 2002 Mike Anderson (andmike@us.ibm.com) + */ + +#include +#include "scsi.h" + +/** + * scsi_normalize_sense - normalize main elements from either fixed or + * descriptor sense data format into a common format. + * + * @sense_buffer: byte array containing sense data returned by device + * @sb_len: number of valid bytes in sense_buffer + * @sshdr: pointer to instance of structure that common + * elements are written to. + * + * Notes: + * The "main elements" from sense data are: response_code, sense_key, + * asc, ascq and additional_length (only for descriptor format). + * + * Typically this function can be called after a device has + * responded to a SCSI command with the CHECK_CONDITION status. + * + * Return value: + * 1 if valid sense data information found, else 0; + **/ +int scsi_normalize_sense(const uint8_t *sense_buffer, int sb_len, + struct scsi_sense_hdr *sshdr) +{ + if (!sense_buffer || !sb_len) + return 0; + + memset(sshdr, 0, sizeof(struct scsi_sense_hdr)); + + sshdr->response_code = (sense_buffer[0] & 0x7f); + + if (!scsi_sense_valid(sshdr)) + return 0; + + if (sshdr->response_code >= 0x72) { + /* + * descriptor format + */ + if (sb_len > 1) + sshdr->sense_key = (sense_buffer[1] & 0xf); + if (sb_len > 2) + sshdr->asc = sense_buffer[2]; + if (sb_len > 3) + sshdr->ascq = sense_buffer[3]; + if (sb_len > 7) + sshdr->additional_length = sense_buffer[7]; + } else { + /* + * fixed format + */ + if (sb_len > 2) + sshdr->sense_key = (sense_buffer[2] & 0xf); + if (sb_len > 7) { + sb_len = (sb_len < (sense_buffer[7] + 8)) ? + sb_len : (sense_buffer[7] + 8); + if (sb_len > 12) + sshdr->asc = sense_buffer[12]; + if (sb_len > 13) + sshdr->ascq = sense_buffer[13]; + } + } + + return 1; +} diff --git a/usr/scsi.h b/usr/scsi.h new file mode 100644 index 0000000..d8ef951 --- /dev/null +++ b/usr/scsi.h @@ -0,0 +1,40 @@ +/* + * this is from the linux kernel scsi_eh.h + */ +#ifndef _SCSI_SCSI_H +#define _SCSI_SCSI_H + +#include + +/* + * This is a slightly modified SCSI sense "descriptor" format header. + * The addition is to allow the 0x70 and 0x71 response codes. The idea + * is to place the salient data from either "fixed" or "descriptor" sense + * format into one structure to ease application processing. + * + * The original sense buffer should be kept around for those cases + * in which more information is required (e.g. the LBA of a MEDIUM ERROR). + */ +struct scsi_sense_hdr { /* See SPC-3 section 4.5 */ + uint8_t response_code; /* permit: 0x0, 0x70, 0x71, 0x72, 0x73 */ + uint8_t sense_key; + uint8_t asc; + uint8_t ascq; + uint8_t byte4; + uint8_t byte5; + uint8_t byte6; + uint8_t additional_length; /* always 0 for fixed sense format */ +}; + +static inline int scsi_sense_valid(struct scsi_sense_hdr *sshdr) +{ + if (!sshdr) + return 0; + + return (sshdr->response_code & 0x70) == 0x70; +} + +extern int scsi_normalize_sense(const uint8_t *sense_buffer, int sb_len, + struct scsi_sense_hdr *sshdr); + +#endif diff --git a/usr/session_info.c b/usr/session_info.c new file mode 100644 index 0000000..0dae82f --- /dev/null +++ b/usr/session_info.c @@ -0,0 +1,482 @@ +#include +#include +#include +#include +#include + +#include + +#include "list.h" +#include "log.h" +#include "iscsi_sysfs.h" +#include "version.h" +#include "iscsi_settings.h" +#include "mgmt_ipc.h" +#include "session_info.h" +#include "transport.h" +#include "initiator.h" +#include "iface.h" +#include "iscsid_req.h" +#include "iscsi_err.h" + +static int session_info_print_flat(struct iscsi_session *se); + +int session_info_create_list(void *data, struct session_info *info) +{ + struct session_link_info *link_info = data; + struct list_head *list = link_info->list; + struct session_info *new, *curr, *match = NULL; + + if (link_info->match_fn && !link_info->match_fn(link_info->data, info)) + return -1; + + new = calloc(1, sizeof(*new)); + if (!new) + return ISCSI_ERR_NOMEM; + memcpy(new, info, sizeof(*new)); + INIT_LIST_HEAD(&new->list); + + if (list_empty(list)) { + list_add_tail(&new->list, list); + return 0; + } + + list_for_each_entry(curr, list, list) { + if (!strcmp(curr->targetname, info->targetname)) { + match = curr; + + if (!strcmp(curr->address, info->address)) { + match = curr; + + if (curr->port == info->port) { + match = curr; + break; + } + } + } + } + + list_add_tail(&new->list, match ? match->list.next : list); + return 0; +} + +void session_info_free_list(struct list_head *list) +{ + struct session_info *curr, *tmp; + + list_for_each_entry_safe(curr, tmp, list, list) { + list_del(&curr->list); + free(curr); + } +} + +static char *get_iscsi_node_type(uint32_t sid) +{ + int pid = iscsi_sysfs_session_user_created((int) sid); + + if (!pid) + return "flash"; + else + return "non-flash"; +} + +static int session_info_print_flat(struct iscsi_session *se) +{ + uint32_t sid = 0; + struct iscsi_transport *t = NULL; + + sid = iscsi_session_sid_get(se); + t = iscsi_sysfs_get_transport_by_sid((int) sid); + + if (strchr(iscsi_session_persistent_address_get(se), '.')) + printf("%s: [%" PRIu32 "] %s:%" PRIi32 ",%"PRIi32 " %s (%s)\n", + t ? t->name : UNKNOWN_VALUE, + sid, iscsi_session_persistent_address_get(se), + iscsi_session_persistent_port_get(se), + iscsi_session_tpgt_get(se), + iscsi_session_target_name_get(se), + get_iscsi_node_type(sid)); + else + printf("%s: [%" PRIu32 "] [%s]:%" PRIi32 ",%" PRIi32 + " %s (%s)\n", + t ? t->name : UNKNOWN_VALUE, + sid, iscsi_session_persistent_address_get(se), + iscsi_session_persistent_port_get(se), + iscsi_session_tpgt_get(se), + iscsi_session_target_name_get(se), + get_iscsi_node_type(sid)); + return 0; +} + +static int print_iscsi_state(int sid, char *prefix, int tmo) +{ + iscsiadm_req_t req; + iscsiadm_rsp_t rsp; + int err; + char *state = NULL; + char state_buff[SCSI_MAX_STATE_VALUE]; + static char *conn_state[] = { + "FREE", + "TRANSPORT WAIT", + "IN LOGIN", + "LOGGED IN", + "IN LOGOUT", + "LOGOUT REQUESTED", + "CLEANUP WAIT", + }; + static char *session_state[] = { + "NO CHANGE", + "CLEANUP", + "REOPEN", + "REDIRECT", + }; + + memset(&req, 0, sizeof(iscsiadm_req_t)); + req.command = MGMT_IPC_SESSION_INFO; + req.u.session.sid = sid; + + err = iscsid_exec_req(&req, &rsp, 1, tmo); + /* + * for drivers like qla4xxx, iscsid does not display + * anything here since it does not know about it. + */ + if (!err && rsp.u.session_state.conn_state >= 0 && + rsp.u.session_state.conn_state <= ISCSI_CONN_STATE_CLEANUP_WAIT) + state = conn_state[rsp.u.session_state.conn_state]; + printf("%s\t\tiSCSI Connection State: %s\n", prefix, + state ? state : "Unknown"); + state = NULL; + + memset(state_buff, 0, SCSI_MAX_STATE_VALUE); + if (!iscsi_sysfs_get_session_state(state_buff, sid)) + printf("%s\t\tiSCSI Session State: %s\n", prefix, state_buff); + else + printf("%s\t\tiSCSI Session State: Unknown\n", prefix); + + if (!err && rsp.u.session_state.session_state >= 0 && + rsp.u.session_state.session_state <= R_STAGE_SESSION_REDIRECT) + state = session_state[rsp.u.session_state.session_state]; + printf("%s\t\tInternal iscsid Session State: %s\n", prefix, + state ? state : "Unknown"); + return 0; +} + +static void print_iscsi_params(int sid, char *prefix) +{ + struct iscsi_session_operational_config session_conf; + struct iscsi_conn_operational_config conn_conf; + + iscsi_sysfs_get_negotiated_session_conf(sid, &session_conf); + iscsi_sysfs_get_negotiated_conn_conf(sid, &conn_conf); + + printf("%s\t\t************************\n", prefix); + printf("%s\t\tNegotiated iSCSI params:\n", prefix); + printf("%s\t\t************************\n", prefix); + + if (is_valid_operational_value(conn_conf.HeaderDigest)) + printf("%s\t\tHeaderDigest: %s\n", prefix, + conn_conf.HeaderDigest ? "CRC32C" : "None"); + if (is_valid_operational_value(conn_conf.DataDigest)) + printf("%s\t\tDataDigest: %s\n", prefix, + conn_conf.DataDigest ? "CRC32C" : "None"); + if (is_valid_operational_value(conn_conf.MaxRecvDataSegmentLength)) + printf("%s\t\tMaxRecvDataSegmentLength: %d\n", prefix, + conn_conf.MaxRecvDataSegmentLength); + if (is_valid_operational_value(conn_conf.MaxXmitDataSegmentLength)) + printf("%s\t\tMaxXmitDataSegmentLength: %d\n", prefix, + conn_conf.MaxXmitDataSegmentLength); + if (is_valid_operational_value(session_conf.FirstBurstLength)) + printf("%s\t\tFirstBurstLength: %d\n", prefix, + session_conf.FirstBurstLength); + if (is_valid_operational_value(session_conf.MaxBurstLength)) + printf("%s\t\tMaxBurstLength: %d\n", prefix, + session_conf.MaxBurstLength); + if (is_valid_operational_value(session_conf.ImmediateData)) + printf("%s\t\tImmediateData: %s\n", prefix, + session_conf.ImmediateData ? "Yes" : "No"); + if (is_valid_operational_value(session_conf.InitialR2T)) + printf("%s\t\tInitialR2T: %s\n", prefix, + session_conf.InitialR2T ? "Yes" : "No"); + if (is_valid_operational_value(session_conf.MaxOutstandingR2T)) + printf("%s\t\tMaxOutstandingR2T: %d\n", prefix, + session_conf.MaxOutstandingR2T); +} + +static void print_scsi_device_info(void *data, int host_no, int target, int lun) +{ + char *prefix = data; + char *blockdev, state[SCSI_MAX_STATE_VALUE]; + + printf("%s\t\tscsi%d Channel 00 Id %d Lun: %d\n", prefix, host_no, + target, lun); + blockdev = iscsi_sysfs_get_blockdev_from_lun(host_no, target, lun); + if (blockdev) { + printf("%s\t\t\tAttached scsi disk %s\t\t", prefix, blockdev); + free(blockdev); + + if (!iscsi_sysfs_get_device_state(state, host_no, target, lun)) + printf("State: %s\n", state); + else + printf("State: Unknown\n"); + } +} + +static int print_scsi_state(int sid, char *prefix, unsigned int flags) +{ + int host_no = -1, err = 0; + char state[SCSI_MAX_STATE_VALUE]; + + printf("%s\t\t************************\n", prefix); + printf("%s\t\tAttached SCSI devices:\n", prefix); + printf("%s\t\t************************\n", prefix); + + host_no = iscsi_sysfs_get_host_no_from_sid(sid, &err); + if (err) { + printf("%s\t\tUnavailable\n", prefix); + return err; + } + + if (flags & SESSION_INFO_HOST_DEVS) { + printf("%s\t\tHost Number: %d\t", prefix, host_no); + if (!iscsi_sysfs_get_host_state(state, host_no)) + printf("State: %s\n", state); + else + printf("State: Unknown\n"); + } + + if (flags & SESSION_INFO_SCSI_DEVS) + iscsi_sysfs_for_each_device(prefix, host_no, sid, + print_scsi_device_info); + return 0; +} + +void session_info_print_tree(struct iscsi_session **ses, uint32_t se_count, + char *prefix, unsigned int flags, int do_show) +{ + struct iscsi_session *curr = NULL; + struct iscsi_session *prev = NULL; + const char *curr_targetname = NULL; + const char *curr_address = NULL; + const char *persistent_address = NULL; + const char *prev_targetname = NULL; + const char *prev_address = NULL; + int32_t curr_port = 0; + int32_t prev_port = 0; + uint32_t i = 0; + uint32_t sid = 0; + char *new_prefix = NULL; + int32_t tgt_reset_tmo = -1; + int32_t lu_reset_tmo = -1; + int32_t abort_tmo = -1; + const char *pass = NULL; + + for (i = 0; i < se_count; ++i) { + curr = ses[i]; + curr_targetname = iscsi_session_target_name_get(curr); + sid = iscsi_session_sid_get(curr); + if (prev != NULL) + prev_targetname = iscsi_session_target_name_get(prev); + else + prev_targetname = NULL; + + if (! ((prev_targetname != NULL) && + (curr_targetname != NULL) && + (strcmp(prev_targetname, curr_targetname) == 0))) { + printf("%sTarget: %s (%s)\n", prefix, curr_targetname, + get_iscsi_node_type(sid)); + prev = NULL; + } + curr_address = iscsi_session_address_get(curr); + curr_port = iscsi_session_port_get(curr); + + if (prev != NULL) { + prev_address = iscsi_session_address_get(prev); + prev_port = iscsi_session_port_get(prev); + } else { + prev_address = NULL; + prev_port = 0; + } + if (! ((prev_address != NULL) && + (curr_address != NULL) && + (prev_port != 0) && + (curr_port != 0) && + (strcmp(prev_address, curr_address) == 0) && + (curr_port == prev_port))) { + if (strchr(curr_address, '.')) + printf("%s\tCurrent Portal: %s:%" PRIi32 + ",%" PRIi32 "\n", + prefix, curr_address, curr_port, + iscsi_session_tpgt_get(curr)); + else + printf("%s\tCurrent Portal: [%s]:%" PRIi32 + ",%" PRIi32 "\n", + prefix, curr_address, curr_port, + iscsi_session_tpgt_get(curr)); + persistent_address = + iscsi_session_persistent_address_get(curr); + + if (strchr(persistent_address, '.')) + printf("%s\tPersistent Portal: %s:%" PRIi32 + ",%" PRIi32 "\n", + prefix, persistent_address, + iscsi_session_persistent_port_get(curr), + iscsi_session_tpgt_get(curr)); + else + printf("%s\tPersistent Portal: [%s]:%" PRIi32 + ",%" PRIi32 "\n", + prefix, persistent_address, + iscsi_session_persistent_port_get(curr), + iscsi_session_tpgt_get(curr)); + } else + printf("\n"); + + if (flags & SESSION_INFO_IFACE) { + printf("%s\t\t**********\n", prefix); + printf("%s\t\tInterface:\n", prefix); + printf("%s\t\t**********\n", prefix); + + new_prefix = calloc(1, 1 + strlen(prefix) + + strlen("\t\t")); + if (new_prefix == NULL) { + printf("Could not print interface info. " + "Out of Memory.\n"); + return; + } else { + sprintf(new_prefix, "%s%s", prefix, "\t\t"); + iface_print(iscsi_session_iface_get(curr), + new_prefix); + } + free(new_prefix); + } + + if (flags & SESSION_INFO_ISCSI_STATE) { + printf("%s\t\tSID: %" PRIu32 "\n", prefix, sid); + print_iscsi_state((int) sid, prefix, -1 /* tmo */); + /* TODO(Gris Ge): It seems in the whole project, + * tmo is always -1, correct? + */ + } + + if (flags & SESSION_INFO_ISCSI_TIM) { + printf("%s\t\t*********\n", prefix); + printf("%s\t\tTimeouts:\n", prefix); + printf("%s\t\t*********\n", prefix); + + printf("%s\t\tRecovery Timeout: %" PRIi32 "\n", prefix, + iscsi_session_recovery_tmo_get(curr)); + + tgt_reset_tmo = iscsi_session_tgt_reset_tmo_get(curr); + lu_reset_tmo = iscsi_session_lu_reset_tmo_get(curr); + abort_tmo = iscsi_session_abort_tmo_get(curr); + + if (tgt_reset_tmo >= 0) + printf("%s\t\tTarget Reset Timeout: %" PRIi32 + "\n", prefix, tgt_reset_tmo); + else + printf("%s\t\tTarget Reset Timeout: %s\n", + prefix, UNKNOWN_VALUE); + + if (lu_reset_tmo >= 0) + printf("%s\t\tLUN Reset Timeout: %" PRIi32 "\n", + prefix, lu_reset_tmo); + else + printf("%s\t\tLUN Reset Timeout: %s\n", prefix, + UNKNOWN_VALUE); + + if (abort_tmo >= 0) + printf("%s\t\tAbort Timeout: %" PRIi32 "\n", + prefix, abort_tmo); + else + printf("%s\t\tAbort Timeout: %s\n", prefix, + UNKNOWN_VALUE); + + } + if (flags & SESSION_INFO_ISCSI_AUTH) { + printf("%s\t\t*****\n", prefix); + printf("%s\t\tCHAP:\n", prefix); + printf("%s\t\t*****\n", prefix); + printf("%s\t\tusername: %s\n", prefix, + strlen(iscsi_session_username_get(curr)) ? + iscsi_session_username_get(curr) : + UNKNOWN_VALUE); + + if (!do_show) + printf("%s\t\tpassword: %s\n", prefix, + "********"); + else { + pass = iscsi_session_password_get(curr); + printf("%s\t\tpassword: %s\n", prefix, + strlen(pass) ? pass : UNKNOWN_VALUE); + } + + printf("%s\t\tusername_in: %s\n", prefix, + strlen(iscsi_session_username_in_get(curr)) ? + iscsi_session_username_in_get(curr) : + UNKNOWN_VALUE); + if (!do_show) + printf("%s\t\tpassword_in: %s\n", prefix, + "********"); + else { + pass = iscsi_session_password_in_get(curr); + printf("%s\t\tpassword: %s\n", prefix, + strlen(pass) ? pass : UNKNOWN_VALUE); + } + } + + if (flags & SESSION_INFO_ISCSI_PARAMS) + print_iscsi_params((int) sid, prefix); + + if (flags & (SESSION_INFO_SCSI_DEVS | SESSION_INFO_HOST_DEVS)) + print_scsi_state((int) sid, prefix, flags); + + prev = curr; + } +} + +int session_info_print(int info_level, struct iscsi_session **ses, + uint32_t se_count, int do_show) +{ + int err = 0; + char *version; + unsigned int flags = 0; + uint32_t i = 0; + + switch (info_level) { + case 0: + case -1: + for (i = 0; i < se_count; ++i) { + err = session_info_print_flat(ses[i]); + if (err != 0) + break; + } + break; + case 3: + version = iscsi_sysfs_get_iscsi_kernel_version(); + if (version) { + printf("iSCSI Transport Class version %s\n", + version); + printf("version %s\n", ISCSI_VERSION_STR); + free(version); + } + + flags |= (SESSION_INFO_SCSI_DEVS | SESSION_INFO_HOST_DEVS); + /* fall through */ + case 2: + flags |= (SESSION_INFO_ISCSI_PARAMS | SESSION_INFO_ISCSI_TIM + | SESSION_INFO_ISCSI_AUTH); + /* fall through */ + case 1: + flags |= (SESSION_INFO_ISCSI_STATE | SESSION_INFO_IFACE); + session_info_print_tree(ses, se_count, "", flags, do_show); + break; + default: + log_error("Invalid info level %d. Try 0 - 3.", info_level); + return ISCSI_ERR_INVAL; + } + + if (err) { + log_error("Can not get list of active sessions (%d)", err); + return err; + } + return 0; +} diff --git a/usr/session_info.h b/usr/session_info.h new file mode 100644 index 0000000..cf2e714 --- /dev/null +++ b/usr/session_info.h @@ -0,0 +1,72 @@ +#ifndef SESSION_INFO_H +#define SESSION_INFO_H +#include + +#include + +#include "sysfs.h" +#include "types.h" +#include "iscsi_proto.h" +#include "config.h" + +struct list; + +struct session_timeout { + int abort_tmo; + int lu_reset_tmo; + int recovery_tmo; + int tgt_reset_tmo; +}; + +struct session_CHAP { + char username[AUTH_STR_MAX_LEN]; + char password[AUTH_STR_MAX_LEN]; + char username_in[AUTH_STR_MAX_LEN]; + char password_in[AUTH_STR_MAX_LEN]; +}; + +struct session_info { + struct list_head list; + /* local info */ + struct iface_rec iface; + int sid; + int iscsid_req_tmo; + + struct session_timeout tmo; + struct session_CHAP chap; + + /* remote info */ + char targetname[TARGET_NAME_MAXLEN + 1]; + int tpgt; + char address[NI_MAXHOST + 1]; + int port; + char persistent_address[NI_MAXHOST + 1]; + int persistent_port; +}; + +typedef int (session_match_info_fn_t)(void *data, struct session_info *info); + +struct session_link_info { + struct list_head *list; + session_match_info_fn_t *match_fn; + void *data; +}; + +#define SESSION_INFO_IFACE 0x1 +#define SESSION_INFO_ISCSI_PARAMS 0x2 +#define SESSION_INFO_ISCSI_STATE 0x4 +#define SESSION_INFO_SCSI_DEVS 0x8 +#define SESSION_INFO_HOST_DEVS 0x10 +#define SESSION_INFO_ISCSI_TIM 0x20 +#define SESSION_INFO_ISCSI_AUTH 0x40 + +extern int session_info_create_list(void *data, struct session_info *info); +extern void session_info_free_list(struct list_head *list); +extern int session_info_print(int info_level, struct iscsi_session **ses, + uint32_t se_count, int do_show); + +extern void session_info_print_tree(struct iscsi_session **ses, + uint32_t se_count, char *prefix, + unsigned int flags, int do_show); + +#endif diff --git a/usr/session_mgmt.c b/usr/session_mgmt.c new file mode 100644 index 0000000..0500f15 --- /dev/null +++ b/usr/session_mgmt.c @@ -0,0 +1,475 @@ +/* + * iSCSI session management helpers + * + * Copyright (C) 2010 Mike Christie + * Copyright (C) 2010 Red Hat, Inc. All rights reserved. + * Copyright (C) 2011 Dell Inc. + * maintained by open-iscsi@googlegroups.com + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#include +#include +#include +#include + +#include "idbm.h" +#include "list.h" +#include "iscsi_util.h" +#include "mgmt_ipc.h" +#include "session_info.h" +#include "iscsi_sysfs.h" +#include "log.h" +#include "iscsid_req.h" +#include "iscsi_err.h" + +static void log_login_msg(struct node_rec *rec, int rc) +{ + if (rc) { + log_error("Could not login to [iface: %s, target: %s, " + "portal: %s,%d].", rec->iface.name, + rec->name, rec->conn[0].address, + rec->conn[0].port); + iscsi_err_print_msg(rc); + } else + log_info("Login to [iface: %s, target: %s, portal: " + "%s,%d] successful.", rec->iface.name, + rec->name, rec->conn[0].address, + rec->conn[0].port); +} + +struct iscsid_async_req { + struct list_head list; + void *data; + int fd; +}; + +/** + * iscsid_reqs_close - close open async requests + * @list: list of async reqs + * + * This just closes the socket to the daemon. + */ +static void iscsid_reqs_close(struct list_head *list) +{ + struct iscsid_async_req *tmp, *curr; + + list_for_each_entry_safe(curr, tmp, list, list) { + close(curr->fd); + list_del(&curr->list); + free(curr); + } +} + +static int iscsid_login_reqs_wait(struct list_head *list) +{ + struct iscsid_async_req *tmp, *curr; + struct node_rec *rec; + int ret = 0; + + list_for_each_entry_safe(curr, tmp, list, list) { + int err; + + rec = curr->data; + err = iscsid_req_wait(MGMT_IPC_SESSION_LOGIN, curr->fd); + if (err && !ret) + ret = err; + log_login_msg(rec, err); + list_del(&curr->list); + free(curr); + } + return ret; +} + +/** + * __iscsi_login_portal - request iscsid to login to portal + * @data: If set, copies the session.multiple value to the portal record + * so it is propagated to iscsid. + * @list: If async, list to add session to + * @rec: portal rec to log into + */ +static int +__iscsi_login_portal(void *data, struct list_head *list, struct node_rec *rec) +{ + struct iscsid_async_req *async_req = NULL; + int rc = 0, fd; + + if (data && !rec->session.multiple) { + struct node_rec *pattern_rec = data; + rec->session.multiple = pattern_rec->session.multiple; + } + + log_info("Logging in to [iface: %s, target: %s, portal: %s,%d]%s", + rec->iface.name, rec->name, rec->conn[0].address, + rec->conn[0].port, + (rec->session.multiple ? " (multiple)" : "")); + + if (list) { + async_req = calloc(1, sizeof(*async_req)); + if (!async_req) + log_info("Could not allocate memory for async login " + "handling. Using sequential login instead."); + else + INIT_LIST_HEAD(&async_req->list); + } + + if (async_req) + rc = iscsid_req_by_rec_async(MGMT_IPC_SESSION_LOGIN, + rec, &fd); + else + rc = iscsid_req_by_rec(MGMT_IPC_SESSION_LOGIN, rec); + + if (rc) { + log_login_msg(rec, rc); + if (async_req) + free(async_req); + return rc; + } + + if (async_req) { + list_add_tail(&async_req->list, list); + async_req->fd = fd; + async_req->data = rec; + } else + log_login_msg(rec, rc); + + return 0; +} + +/** + * iscsi_login_portal - request iscsid to login to portal multiple + * times, based on the session.nr_sessions in the portal record. + * @data: If set, session.multiple will cause an additional session to + * be created regardless of the value of session.nr_sessions + * @list: If async, list to add session to + * @rec: portal rec to log into + */ +int iscsi_login_portal(void *data, struct list_head *list, struct node_rec *rec) +{ + struct node_rec *pattern_rec = data; + int rc = 0, session_count = 0, i; + + /* + * If pattern_rec->session.multiple is set, just add a single new + * session by passing things along to __iscsi_login_portal + */ + if (pattern_rec && pattern_rec->session.multiple) + return __iscsi_login_portal(data, list, rec); + + /* + * Count the current number of sessions, and only create those + * that are missing. + */ + rc = iscsi_sysfs_for_each_session(rec, &session_count, + iscsi_match_session_count, 0); + if (rc) { + log_error("Could not count current number of sessions"); + goto done; + } + if (session_count >= rec->session.nr_sessions) { + log_warning("%s: %d session%s requested, but %d " + "already present.", + rec->iface.name, rec->session.nr_sessions, + rec->session.nr_sessions == 1 ? "" : "s", + session_count); + rc = ISCSI_ERR_SESS_EXISTS; + goto done; + } + + /* + * Ensure the record's 'multiple' flag is set so __iscsi_login_portal + * will allow multiple logins, but only if configured for more + * than one + */ + if (rec->session.nr_sessions > 1) + rec->session.multiple = 1; + for (i = session_count; i < rec->session.nr_sessions; ++i) { + log_debug(1, "%s: Creating session %d/%d", rec->iface.name, + i + 1, rec->session.nr_sessions); + int err = __iscsi_login_portal(pattern_rec, list, rec); + if (err && !rc) + rc = err; + } + +done: + return rc; +} + +/** + * iscsi_login_portal_nowait - request iscsid to login to portal + * @rec: portal rec to log into + * + * This sends the login request, but does not wait for the result. + */ +int iscsi_login_portal_nowait(struct node_rec *rec) +{ + struct list_head list; + int err; + + INIT_LIST_HEAD(&list); + err = iscsi_login_portal(NULL, &list, rec); + if (err > 0) + return err; + iscsid_reqs_close(&list); + return 0; +} + +/** + * __iscsi_login_portals - login into portals on @rec_list, + * @data: data to pass to login_fn + * @nr_found: returned with number of portals logged into + * @wait: bool indicating if the fn should wait for the result + * @rec_list: list of portals to log into + * @clear_list: If set, delete and free rec_list after iterating through. + * @login_fn: list iter function + * + * This will loop over the list of portals and login. It + * will attempt to login asynchronously, and then wait for + * them to complete if wait is set. + */ +static +int __iscsi_login_portals(void *data, int *nr_found, int wait, + struct list_head *rec_list, int clear_list, + int (*login_fn)(void *, struct list_head *, + struct node_rec *)) +{ + struct node_rec *curr_rec, *tmp; + struct list_head login_list; + int ret = 0, err; + + *nr_found = 0; + INIT_LIST_HEAD(&login_list); + + list_for_each_entry(curr_rec, rec_list, list) { + err = login_fn(data, &login_list, curr_rec); + if (err > 0 && !ret) + ret = err; + if (!err) + (*nr_found)++; + } + if (wait) { + err = iscsid_login_reqs_wait(&login_list); + if (err && !ret) + ret = err; + } else + iscsid_reqs_close(&login_list); + + if (clear_list) { + list_for_each_entry_safe(curr_rec, tmp, rec_list, list) { + list_del(&curr_rec->list); + free(curr_rec); + } + } + return ret; +} + +/** + * iscsi_login_portals - login into portals on @rec_list, + * @data: data to pass to login_fn + * @nr_found: returned with number of portals logged into + * @wait: bool indicating if the fn should wait for the result + * @rec_list: list of portals to log into. This list is deleted after + * iterating through it. + * @login_fn: list iter function + * + * This will loop over the list of portals and login. It + * will attempt to login asynchronously, and then wait for + * them to complete if wait is set. + */ +int iscsi_login_portals(void *data, int *nr_found, int wait, + struct list_head *rec_list, + int (*login_fn)(void *, struct list_head *, + struct node_rec *)) +{ + return __iscsi_login_portals(data, nr_found, wait, rec_list, + 1, login_fn); +} + +/** + * iscsi_login_portals_safe - login into portals on @rec_list, but do not + * clear out rec_list. + */ +int iscsi_login_portals_safe(void *data, int *nr_found, int wait, + struct list_head *rec_list, + int (*login_fn)(void *, struct list_head *, + struct node_rec *)) +{ + return __iscsi_login_portals(data, nr_found, wait, rec_list, + 0, login_fn); +} + +static void log_logout_msg(struct session_info *info, int rc) +{ + if (rc) { + log_error("Could not logout of [sid: %d, target: %s, " + "portal: %s,%d].", info->sid, + info->targetname, + info->persistent_address, info->port); + iscsi_err_print_msg(rc); + } else + log_info("Logout of [sid: %d, target: %s, " + "portal: %s,%d] successful.", + info->sid, info->targetname, + info->persistent_address, info->port); +} + +static int iscsid_logout_reqs_wait(struct list_head *list) +{ + struct iscsid_async_req *tmp, *curr; + struct session_info *info; + int ret = 0; + + list_for_each_entry_safe(curr, tmp, list, list) { + int err; + + info = curr->data; + err = iscsid_req_wait(MGMT_IPC_SESSION_LOGOUT, curr->fd); + log_logout_msg(info, err); + if (err) + ret = err; + list_del(&curr->list); + free(curr); + } + return ret; +} + +/** + * iscsi_logout_portal - logout of portal + * @info: session to log out of + * @list: if async, this is the list to add the logout req to + */ +int iscsi_logout_portal(struct session_info *info, struct list_head *list) +{ + struct iscsid_async_req *async_req = NULL; + int fd, rc; + + /* TODO: add fn to add session prefix info like dev_printk */ + log_info("Logging out of session [sid: %d, target: %s, portal: " + "%s,%d]", + info->sid, info->targetname, info->persistent_address, + info->port); + + if (list) { + async_req = calloc(1, sizeof(*async_req)); + if (!async_req) + log_info("Could not allocate memory for async logout " + "handling. Using sequential logout instead."); + } + + if (!async_req) + rc = iscsid_req_by_sid(MGMT_IPC_SESSION_LOGOUT, info->sid); + else { + INIT_LIST_HEAD(&async_req->list); + rc = iscsid_req_by_sid_async(MGMT_IPC_SESSION_LOGOUT, + info->sid, &fd); + } + + /* we raced with another app or instance of iscsiadm */ + if (rc) { + log_logout_msg(info, rc); + if (async_req) + free(async_req); + return rc; + } + + if (async_req) { + list_add_tail(&async_req->list, list); + async_req->fd = fd; + async_req->data = info; + } else + log_logout_msg(info, rc); + + return 0; +} + +/** + * iscsi_logout_portals - logout portals + * @data: data to pass to iter logout_fn + * @nr_found: number of sessions logged out + * @wait: bool indicating if the fn should wait for the result + * @logout_fn: logout iter function + * + * This will loop over the list of sessions and run the logout fn + * on them. It will attempt to logout asynchronously, and then wait for + * them to complete if wait is set. + */ +int iscsi_logout_portals(void *data, int *nr_found, int wait, + int (*logout_fn)(void *, struct list_head *, + struct session_info *)) +{ + struct session_info *curr_info; + struct session_link_info link_info; + struct list_head session_list, logout_list; + int ret = 0, err; + + INIT_LIST_HEAD(&session_list); + INIT_LIST_HEAD(&logout_list); + + memset(&link_info, 0, sizeof(link_info)); + link_info.list = &session_list; + link_info.data = NULL; + link_info.match_fn = NULL; + *nr_found = 0; + + err = iscsi_sysfs_for_each_session(&link_info, nr_found, + session_info_create_list, 0); + if (err && !list_empty(&session_list)) + log_error("Could not read in all sessions: %s", + iscsi_err_to_str(err)); + else if (err && list_empty(&session_list)) { + log_error("Could not read session info."); + return err; + } else if (list_empty(&session_list)) + return ISCSI_ERR_NO_OBJS_FOUND; + ret = err; + *nr_found = 0; + + list_for_each_entry(curr_info, &session_list, list) { + err = logout_fn(data, &logout_list, curr_info); + if (err > 0 && !ret) + ret = err; + if (!err) + (*nr_found)++; + } + + if (!*nr_found) { + ret = ISCSI_ERR_NO_OBJS_FOUND; + goto free_list; + } + + if (wait) { + err = iscsid_logout_reqs_wait(&logout_list); + if (err && !ret) + ret = err; + } else + iscsid_reqs_close(&logout_list); + + if (ret) + log_error("Could not logout of all requested sessions"); + +free_list: + session_info_free_list(&session_list); + return ret; +} + +/* TODO merge with initiator.c implementation */ +/* And add locking */ +int iscsi_check_for_running_session(struct node_rec *rec) +{ + int nr_found = 0; + if (iscsi_sysfs_for_each_session(rec, &nr_found, iscsi_match_session, + 0)) + return 1; + return 0; +} diff --git a/usr/session_mgmt.h b/usr/session_mgmt.h new file mode 100644 index 0000000..a28bfa4 --- /dev/null +++ b/usr/session_mgmt.h @@ -0,0 +1,26 @@ +#ifndef _SESSION_MGMT_H_ +#define _SESSION_MGMT_H_ + +struct node_rec; +struct list_head; +struct session_info; + +extern int iscsi_login_portal(void *data, struct list_head *list, + struct node_rec *rec); +extern int iscsi_login_portal_nowait(struct node_rec *rec); +extern int iscsi_login_portals(void *data, int *nr_found, int wait, + struct list_head *rec_list, + int (*login_fn)(void *, struct list_head *, + struct node_rec *)); +extern int iscsi_login_portals_safe(void *data, int *nr_found, int wait, + struct list_head *rec_list, + int (*login_fn)(void *, struct list_head *, + struct node_rec *)); +extern int iscsi_logout_portal(struct session_info *info, + struct list_head *list); +extern int iscsi_logout_portals(void *data, int *nr_found, int wait, + int (*logout_fn)(void *, struct list_head *, + struct session_info *)); +extern int iscsi_check_for_running_session(struct node_rec *rec); + +#endif diff --git a/usr/statics.c b/usr/statics.c new file mode 100644 index 0000000..59fb044 --- /dev/null +++ b/usr/statics.c @@ -0,0 +1,20 @@ +#include +#include +#include +#include + +static struct passwd root_pw = { + .pw_name = "root", +}; + +struct passwd* +getpwuid(uid_t uid) +{ + if (uid == 0) + return &root_pw; + else { + errno = ENOENT; + return 0; + } +} + diff --git a/usr/sysfs.c b/usr/sysfs.c new file mode 100644 index 0000000..2488160 --- /dev/null +++ b/usr/sysfs.c @@ -0,0 +1,720 @@ +/* + * This is from udev-121's udev_sysfs.c + * + * Copyright (C) 2005-2006 Kay Sievers + * + * 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 version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "sysdeps.h" +#include "sysfs.h" + +/* + * We take this file from udev so we want to make as few changes are possible, + * so we can maintain patches. + * + * This is from udev_utils_string.c + */ +static void remove_trailing_chars(char *path, char c) +{ + size_t len; + + len = strlen(path); + while (len > 0 && path[len-1] == c) + path[--len] = '\0'; +} + +/* this converts udevs logging to ours. */ +#define dbg(format, arg...) \ + do { \ + log_debug(3, "%s: " format, __FUNCTION__,## arg); \ + } while (0) + +/* + * Begin udev_sysfs.c code + */ +char sysfs_path[PATH_SIZE]; + +/* device cache */ +static LIST_HEAD(dev_list); + +int sysfs_init(void) +{ + const char *env; + + env = getenv("SYSFS_PATH"); + if (env) { + strlcpy(sysfs_path, env, sizeof(sysfs_path)); + remove_trailing_chars(sysfs_path, '/'); + } else + strlcpy(sysfs_path, "/sys", sizeof(sysfs_path)); + dbg("sysfs_path='%s'", sysfs_path); + + INIT_LIST_HEAD(&dev_list); + return 0; +} + +void sysfs_cleanup(void) +{ + struct sysfs_device *dev_loop; + struct sysfs_device *dev_temp; + + list_for_each_entry_safe(dev_loop, dev_temp, &dev_list, node) { + list_del_init(&dev_loop->node); + free(dev_loop); + } +} + +void sysfs_device_set_values(struct sysfs_device *dev, const char *devpath, + const char *subsystem, const char *driver) +{ + char *pos; + + strlcpy(dev->devpath, devpath, sizeof(dev->devpath)); + if (subsystem != NULL) + strlcpy(dev->subsystem, subsystem, sizeof(dev->subsystem)); + if (driver != NULL) + strlcpy(dev->driver, driver, sizeof(dev->driver)); + + /* set kernel name */ + pos = strrchr(dev->devpath, '/'); + if (pos == NULL) + return; + strlcpy(dev->kernel, &pos[1], sizeof(dev->kernel)); + dbg("kernel='%s'", dev->kernel); + + /* some devices have '!' in their name, change that to '/' */ + pos = dev->kernel; + while (pos[0] != '\0') { + if (pos[0] == '!') + pos[0] = '/'; + pos++; + } + + /* get kernel number */ + pos = &dev->kernel[strlen(dev->kernel)]; + while (isdigit(pos[-1])) + pos--; + strlcpy(dev->kernel_number, pos, sizeof(dev->kernel_number)); + dbg("kernel_number='%s'", dev->kernel_number); +} + +int sysfs_resolve_link(char *devpath, size_t size) +{ + char link_path[PATH_SIZE]; + char link_target[PATH_SIZE]; + int len; + int i; + int back; + + strlcpy(link_path, sysfs_path, sizeof(link_path)); + strlcat(link_path, devpath, sizeof(link_path)); + len = readlink(link_path, link_target, sizeof(link_target) - 1); + if (len <= 0) + return -1; + link_target[len] = '\0'; + dbg("path link '%s' points to '%s'", devpath, link_target); + + for (back = 0; strncmp(&link_target[back * 3], "../", 3) == 0; back++) + ; + dbg("base '%s', tail '%s', back %i", devpath, &link_target[back * 3], back); + for (i = 0; i <= back; i++) { + char *pos = strrchr(devpath, '/'); + + if (pos == NULL) + return -1; + pos[0] = '\0'; + } + dbg("after moving back '%s'", devpath); + strlcat(devpath, "/", size); + strlcat(devpath, &link_target[back * 3], size); + return 0; +} + +struct sysfs_device *sysfs_device_get(const char *devpath) +{ + char path[PATH_SIZE]; + char devpath_real[PATH_SIZE]; + struct sysfs_device *dev; + struct sysfs_device *dev_loop; + struct stat statbuf; + char link_path[PATH_SIZE]; + char link_target[PATH_SIZE]; + int len; + char *pos; + + /* we handle only these devpathes */ + if (devpath != NULL && + strncmp(devpath, "/devices/", 9) != 0 && + strncmp(devpath, "/subsystem/", 11) != 0 && + strncmp(devpath, "/module/", 8) != 0 && + strncmp(devpath, "/bus/", 5) != 0 && + strncmp(devpath, "/class/", 7) != 0 && + strncmp(devpath, "/block/", 7) != 0) + return NULL; + + dbg("open '%s'", devpath); + strlcpy(devpath_real, devpath, sizeof(devpath_real)); + remove_trailing_chars(devpath_real, '/'); + if (devpath[0] == '\0' ) + return NULL; + + /* look for device already in cache (we never put an untranslated path in the cache) */ + list_for_each_entry(dev_loop, &dev_list, node) { + if (strcmp(dev_loop->devpath, devpath_real) == 0) { + dbg("found in cache '%s'", dev_loop->devpath); + return dev_loop; + } + } + + /* if we got a link, resolve it to the real device */ + strlcpy(path, sysfs_path, sizeof(path)); + strlcat(path, devpath_real, sizeof(path)); + if (lstat(path, &statbuf) != 0) { + dbg("stat '%s' failed: %s", path, strerror(errno)); + return NULL; + } + if (S_ISLNK(statbuf.st_mode)) { + if (sysfs_resolve_link(devpath_real, sizeof(devpath_real)) != 0) + return NULL; + + /* now look for device in cache after path translation */ + list_for_each_entry(dev_loop, &dev_list, node) { + if (strcmp(dev_loop->devpath, devpath_real) == 0) { + dbg("found in cache '%s'", dev_loop->devpath); + return dev_loop; + } + } + } + + /* it is a new device */ + dbg("new uncached device '%s'", devpath_real); + dev = malloc(sizeof(struct sysfs_device)); + if (dev == NULL) + return NULL; + memset(dev, 0x00, sizeof(struct sysfs_device)); + + sysfs_device_set_values(dev, devpath_real, NULL, NULL); + + /* get subsystem name */ + strlcpy(link_path, sysfs_path, sizeof(link_path)); + strlcat(link_path, dev->devpath, sizeof(link_path)); + strlcat(link_path, "/subsystem", sizeof(link_path)); + len = readlink(link_path, link_target, sizeof(link_target) - 1); + if (len > 0) { + /* get subsystem from "subsystem" link */ + link_target[len] = '\0'; + dbg("subsystem link '%s' points to '%s'", link_path, link_target); + pos = strrchr(link_target, '/'); + if (pos != NULL) + strlcpy(dev->subsystem, &pos[1], sizeof(dev->subsystem)); + } else if (strstr(dev->devpath, "/drivers/") != NULL) { + strlcpy(dev->subsystem, "drivers", sizeof(dev->subsystem)); + } else if (strncmp(dev->devpath, "/module/", 8) == 0) { + strlcpy(dev->subsystem, "module", sizeof(dev->subsystem)); + } else if (strncmp(dev->devpath, "/subsystem/", 11) == 0) { + pos = strrchr(dev->devpath, '/'); + if (pos == &dev->devpath[10]) + strlcpy(dev->subsystem, "subsystem", sizeof(dev->subsystem)); + } else if (strncmp(dev->devpath, "/class/", 7) == 0) { + pos = strrchr(dev->devpath, '/'); + if (pos == &dev->devpath[6]) + strlcpy(dev->subsystem, "subsystem", sizeof(dev->subsystem)); + } else if (strncmp(dev->devpath, "/bus/", 5) == 0) { + pos = strrchr(dev->devpath, '/'); + if (pos == &dev->devpath[4]) + strlcpy(dev->subsystem, "subsystem", sizeof(dev->subsystem)); + } + + /* get driver name */ + strlcpy(link_path, sysfs_path, sizeof(link_path)); + strlcat(link_path, dev->devpath, sizeof(link_path)); + strlcat(link_path, "/driver", sizeof(link_path)); + len = readlink(link_path, link_target, sizeof(link_target) - 1); + if (len > 0) { + link_target[len] = '\0'; + dbg("driver link '%s' points to '%s'", link_path, link_target); + pos = strrchr(link_target, '/'); + if (pos != NULL) + strlcpy(dev->driver, &pos[1], sizeof(dev->driver)); + } + + dbg("add to cache 'devpath=%s', subsystem='%s', driver='%s'", dev->devpath, dev->subsystem, dev->driver); + list_add(&dev->node, &dev_list); + + return dev; +} + +struct sysfs_device *sysfs_device_get_parent(struct sysfs_device *dev) +{ + char parent_devpath[PATH_SIZE]; + char *pos; + + dbg("open '%s'", dev->devpath); + + /* look if we already know the parent */ + if (dev->parent != NULL) + return dev->parent; + + strlcpy(parent_devpath, dev->devpath, sizeof(parent_devpath)); + dbg("'%s'", parent_devpath); + + /* strip last element */ + pos = strrchr(parent_devpath, '/'); + if (pos == NULL || pos == parent_devpath) + return NULL; + pos[0] = '\0'; + + if (strncmp(parent_devpath, "/class", 6) == 0) { + pos = strrchr(parent_devpath, '/'); + if (pos == &parent_devpath[6] || pos == parent_devpath) { + dbg("/class top level, look for device link"); + goto device_link; + } + } + if (strcmp(parent_devpath, "/block") == 0) { + dbg("/block top level, look for device link"); + goto device_link; + } + + /* are we at the top level? */ + pos = strrchr(parent_devpath, '/'); + if (pos == NULL || pos == parent_devpath) + return NULL; + + /* get parent and remember it */ + dev->parent = sysfs_device_get(parent_devpath); + return dev->parent; + +device_link: + strlcpy(parent_devpath, dev->devpath, sizeof(parent_devpath)); + strlcat(parent_devpath, "/device", sizeof(parent_devpath)); + if (sysfs_resolve_link(parent_devpath, sizeof(parent_devpath)) != 0) + return NULL; + + /* get parent and remember it */ + dev->parent = sysfs_device_get(parent_devpath); + return dev->parent; +} + +struct sysfs_device *sysfs_device_get_parent_with_subsystem(struct sysfs_device *dev, const char *subsystem) +{ + struct sysfs_device *dev_parent; + + dev_parent = sysfs_device_get_parent(dev); + while (dev_parent != NULL) { + if (strcmp(dev_parent->subsystem, subsystem) == 0) + return dev_parent; + dev_parent = sysfs_device_get_parent(dev_parent); + } + return NULL; +} + +char *sysfs_attr_get_value(const char *devpath, const char *attr_name) +{ + char path_full[PATH_SIZE]; + char value[NAME_SIZE] = { '\0', }; + struct stat statbuf; + int fd; + ssize_t size; + size_t sysfs_len; + + dbg("open '%s'/'%s'", devpath, attr_name); + sysfs_len = strlcpy(path_full, sysfs_path, sizeof(path_full)); + if(sysfs_len >= sizeof(path_full)) + sysfs_len = sizeof(path_full) - 1; + strlcat(path_full, devpath, sizeof(path_full)); + strlcat(path_full, "/", sizeof(path_full)); + strlcat(path_full, attr_name, sizeof(path_full)); + + if (lstat(path_full, &statbuf) != 0) { + dbg("stat '%s' failed: %s", path_full, strerror(errno)); + goto out; + } + + if (S_ISLNK(statbuf.st_mode)) { + /* links return the last element of the target path */ + char link_target[PATH_SIZE]; + int len; + const char *pos; + + len = readlink(path_full, link_target, sizeof(link_target) - 1); + if (len > 0) { + link_target[len] = '\0'; + pos = strrchr(link_target, '/'); + if (pos != NULL) { + dbg("cache '%s' with link value '%s'", path_full, value); + strlcpy(value, &pos[1], NAME_SIZE); + } + } + goto out; + } + + /* skip directories */ + if (S_ISDIR(statbuf.st_mode)) + goto out; + + /* skip non-readable files */ + if ((statbuf.st_mode & S_IRUSR) == 0) + goto out; + + /* read attribute value */ + fd = open(path_full, O_RDONLY); + if (fd < 0) { + dbg("attribute '%s' can not be opened", path_full); + goto out; + } + size = read(fd, value, sizeof(value)); + close(fd); + if (size < 0) + goto out; + if (size == sizeof(value)) + goto out; + + /* got a valid value, store and return it */ + value[size] = '\0'; + remove_trailing_chars(value, '\n'); + +out: + if (value[0] == '\0') + return NULL; + return strdup(value); +} + +int sysfs_lookup_devpath_by_subsys_id(char *devpath_full, size_t len, const char *subsystem, const char *id) +{ + size_t sysfs_len; + char path_full[PATH_SIZE]; + char *path; + struct stat statbuf; + + sysfs_len = strlcpy(path_full, sysfs_path, sizeof(path_full)); + path = &path_full[sysfs_len]; + + if (strcmp(subsystem, "subsystem") == 0) { + strlcpy(path, "/subsystem/", sizeof(path_full) - sysfs_len); + strlcat(path, id, sizeof(path_full) - sysfs_len); + if (stat(path_full, &statbuf) == 0) + goto found; + + strlcpy(path, "/bus/", sizeof(path_full) - sysfs_len); + strlcat(path, id, sizeof(path_full) - sysfs_len); + if (stat(path_full, &statbuf) == 0) + goto found; + goto out; + + strlcpy(path, "/class/", sizeof(path_full) - sysfs_len); + strlcat(path, id, sizeof(path_full) - sysfs_len); + if (stat(path_full, &statbuf) == 0) + goto found; + } + + if (strcmp(subsystem, "module") == 0) { + strlcpy(path, "/module/", sizeof(path_full) - sysfs_len); + strlcat(path, id, sizeof(path_full) - sysfs_len); + if (stat(path_full, &statbuf) == 0) + goto found; + goto out; + } + + if (strcmp(subsystem, "drivers") == 0) { + char subsys[NAME_SIZE]; + char *driver; + + strlcpy(subsys, id, sizeof(subsys)); + driver = strchr(subsys, ':'); + if (driver != NULL) { + driver[0] = '\0'; + driver = &driver[1]; + strlcpy(path, "/subsystem/", sizeof(path_full) - sysfs_len); + strlcat(path, subsys, sizeof(path_full) - sysfs_len); + strlcat(path, "/drivers/", sizeof(path_full) - sysfs_len); + strlcat(path, driver, sizeof(path_full) - sysfs_len); + if (stat(path_full, &statbuf) == 0) + goto found; + + strlcpy(path, "/bus/", sizeof(path_full) - sysfs_len); + strlcat(path, subsys, sizeof(path_full) - sysfs_len); + strlcat(path, "/drivers/", sizeof(path_full) - sysfs_len); + strlcat(path, driver, sizeof(path_full) - sysfs_len); + if (stat(path_full, &statbuf) == 0) + goto found; + } + goto out; + } + + strlcpy(path, "/subsystem/", sizeof(path_full) - sysfs_len); + strlcat(path, subsystem, sizeof(path_full) - sysfs_len); + strlcat(path, "/devices/", sizeof(path_full) - sysfs_len); + strlcat(path, id, sizeof(path_full) - sysfs_len); + if (stat(path_full, &statbuf) == 0) + goto found; + + strlcpy(path, "/bus/", sizeof(path_full) - sysfs_len); + strlcat(path, subsystem, sizeof(path_full) - sysfs_len); + strlcat(path, "/devices/", sizeof(path_full) - sysfs_len); + strlcat(path, id, sizeof(path_full) - sysfs_len); + if (stat(path_full, &statbuf) == 0) + goto found; + + strlcpy(path, "/class/", sizeof(path_full) - sysfs_len); + strlcat(path, subsystem, sizeof(path_full) - sysfs_len); + strlcat(path, "/", sizeof(path_full) - sysfs_len); + strlcat(path, id, sizeof(path_full) - sysfs_len); + if (stat(path_full, &statbuf) == 0) + goto found; + + strlcpy(path, "/firmware/", sizeof(path_full) - sysfs_len); + strlcat(path, subsystem, sizeof(path_full) - sysfs_len); + strlcat(path, "/", sizeof(path_full) - sysfs_len); + strlcat(path, id, sizeof(path_full) - sysfs_len); + if (stat(path_full, &statbuf) == 0) + goto found; + +out: + return 0; +found: + if (S_ISLNK(statbuf.st_mode)) + sysfs_resolve_link(path, sizeof(path_full) - sysfs_len); + strlcpy(devpath_full, path, len); + return 1; +} + + +char *sysfs_get_value(const char *id, char *subsys, char *param) +{ + char devpath[PATH_SIZE]; + char *sysfs_value; + + if (!sysfs_lookup_devpath_by_subsys_id(devpath, sizeof(devpath), + subsys, id)) { + log_debug(3, "Could not lookup devpath for %s %s", + subsys, id); + return NULL; + } + + sysfs_value = sysfs_attr_get_value(devpath, param); + if (!sysfs_value) { + log_debug(3, "Could not read attr %s on path %s", + param, devpath); + return NULL; + } + + if (!strncmp(sysfs_value, "", 6) || + !strncmp(sysfs_value, "(null)", 6)) { + free(sysfs_value); + return NULL; + } + + return sysfs_value; +} + +int sysfs_get_uint(char *id, char *subsys, char *param, + unsigned int *value) +{ + char *sysfs_value; + + *value = -1; + sysfs_value = sysfs_get_value(id, subsys, param); + if (!sysfs_value) + return EIO; + + errno = 0; + *value = strtoul(sysfs_value, NULL, 0); + free(sysfs_value); + if (errno) + return errno; + return 0; +} + +int sysfs_get_int(const char *id, char *subsys, char *param, int *value) +{ + char *sysfs_value; + + *value = -1; + sysfs_value = sysfs_get_value(id, subsys, param); + if (!sysfs_value) + return EIO; + + *value = atoi(sysfs_value); + free(sysfs_value); + return 0; +} + +int sysfs_get_str(char *id, char *subsys, char *param, char *value, + int value_size) +{ + char *sysfs_value; + int len; + + value[0] = '\0'; + sysfs_value = sysfs_get_value(id, subsys, param); + if (!sysfs_value || !strlen(sysfs_value)) + return EIO; + + len = strlen(sysfs_value); + if (len && (sysfs_value[len - 1] == '\n')) + sysfs_value[len - 1] = '\0'; + strncpy(value, sysfs_value, value_size); + value[value_size - 1] = '\0'; + free(sysfs_value); + return 0; +} + +int sysfs_get_uint64(char *id, char *subsys, char *param, uint64_t *value) +{ + char *sysfs_value; + + *value = -1; + sysfs_value = sysfs_get_value(id, subsys, param); + if (!sysfs_value) + return EIO; + + if (sscanf(sysfs_value, "%" PRIu64 "\n", value) != 1) { + free(sysfs_value); + return EINVAL; + } + free(sysfs_value); + return 0; +} + +int sysfs_get_uint8(char *id, char *subsys, char *param, + uint8_t *value) +{ + char *sysfs_value; + + *value = -1; + sysfs_value = sysfs_get_value(id, subsys, param); + if (!sysfs_value) + return EIO; + + *value = (uint8_t)atoi(sysfs_value); + free(sysfs_value); + return 0; +} + +int sysfs_get_uint16(char *id, char *subsys, char *param, + uint16_t *value) +{ + char *sysfs_value; + + *value = -1; + sysfs_value = sysfs_get_value(id, subsys, param); + if (!sysfs_value) + return EIO; + + *value = (uint16_t)atoi(sysfs_value); + free(sysfs_value); + return 0; +} + +int sysfs_set_param(char *id, char *subsys, char *attr_name, + char *write_buf, ssize_t buf_size) +{ + struct stat statbuf; + char devpath[PATH_SIZE]; + size_t sysfs_len; + char path_full[PATH_SIZE]; + int rc = 0, fd; + + if (!sysfs_lookup_devpath_by_subsys_id(devpath, sizeof(devpath), + subsys, id)) { + log_debug(3, "Could not lookup devpath for %s %s", + subsys, id); + return EIO; + } + + sysfs_len = strlcpy(path_full, sysfs_path, sizeof(path_full)); + if(sysfs_len >= sizeof(path_full)) + sysfs_len = sizeof(path_full) - 1; + strlcat(path_full, devpath, sizeof(path_full)); + strlcat(path_full, "/", sizeof(path_full)); + strlcat(path_full, attr_name, sizeof(path_full)); + + if (lstat(path_full, &statbuf)) { + log_debug(3, "Could not stat %s", path_full); + return errno; + } + + if ((statbuf.st_mode & S_IWUSR) == 0) { + log_error("Could not write to %s. Invalid permissions.", + path_full); + return EACCES; + } + + fd = open(path_full, O_WRONLY); + if (fd < 0) { + log_error("Could not open %s err %d", path_full, errno); + return errno; + } + + if (write(fd, write_buf, buf_size) == -1) + rc = errno; + close(fd); + return rc; +} + +char *sysfs_get_uevent_field(const char *path, const char *field) +{ + char *uevent_path = NULL; + FILE *f = NULL; + char *line, buffer[1024]; + char *ff, *d; + char *out = NULL; + + uevent_path = calloc(1, PATH_MAX); + if (!uevent_path) + return NULL; + snprintf(uevent_path, PATH_MAX, "%s/uevent", path); + + f = fopen(uevent_path, "r"); + if (!f) + goto out; + while ((line = fgets(buffer, sizeof (buffer), f))) { + ff = strtok(line, "="); + d = strtok(NULL, "\n"); + if (strcmp(ff, field)) + continue; + out = strdup(d); + break; + } + fclose(f); +out: + free(uevent_path); + return out; +} + +char *sysfs_get_uevent_devtype(const char *path) +{ + return sysfs_get_uevent_field(path, "DEVTYPE"); +} + +char *sysfs_get_uevent_devname(const char *path) +{ + return sysfs_get_uevent_field(path, "DEVNAME"); +} diff --git a/usr/sysfs.h b/usr/sysfs.h new file mode 100644 index 0000000..462060e --- /dev/null +++ b/usr/sysfs.h @@ -0,0 +1,73 @@ +/* + * This is from udev-121 udev.h + * + * Copyright (C) 2003 Greg Kroah-Hartman + * Copyright (C) 2003-2006 Kay Sievers + * + * 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 version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef _SYSFS_ +#define _SYSFS_ + +#include +#include "list.h" +#include "string.h" + +#define PATH_SIZE 512 +#define NAME_SIZE 256 + +struct sysfs_device { + struct list_head node; /* for device cache */ + struct sysfs_device *parent; /* already cached parent*/ + char devpath[PATH_SIZE]; + char subsystem[NAME_SIZE]; /* $class, $bus, drivers, module */ + char kernel[NAME_SIZE]; /* device instance name */ + char kernel_number[NAME_SIZE]; + char driver[NAME_SIZE]; /* device driver name */ +}; + +extern char sysfs_path[PATH_SIZE]; +extern int sysfs_init(void); +extern void sysfs_cleanup(void); +extern void sysfs_device_set_values(struct sysfs_device *dev, const char *devpath, + const char *subsystem, const char *driver); +extern struct sysfs_device *sysfs_device_get(const char *devpath); +extern struct sysfs_device *sysfs_device_get_parent(struct sysfs_device *dev); +extern struct sysfs_device *sysfs_device_get_parent_with_subsystem(struct sysfs_device *dev, const char *subsystem); +extern char *sysfs_attr_get_value(const char *devpath, const char *attr_name); +extern int sysfs_resolve_link(char *path, size_t size); +extern int sysfs_lookup_devpath_by_subsys_id(char *devpath, size_t len, const char *subsystem, const char *id); + +extern char *sysfs_get_value(const char *id, char *subsys, char *param); +extern int sysfs_get_uint(char *id, char *subsys, char *param, + unsigned int *value); +extern int sysfs_get_int(const char *id, char *subsys, char *param, int *value); +extern int sysfs_get_str(char *id, char *subsys, char *param, char *value, + int value_size); +extern int sysfs_get_uint64(char *id, char *subsys, char *param, + uint64_t *value); +extern int sysfs_get_uint8(char *id, char *subsys, char *param, + uint8_t *value); +extern int sysfs_get_uint16(char *id, char *subsys, char *param, + uint16_t *value); +extern int sysfs_set_param(char *id, char *subsys, char *attr_name, + char *write_buf, ssize_t buf_size); + +extern char *sysfs_get_uevent_field(const char *path, const char *field); +extern char *sysfs_get_uevent_devtype(const char *path); +extern char *sysfs_get_uevent_devname(const char *path); + +#endif diff --git a/usr/transport.c b/usr/transport.c new file mode 100644 index 0000000..4004582 --- /dev/null +++ b/usr/transport.c @@ -0,0 +1,256 @@ +/* + * iSCSI transport + * + * Copyright (C) 2006 Mike Christie + * Copyright (C) 2006 Red Hat, Inc. 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 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sysdeps.h" +#include "iscsi_err.h" +#include "initiator.h" +#include "transport.h" +#include "log.h" +#include "iscsi_util.h" +#include "iscsi_sysfs.h" +#include "uip_mgmt_ipc.h" +#include "cxgbi.h" +#include "be2iscsi.h" +#include "iser.h" + +struct iscsi_transport_template iscsi_tcp = { + .name = "tcp", + .ep_connect = iscsi_io_tcp_connect, + .ep_poll = iscsi_io_tcp_poll, + .ep_disconnect = iscsi_io_tcp_disconnect, +}; + +struct iscsi_transport_template iscsi_iser = { + .name = "iser", + .rdma = 1, + .ep_connect = ktransport_ep_connect, + .ep_poll = ktransport_ep_poll, + .ep_disconnect = ktransport_ep_disconnect, + .create_conn = iser_create_conn, +}; + +struct iscsi_transport_template cxgb3i = { + .name = "cxgb3i", + .set_host_ip = SET_HOST_IP_OPT, + .bind_ep_required = 1, + .ep_connect = ktransport_ep_connect, + .ep_poll = ktransport_ep_poll, + .ep_disconnect = ktransport_ep_disconnect, + .create_conn = cxgbi_create_conn, +}; + +struct iscsi_transport_template cxgb4i = { + .name = "cxgb4i", + .set_host_ip = SET_HOST_IP_NOT_REQ, + .bind_ep_required = 1, + .ep_connect = ktransport_ep_connect, + .ep_poll = ktransport_ep_poll, + .ep_disconnect = ktransport_ep_disconnect, + .create_conn = cxgbi_create_conn, +}; + +struct iscsi_transport_template bnx2i = { + .name = "bnx2i", + .set_host_ip = SET_HOST_IP_REQ, + .use_boot_info = 1, + .bind_ep_required = 1, + .ep_connect = ktransport_ep_connect, + .ep_poll = ktransport_ep_poll, + .ep_disconnect = ktransport_ep_disconnect, + .set_net_config = uip_broadcast_params, + .exec_ping = uip_broadcast_ping_req, +}; + +struct iscsi_transport_template be2iscsi = { + .name = "be2iscsi", + .bind_ep_required = 1, + .sync_vlan_settings = 1, + .create_conn = be2iscsi_create_conn, + .ep_connect = ktransport_ep_connect, + .ep_poll = ktransport_ep_poll, + .ep_disconnect = ktransport_ep_disconnect, +}; + +struct iscsi_transport_template qla4xxx = { + .name = "qla4xxx", + .set_host_ip = SET_HOST_IP_NOT_REQ, + .bind_ep_required = 1, + .ep_connect = ktransport_ep_connect, + .ep_poll = ktransport_ep_poll, + .ep_disconnect = ktransport_ep_disconnect, +}; + +struct iscsi_transport_template ocs = { + .name = "ocs", + .bind_ep_required = 1, + .ep_connect = ktransport_ep_connect, + .ep_poll = ktransport_ep_poll, + .ep_disconnect = ktransport_ep_disconnect, +}; + +struct iscsi_transport_template qedi = { + .name = "qedi", + .set_host_ip = SET_HOST_IP_REQ, + .use_boot_info = 1, + .bind_ep_required = 1, + .no_netdev = 1, + .ep_connect = ktransport_ep_connect, + .ep_poll = ktransport_ep_poll, + .ep_disconnect = ktransport_ep_disconnect, + .set_net_config = uip_broadcast_params, + .exec_ping = uip_broadcast_ping_req, +}; + +static struct iscsi_transport_template *iscsi_transport_templates[] = { + &iscsi_tcp, + &iscsi_iser, + &cxgb3i, + &cxgb4i, + &bnx2i, + &qla4xxx, + &be2iscsi, + &ocs, + &qedi, + NULL +}; + +int transport_probe_for_offload(void) +{ + struct if_nameindex *ifni; + char transport_name[ISCSI_TRANSPORT_NAME_MAXLEN]; + int i, sockfd; + struct ifreq if_hwaddr; + + ifni = if_nameindex(); + if (!ifni) { + log_error("Could not search for transport modules: %s", + strerror(errno)); + return ISCSI_ERR_TRANS_NOT_FOUND; + } + + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + log_error("Could not open socket for ioctl: %s", + strerror(errno)); + goto free_ifni; + } + + for (i = 0; ifni[i].if_index && ifni[i].if_name; i++) { + struct if_nameindex *n = &ifni[i]; + + log_debug(6, "kmod probe found %s", n->if_name); + + strlcpy(if_hwaddr.ifr_name, n->if_name, IFNAMSIZ); + if (ioctl(sockfd, SIOCGIFHWADDR, &if_hwaddr) < 0) + continue; + + /* check for ARPHRD_ETHER (ethernet) */ + if (if_hwaddr.ifr_hwaddr.sa_family != 1) + continue; + + if (net_get_transport_name_from_netdev(n->if_name, + transport_name)) + continue; + + transport_load_kmod(transport_name); + } + close(sockfd); + +free_ifni: + if_freenameindex(ifni); + return 0; +} + +/* + * Most distros still do not have wide libkmod use, so + * use modprobe for now + */ +int transport_load_kmod(char *transport_name) +{ + struct kmod_ctx *ctx; + struct kmod_module *mod; + int rc; + + ctx = kmod_new(NULL, NULL); + if (!ctx) { + log_error("Could not load transport module %s. Out of " + "memory.", transport_name); + return ISCSI_ERR_NOMEM; + } + + kmod_load_resources(ctx); + + /* + * dumb dumb dumb - named iscsi_tcp and ib_iser differently from + * transport name + */ + if (!strcmp(transport_name, "tcp")) + rc = kmod_module_new_from_name(ctx, "iscsi_tcp", &mod); + else if (!strcmp(transport_name, "iser")) + rc = kmod_module_new_from_name(ctx, "ib_iser", &mod); + else + rc = kmod_module_new_from_name(ctx, transport_name, &mod); + if (rc) { + log_error("Failed to load module %s.", transport_name); + rc = ISCSI_ERR_TRANS_NOT_FOUND; + goto unref_mod; + } + + rc = kmod_module_probe_insert_module(mod, KMOD_PROBE_APPLY_BLACKLIST, + NULL, NULL, NULL, NULL); + if (rc) { + log_error("Could not insert module %s. Kmod error %d", + transport_name, rc); + rc = ISCSI_ERR_TRANS_NOT_FOUND; + } + kmod_module_unref(mod); + +unref_mod: + kmod_unref(ctx); + return rc; +} + +int set_transport_template(struct iscsi_transport *t) +{ + struct iscsi_transport_template *tmpl; + int j; + + for (j = 0; iscsi_transport_templates[j] != NULL; j++) { + tmpl = iscsi_transport_templates[j]; + + if (!strcmp(tmpl->name, t->name)) { + t->template = tmpl; + log_debug(3, "Matched transport %s", t->name); + return 0; + } + } + + log_error("Could not find template for %s. An updated iscsiadm " + "is probably needed.", t->name); + return ENOSYS; +} diff --git a/usr/transport.h b/usr/transport.h new file mode 100644 index 0000000..0702756 --- /dev/null +++ b/usr/transport.h @@ -0,0 +1,72 @@ +/* + * iSCSI transport + * + * Copyright (C) 2006 Mike Christie + * Copyright (C) 2006 Red Hat, Inc. 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 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. + */ +#ifndef ISCSI_TRANSPORT_H +#define ISCSI_TRANSPORT_H + +#include "types.h" +#include "config.h" + +enum set_host_ip_opts { + SET_HOST_IP_NOT_REQ, /* iface.ipaddress is not supported */ + SET_HOST_IP_REQ, /* iface.ipaddress must be specified */ + SET_HOST_IP_OPT, /* iface.ipaddress is not required */ +}; + +struct iscsi_transport; +struct iscsi_conn; + +struct iscsi_transport_template { + const char *name; + uint8_t rdma; + /* + * Drivers should set this if they require iscsid to set + * the host's ip address. + */ + uint8_t set_host_ip; + uint8_t use_boot_info; + uint8_t bind_ep_required; + uint8_t no_netdev; + /* be2iscsi has a single host vlan setting, + * but uses 2 ifaces for ipv4 and ipv6 settings; keep them in sync */ + uint8_t sync_vlan_settings; + int (*ep_connect) (struct iscsi_conn *conn, int non_blocking); + int (*ep_poll) (struct iscsi_conn *conn, int timeout_ms); + void (*ep_disconnect) (struct iscsi_conn *conn); + void (*create_conn) (struct iscsi_conn *conn); + int (*set_net_config) (struct iscsi_transport *t, + struct iface_rec *iface, + struct iscsi_session *session); + int (*exec_ping) (struct iscsi_transport *t, + struct iface_rec *iface, int datalen, + struct sockaddr_storage *dst_addr, uint32_t *status); +}; + +/* represents data path provider */ +struct iscsi_transport { + struct list_head list; + uint64_t handle; + uint32_t caps; + char name[ISCSI_TRANSPORT_NAME_MAXLEN]; + struct list_head sessions; + struct iscsi_transport_template *template; +}; + +extern int set_transport_template(struct iscsi_transport *t); +extern int transport_load_kmod(char *transport_name); +extern int transport_probe_for_offload(void); + +#endif diff --git a/usr/types.h b/usr/types.h new file mode 100644 index 0000000..9d9ba86 --- /dev/null +++ b/usr/types.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2002-2003 Ardis Technolgies + * + * Released under the terms of the GNU GPL v2.0. + */ + +#ifndef TYPES_H +#define TYPES_H + +#include +#include +#include +#include + +/* + * using the __be types allows stricter static + * typechecking in the kernel using sparse + */ +typedef uint16_t __be16; +typedef uint32_t __be32; + +#endif /* TYPES_H */ diff --git a/usr/uip_mgmt_ipc.c b/usr/uip_mgmt_ipc.c new file mode 100644 index 0000000..2f74657 --- /dev/null +++ b/usr/uip_mgmt_ipc.c @@ -0,0 +1,78 @@ +/* + * uIP iSCSI Daemon/Admin Management IPC + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +#include +#include + +#include "log.h" +#include "uip_mgmt_ipc.h" +#include "iscsid_req.h" +#include "iscsi_err.h" + +int uip_broadcast_params(struct iscsi_transport *t, + struct iface_rec *iface, + struct iscsi_session *session) +{ + struct iscsid_uip_broadcast broadcast; + + log_debug(3, "broadcasting to uip"); + + memset(&broadcast, 0, sizeof(broadcast)); + + broadcast.header.command = ISCSID_UIP_IPC_GET_IFACE; + broadcast.header.payload_len = sizeof(*iface); + + memcpy(&broadcast.u.iface_rec, iface, sizeof(*iface)); + + return uip_broadcast(&broadcast, + sizeof(iscsid_uip_broadcast_header_t) + + sizeof(*iface), O_NONBLOCK, NULL); +} + +int uip_broadcast_ping_req(struct iscsi_transport *t, + struct iface_rec *iface, int datalen, + struct sockaddr_storage *dst_addr, uint32_t *status) +{ + struct iscsid_uip_broadcast broadcast; + int len = 0; + + log_debug(3, "broadcasting ping request to uip\n"); + + memset(&broadcast, 0, sizeof(broadcast)); + + broadcast.header.command = ISCSID_UIP_IPC_PING; + len = sizeof(*iface) + sizeof(*dst_addr) + sizeof(datalen); + broadcast.header.payload_len = len; + + memcpy(&broadcast.u.ping_rec.ifrec, iface, sizeof(*iface)); + + if (dst_addr->ss_family == PF_INET) { + len = sizeof(struct sockaddr_in); + } else if (dst_addr->ss_family == PF_INET6) { + len = sizeof(struct sockaddr_in6); + } else { + log_error("%s unknown addr family %d\n", + __FUNCTION__, dst_addr->ss_family); + return ISCSI_ERR_INVAL; + } + + memcpy(&broadcast.u.ping_rec.ipaddr, dst_addr, len); + broadcast.u.ping_rec.datalen = datalen; + + return uip_broadcast(&broadcast, + sizeof(iscsid_uip_broadcast_header_t) + + broadcast.header.payload_len, 0, status); +} diff --git a/usr/uip_mgmt_ipc.h b/usr/uip_mgmt_ipc.h new file mode 100644 index 0000000..916113d --- /dev/null +++ b/usr/uip_mgmt_ipc.h @@ -0,0 +1,86 @@ +/* + * uIP iSCSI Daemon/Admin Management IPC + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ +#ifndef UIP_MGMT_IPC_H +#define UIP_MGMT_IPC_H + +#include "types.h" +#include "iscsi_if.h" +#include "config.h" +#include "mgmt_ipc.h" + +#include "initiator.h" +#include "transport.h" + +#define ISCSID_UIP_NAMESPACE "ISCSID_UIP_ABSTRACT_NAMESPACE" + +typedef enum iscsid_uip_cmd { + ISCSID_UIP_IPC_UNKNOWN = 0, + ISCSID_UIP_IPC_GET_IFACE = 1, + ISCSID_UIP_IPC_PING = 2, + + __ISCSID_UIP_IPC_MAX_COMMAND +} iscsid_uip_cmd_e; + +typedef struct iscsid_uip_broadcast_header { + iscsid_uip_cmd_e command; + uint32_t payload_len; +} iscsid_uip_broadcast_header_t; + +/* IPC Request */ +typedef struct iscsid_uip_broadcast { + struct iscsid_uip_broadcast_header header; + + union { + /* messages */ + struct ipc_broadcast_iface_rec { + struct iface_rec rec; + } iface_rec; + + struct ipc_broadcast_ping_rec { + struct iface_rec ifrec; + struct sockaddr_storage ipaddr; + int datalen; + int *status; + } ping_rec; + } u; +} iscsid_uip_broadcast_t; + +typedef enum iscsid_uip_mgmt_ipc_err { + ISCSID_UIP_MGMT_IPC_OK = 0, + ISCSID_UIP_MGMT_IPC_ERR = 1, + ISCSID_UIP_MGMT_IPC_ERR_NOT_FOUND = 2, + ISCSID_UIP_MGMT_IPC_ERR_NOMEM = 3, + ISCSID_UIP_MGMT_IPC_DEVICE_UP = 4, + ISCSID_UIP_MGMT_IPC_DEVICE_INITIALIZING = 5, +} iscsid_uip_mgmt_ipc_err_e; + +/* IPC Response */ +typedef struct iscsid_uip_mgmt_rsp { + iscsid_uip_cmd_e command; + iscsid_uip_mgmt_ipc_err_e err; + enum iscsi_ping_status_code ping_sc; +} iscsid_uip_rsp_t; + +extern int uip_broadcast_params(struct iscsi_transport *t, + struct iface_rec *iface, + struct iscsi_session *session); + +extern int uip_broadcast_ping_req(struct iscsi_transport *t, + struct iface_rec *iface, int datalen, + struct sockaddr_storage *dst_addr, + uint32_t *status); + +#endif /* UIP_MGMT_IPC_H */ diff --git a/usr/version.h b/usr/version.h new file mode 100644 index 0000000..615f533 --- /dev/null +++ b/usr/version.h @@ -0,0 +1,12 @@ +#ifndef ISCSI_VERSION_DEF +#define ISCSI_VERSION_DEF + +/* + * iSCSI tools version. + * This may not be the same value as the kernel versions because + * some other maintainer could merge a patch without going through us + */ +#define ISCSI_VERSION_STR "2.0-878" +#define ISCSI_VERSION_FILE "/sys/module/scsi_transport_iscsi/version" + +#endif diff --git a/utils/.gitignore b/utils/.gitignore new file mode 100644 index 0000000..7efe3fd --- /dev/null +++ b/utils/.gitignore @@ -0,0 +1 @@ +iscsi-iname diff --git a/utils/50-iscsi-firmware-login.rules b/utils/50-iscsi-firmware-login.rules new file mode 100644 index 0000000..47b3cf9 --- /dev/null +++ b/utils/50-iscsi-firmware-login.rules @@ -0,0 +1,15 @@ +# This file contains the rules to handle iscsi firmware changes + +# DO NOT WRAP THIS LINE +# +# old udev does not understand some of it, +# and would end up skipping only some lines, not the full rule. +# which can cause all sort of trouble with strange-named device nodes +# for completely unrelated devices, +# resulting in unusable network lookback, etc. +# +# in case this is "accidentally" installed on a system with old udev, +# having it as one single line avoids those problems. +# +# DO NOT WRAP THIS LINE +SUBSYSTEM=="iscsi_boot*", ACTION=="add", DEVPATH=="*/target*", RUN+="/sbin/iscsi_fw_login" diff --git a/utils/Makefile b/utils/Makefile new file mode 100644 index 0000000..f65f1e7 --- /dev/null +++ b/utils/Makefile @@ -0,0 +1,18 @@ +# This Makefile will work only with GNU make. + +CFLAGS ?= -O2 -fno-inline -g +CFLAGS += -Wall -Wstrict-prototypes +PROGRAMS = iscsi-iname + +all: $(PROGRAMS) + +iscsi-iname: md5.o iscsi-iname.o + $(CC) $(CFLAGS) $(LDFLAGS) $^ $(DBM_LIB) -o $@ + +clean: + rm -f *.o $(PROGRAMS) .depend + +depend: + gcc $(CFLAGS) -M `ls *.c` > .depend + +-include .depend diff --git a/utils/fwparam_ibft/Makefile b/utils/fwparam_ibft/Makefile new file mode 100644 index 0000000..1a2b4cf --- /dev/null +++ b/utils/fwparam_ibft/Makefile @@ -0,0 +1,51 @@ +# +# Copyright (C) IBM Corporation. 2007 +# Author: Doug Maxey +# +# 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, see . +# +# Authors: Patrick Mansfield +# Mike Anderson +# Doug Maxey +# "Prasanna Mumbai" +# +ifeq ($(TOPDIR),) + TOPDIR = ../.. +endif + + +SYSDEPS_OBJS = $(sort $(wildcard ../sysdeps/*.o)) +OBJS := fw_entry.o fwparam_sysfs.o $(SYSDEPS_OBJS) \ + $(TOPDIR)/usr/iscsi_net_util.o +OBJS += prom_lex.o prom_parse.tab.o fwparam_ppc.o +CLEANFILES = $(OBJS) *.output *~ + +CFLAGS ?= -O2 -g +WARNFLAGS ?= -Wall -Wstrict-prototypes -Wno-format-truncation +CFLAGS += -fPIC $(WARNFLAGS) -I$(TOPDIR)/include -I$(TOPDIR)/usr -D_GNU_SOURCE \ + -I$(TOPDIR)/libopeniscsiusr + +LDFLAGS += -L$(TOPDIR)/libopeniscsiusr -liscsiusr + +all: $(OBJS) + +clean: + rm -f *.o $(CLEANFILES) .depend + +$(OBJS): prom_parse.tab.h prom_parse.h fwparam_ibft.h + +depend: + gcc $(CFLAGS) -M `ls *.c` > .depend + +-include .depend diff --git a/utils/fwparam_ibft/fw_entry.c b/utils/fwparam_ibft/fw_entry.c new file mode 100644 index 0000000..96af145 --- /dev/null +++ b/utils/fwparam_ibft/fw_entry.c @@ -0,0 +1,251 @@ +/* + * Copyright (C) IBM Corporation. 2007 + * Author: Doug Maxey + * based on code written by "Prasanna Mumbai" + * + * 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, see . + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fw_context.h" +#include "fwparam.h" +#include "idbm_fields.h" +#include "iscsi_net_util.h" +#include "iscsi_err.h" +#include "config.h" +#include "iface.h" + +/** + * fw_setup_nics - setup nics (ethXs) based on ibft net info + * + * If this is a offload card, this function does nothing. The + * net info is used by the iscsi iface settings for the iscsi + * function. + */ +int fw_setup_nics(void) +{ + struct boot_context *context; + struct list_head targets; + char *iface_prev = NULL, transport[16]; + int needs_bringup = 0, ret = 0, err; + + INIT_LIST_HEAD(&targets); + + ret = fw_get_targets(&targets); + if (ret || list_empty(&targets)) { + printf("Could not setup fw entries.\n"); + return ISCSI_ERR_NO_OBJS_FOUND; + } + + /* + * For each target in iBFT bring up required NIC and use routing + * to force iSCSI traffic through correct NIC + */ + list_for_each_entry(context, &targets, list) { + if (iface_prev == NULL || strcmp(context->iface, iface_prev)) { + /* Note: test above works because there is a + * maximum of two targets in the iBFT + */ + iface_prev = context->iface; + needs_bringup = 1; + } + if (net_get_transport_name_from_netdev(context->iface, transport)) { + /* Setup software NIC, */ + printf("Setting up software interface %s\n", context->iface); + err = net_setup_netdev(context->iface, context->ipaddr, + context->mask, context->gateway, + context->vlan, + context->target_ipaddr, needs_bringup); + if (err) { + printf("Setting up software interface %s failed\n", + context->iface); + ret = err; + } + } else { + /* Setup offload NIC. */ + struct iface_rec iface; + + memset(&iface, 0, sizeof(iface)); + iface_setup_defaults(&iface); + printf("Setting up offload interface %s\n", context->iface); + if (!iface_setup_from_boot_context(&iface, context)) { + printf("Setting up offload interface %s failed\n", + context->iface); + + ret = ISCSI_ERR; + } + } + } + + fw_free_targets(&targets); + if (ret) + return ISCSI_ERR; + else + return 0; +} + +/** + * fw_get_entry - return boot context of portal used for boot + * @context: firmware info of portal + * + * Returns non-zero if no portal was used for boot. + * + * This function is not thread safe. + */ +int fw_get_entry(struct boot_context *context) +{ + int ret; + + ret = fwparam_ppc_boot_info(context); + if (ret) + ret = fwparam_sysfs_boot_info(context); + return ret; +} + +/** + * fw_get_targets - get a boot_context struct for each target + * @list: list to add entires on. + * + * Returns zero if entries were found that can be traversed with the + * list.h helpers, or non-zero if no entries are found. + * + * fw_free_targets should be called to free the list. + * + * This function is not thread safe. + */ +int fw_get_targets(struct list_head *list) +{ + int ret; + + ret = fwparam_ppc_get_targets(list); + if (ret) + ret = fwparam_sysfs_get_targets(list); + return ret; +} + +void fw_free_targets(struct list_head *list) +{ + struct boot_context *curr, *tmp; + + if (!list || list_empty(list)) + return; + + list_for_each_entry_safe(curr, tmp, list, list) { + list_del(&curr->list); + free(curr); + } +} + +static void dump_initiator(struct boot_context *context) +{ + struct iface_rec iface; + + memset(&iface, 0, sizeof(iface)); + iface_setup_defaults(&iface); + iface_setup_from_boot_context(&iface, context); + + if (strlen(context->initiatorname)) + printf("%s = %s\n", IFACE_INAME, context->initiatorname); + + if (strlen(context->isid)) + printf("%s = %s\n", IFACE_ISID, context->isid); + + printf("%s = %s\n", IFACE_TRANSPORTNAME, iface.transport_name); +} + +static void dump_target(struct boot_context *context) +{ + if (strlen(context->targetname)) + printf("%s = %s\n", NODE_NAME, context->targetname); + + if (strlen(context->target_ipaddr)) + printf(CONN_ADDR" = %s\n", 0, context->target_ipaddr); + printf(CONN_PORT" = %d\n", 0, context->target_port); + + if (strlen(context->chap_name)) + printf("%s = %s\n", SESSION_USERNAME, context->chap_name); + if (strlen(context->chap_password)) + printf("%s = %s\n", SESSION_PASSWORD, context->chap_password); + if (strlen(context->chap_name_in)) + printf("%s = %s\n", SESSION_USERNAME_IN, context->chap_name_in); + if (strlen(context->chap_password_in)) + printf("%s = %s\n", SESSION_PASSWORD_IN, + context->chap_password_in); + + if (strlen(context->lun)) + printf("%s = %s\n", NODE_BOOT_LUN, context->lun); +} + +static void dump_network(struct boot_context *context) +{ + /* Dump the 8 byte mac address (not iser support) */ + if (strlen(context->mac)) + printf("%s = %s\n", IFACE_HWADDR, context->mac); + /* + * If the 'origin' field is 3 (IBFT_IP_PREFIX_ORIGIN_DHCP), + * then DHCP is used. + * Otherwise evaluate the 'dhcp' field, if this has a valid + * address then DHCP was used (broadcom sends 0.0.0.0). + */ + if ((context->origin == IBFT_IP_PREFIX_ORIGIN_DHCP) || + (strlen(context->dhcp) && strcmp(context->dhcp, "0.0.0.0"))) + printf("%s = DHCP\n", IFACE_BOOT_PROTO); + else + printf("%s = STATIC\n", IFACE_BOOT_PROTO); + if (strlen(context->ipaddr)) + printf("%s = %s\n", IFACE_IPADDR, context->ipaddr); + if (context->prefix) + printf("%s = %d\n", IFACE_PREFIX_LEN, context->prefix); + if (strlen(context->mask)) + printf("%s = %s\n", IFACE_SUBNET_MASK, context->mask); + if (strlen(context->gateway)) + printf("%s = %s\n", IFACE_GATEWAY, context->gateway); + if (strlen(context->primary_dns)) + printf("%s = %s\n", IFACE_PRIMARY_DNS, context->primary_dns); + if (strlen(context->secondary_dns)) + printf("%s = %s\n", IFACE_SEC_DNS, context->secondary_dns); + if (strlen(context->vlan)) + printf("%s = %s\n", IFACE_VLAN_ID, context->vlan); + if (strlen(context->iface)) + printf("%s = %s\n", IFACE_NETNAME, context->iface); +} + +/** + * fw_print_entry - print boot context info of portal used for boot + * @context: firmware info of portal + * + * Does not print anything if no portal was used for boot. + * + * TODO: Merge this in with idbm.c helpers. + */ +void fw_print_entry(struct boot_context *context) +{ + printf("%s\n", ISCSI_BEGIN_REC); + dump_initiator(context); + dump_network(context); + dump_target(context); + printf("%s\n", ISCSI_END_REC); +} diff --git a/utils/fwparam_ibft/fwparam.h b/utils/fwparam_ibft/fwparam.h new file mode 100644 index 0000000..141def7 --- /dev/null +++ b/utils/fwparam_ibft/fwparam.h @@ -0,0 +1,32 @@ +/* + * 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ +#ifndef FWPARAM_H_ +#define FWPARAM_H_ + +#include +#include "fw_context.h" + +#define FILENAMESZ (1024) + +struct boot_context; + +int fwparam_sysfs_boot_info(struct boot_context *context); +int fwparam_sysfs_get_targets(struct list_head *list); +int fwparam_ppc_boot_info(struct boot_context *context); +int fwparam_ppc_get_targets(struct list_head *list); + +#endif /* FWPARAM_IBFT_H_ */ diff --git a/utils/fwparam_ibft/fwparam_ibft.c b/utils/fwparam_ibft/fwparam_ibft.c new file mode 100644 index 0000000..52edac1 --- /dev/null +++ b/utils/fwparam_ibft/fwparam_ibft.c @@ -0,0 +1,498 @@ +/* + * 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * Copyright (C) IBM Corporation, 2006 + * + * Authors: Patrick Mansfield + * Mike Anderson + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fwparam_ibft.h" +#include "fw_context.h" + +char *progname = "fwparam_ibft"; +int debug; +int dev_count; +char filename[FILENAMESZ]; + +const char nulls[16]; /* defaults to zero */ + +int +verify_hdr(char *name, struct ibft_hdr *hdr, int id, int version, int length) +{ +#define VERIFY_HDR_FIELD(val) \ + if (hdr->val != val) { \ + fprintf(stderr, \ + "%s: error, %s structure expected %s %d but" \ + " got %d\n", \ + progname, name, #val, hdr->val, val); \ + return -1; \ + } + + if (debug > 1) + fprintf(stderr, "%s: verifying %s header\n", __FUNCTION__, + name); + + VERIFY_HDR_FIELD(id); + VERIFY_HDR_FIELD(version); + VERIFY_HDR_FIELD(length); + +#undef VERIFY_HDR_FIELD + return 0; +} + +#define CHECK_HDR(ident, name) \ + verify_hdr(#name, &ident->hdr, id_##name, version_##name, \ + sizeof(*ident)) + +/* + * Format 8 byte scsi LUN. Just format 8 bytes of hex, we could also + * format in the format as specified in rfc4173 (1-2-3-4, or 1-2), that is + * a nice format for humans :) + */ +void +format_lun(char *buf, size_t size, uint8_t *lun) +{ + int i; + + for (i = 0; i < 8; i++) + snprintf(buf++, size--, "%x", lun[i]); +} + +void +dump_lun(char *prefix, char *id, uint8_t *lun) +{ + char buf[32]; + + format_lun(buf, sizeof(buf), lun); + + if (prefix) + printf("%s%s=%s\n", prefix, id, buf); + else + printf("%s=%s\n", id, buf); + +} + +void +dump_word(char *prefix, char *id, unsigned short value) +{ + if (prefix) + printf("%s%s=%d\n", prefix, id, value); + else + printf("%s=%d\n", id, value); +} + +void +dump_string(char *prefix, char *id, char *value, int len) +{ + if (len == 0) + return; + /* + * Not checking if the offset is non-zero, it is not even passed + * in, else we need to pass a start and offset rather than value. + */ + + /* + * prints the string in "value" that has "len" characters (the + * printf "*" * means use the next argument as the length). + */ + if (prefix) + printf("%s%s=%.*s\n", prefix, id, len, value); + else + printf("%s=%.*s\n", id, len, value); +} + +void +format_ipaddr(char *buf, size_t size, uint8_t *ip) +{ + if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0 && + ip[4] == 0 && ip[5] == 0 && ip[6] == 0 && ip[7] == 0 && + ip[8] == 0 && ip[9] == 0 && ip[10] == 0xff && ip[11] == 0xff) { + /* + * IPV4 + */ + snprintf(buf, size, "%d.%d.%d.%d", ip[12], ip[13], ip[14], ip[15]); + } else { + /* XXX ... */ + fprintf(stderr, "%s: warning no IPV6 support.\n", progname); + buf[0] = '\0'; + return; + } + +} + +/* + * Dump the 16 byte ipaddr, as IPV6 or IPV4. + */ +void +dump_ipaddr(char *prefix, char *id, uint8_t *ip) +{ + char buf[32]; + + /* + * Assumes all zero means no IP address. + */ + if (!memcmp(ip, nulls, sizeof(nulls))) + return; + + format_ipaddr(buf, sizeof(buf), ip); + + if (prefix) + printf("%s%s=%s\n", prefix, id, buf); + else + printf("%s=%s\n", id, buf); + +} + +/* + * Dump the 8 byte mac address + */ +void +dump_mac(char *prefix, char *id, uint8_t *mac) +{ + int i; + + if (prefix) + printf("%s%s=", prefix, id); + else + printf("%s=", id); + + for (i = 0; i < 5; i++) + printf("%02x:", mac[i]); + printf("%02x\n", mac[i]); +} + + +void +dump_initiator_prefix(void *ibft_loc, struct ibft_initiator *initiator, char *prefix) +{ + if (!initiator) + return; + /* + * Not all fields are (or were) supported by open-iscsi. Plus, + * some of these are for discovery. + */ + dump_ipaddr(prefix, "ISNS", initiator->isns_server); + dump_ipaddr(prefix, "SLP", initiator->slp_server); + dump_ipaddr(prefix, "PRIMARY_RADIUS_SERVER", initiator->pri_radius_server); + dump_ipaddr(prefix, "SECONDARY_RADIUS_SERVER", initiator->sec_radius_server); + dump_string(prefix, "NAME", ibft_loc + + initiator->initiator_name_off, initiator->initiator_name_len); +} + +void +dump_nic_prefix(void *ibft_loc, struct ibft_nic *nic, char *prefix) +{ + + if (!nic) + return; + + dump_mac(prefix, "HWADDR", nic->mac); + /* + * Assume dhcp if any non-zero portions of its address are set + * (again, undocumented). + */ + if (memcmp(nic->dhcp, nulls, sizeof(nic->dhcp))) { + dump_ipaddr(prefix, "DHCP", nic->dhcp); + } else { + dump_ipaddr(prefix, "IPADDR", nic->ip_addr); + /* + * XXX: Not sure how a mask "prefix" will be used in network + * bringup, this sounds less flexible than the normal + * masks used. + */ + printf("%s%s=%d\n", prefix, "MASK", nic->subnet_mask_prefix); + dump_ipaddr(prefix, "GATEWAY", nic->gateway); + dump_ipaddr(prefix, "DNSADDR1", nic->primary_dns); + dump_ipaddr(prefix, "DNSADDR2", nic->secondary_dns); + } + + dump_string(prefix, "HOSTNAME", ibft_loc + nic->hostname_off, + nic->hostname_len); + /* + * XXX unknown vlan: + */ + dump_word(prefix, "VLAN", nic->vlan); + /* + * XXX sort of unknown pci_bdf: 8 bits bus, 5 bits device, 3 bits + * function. + */ + if (prefix ) + printf("%s%s=%d:%d:%d\n", prefix, "PCI_BDF", + /* bus */ (nic->pci_bdf & 0xff00) >> 8, + /* device */ (nic->pci_bdf & 0xf8) >> 3, + /* function */ (nic->pci_bdf & 0x07)); + else + printf("%s=%d:%d:%d\n", "PCI_BDF", + /* bus */ (nic->pci_bdf & 0xff00) >> 8, + /* device */ (nic->pci_bdf & 0xf8) >> 3, + /* function */ (nic->pci_bdf & 0x07)); +} + +void +dump_tgt_prefix(void *ibft_loc, struct ibft_tgt *tgt, char *prefix) +{ + + if (!tgt) + return; + + dump_ipaddr(prefix, "IPADDR", tgt->ip_addr); + dump_word(prefix, "PORT", tgt->port); + /* + * XXX there should at least be a "no LUN specified field", or + * have different location objects, so the setup can search for + * the appropriate LU (like mount by label, or use of the + * /dev/disk/by-id names, or .... + * + * Like: + * uint8_t lu_type; 0: nothing specified, 1: LUN, 2: misc + * name - OS can use any way it wants, would have embedded a + * "NAME=string", like "LABEL=myrootvolume", or + * "DEV_NAME=/dev/disk/by-id/scsi-198279562093043094003030903". + * union lu_value { + * uint8_t lun[8]; + * uint8_t misc_name[64]; + * }; + * + * Maybe just add an extension header, and let the admin/user put + * strings like: "area:VALUE=string" into it? + */ + dump_lun(prefix, "LUN", tgt->lun); + dump_string(prefix, "NAME", ibft_loc + tgt->tgt_name_off, + tgt->tgt_name_len); + /* + * Note: don't dump the nic association, just let the IP address take + * care of the routing. + */ + /* + * Note: don't dump the chap "type", just the chap names and secrets + * if any are specified - they imply CHAP and reversed CHAP. + */ + dump_string(prefix, "CHAP_NAME", ibft_loc + tgt->chap_name_off, + tgt->chap_name_len); + dump_string(prefix, "CHAP_PASSWORD", ibft_loc + tgt->chap_secret_off, + tgt->chap_secret_len); + dump_string(prefix, "CHAP_NAME_IN", ibft_loc + tgt->rev_chap_name_off, + tgt->rev_chap_name_len); + dump_string(prefix, "CHAP_PASSWORD_IN", + ibft_loc + tgt->rev_chap_secret_off, + tgt->rev_chap_secret_len); +} + +/* + * Read in and dump ASCII output for ibft starting at ibft_loc. + */ +int +dump_ibft(void *ibft_loc, struct boot_context *context) +{ + struct ibft_table_hdr *ibft_hdr = ibft_loc; + struct ibft_control *control; + struct ibft_initiator *initiator = NULL; + struct ibft_nic *nic0 = NULL, *nic1 = NULL; + struct ibft_tgt *tgt0 = NULL, *tgt1 = NULL; + char ipbuf[32]; + + control = ibft_loc + sizeof(*ibft_hdr); + CHECK_HDR(control, control); + + /* + * The ibft is setup to return multiple pieces for each + * object (like multiple nic's or multiple targets), but it only + * maps 1 initiator, two targets, and two nics, follow that layout + * here (i.e. don't search for others). + * + * Also, unknown what to do for extensions piece, it is not + * documented. + */ + + if (control->initiator_off) { + initiator = ibft_loc + control->initiator_off; + CHECK_HDR(initiator, initiator); + } + + if (control->nic0_off) { + nic0 = ibft_loc + control->nic0_off; + CHECK_HDR(nic0, nic); + } + + if (control->nic1_off) { + nic1 = ibft_loc + control->nic1_off; + CHECK_HDR(nic1, nic); + } + + if (control->tgt0_off) { + tgt0 = ibft_loc + control->tgt0_off; + CHECK_HDR(tgt0, target); + } + + if (control->tgt1_off) { + tgt1 = ibft_loc + control->tgt1_off; + CHECK_HDR(tgt1, target); + } + + strlcpy(context->initiatorname, + (char *)ibft_loc+initiator->initiator_name_off, + initiator->initiator_name_len + 1); + + if (tgt0 && (tgt0->hdr.flags & INIT_FLAG_FW_SEL_BOOT)) { + strlcpy((char *)context->targetname, + (char *)(ibft_loc+tgt0->tgt_name_off), + tgt0->tgt_name_len); + format_ipaddr(ipbuf, sizeof(ipbuf), + tgt0->ip_addr); + strlcpy((char *)context->target_ipaddr, ipbuf, + sizeof(ipbuf)); + context->target_port = tgt0->port; + strlcpy(context->chap_name, + (char *)(ibft_loc + tgt0->chap_name_off), + tgt0->chap_name_len); + strlcpy(context->chap_password, + (char*)(ibft_loc + tgt0->chap_secret_off), + tgt0->chap_secret_len); + strlcpy(context->chap_name_in, + (char *)(ibft_loc + tgt0->rev_chap_name_off), + tgt0->rev_chap_name_len); + strlcpy(context->chap_password_in, + (char *)(ibft_loc + tgt0->rev_chap_secret_off), + tgt0->rev_chap_secret_len); + } else if (tgt1 && + (tgt1->hdr.flags & INIT_FLAG_FW_SEL_BOOT)) { + strlcpy((char *)context->targetname, + (char *)(ibft_loc+tgt1->tgt_name_off), + tgt1->tgt_name_len); + format_ipaddr(ipbuf, sizeof(ipbuf), + tgt1->ip_addr); + strlcpy((char *)context->target_ipaddr,ipbuf, + sizeof(ipbuf)); + context->target_port = tgt1->port; + strlcpy(context->chap_name, + (char *)(ibft_loc + tgt1->chap_name_off), + tgt1->chap_name_len); + strlcpy(context->chap_password, + (char*)(ibft_loc + tgt1->chap_secret_off), + tgt1->chap_secret_len); + strlcpy(context->chap_name_in, + (char *)(ibft_loc + tgt1->rev_chap_name_off), + tgt1->rev_chap_name_len); + strlcpy(context->chap_password_in, + (char *)(ibft_loc + tgt1->rev_chap_secret_off), + tgt1->rev_chap_secret_len); + } + + return 0; +} + +char *search_ibft(unsigned char *start, int length) +{ + unsigned char *cur_ptr; + struct ibft_table_hdr *ibft_hdr; + unsigned char check_sum; + uint32_t i; + + cur_ptr = (unsigned char *)start; + for (cur_ptr = (unsigned char *)start; + cur_ptr < (start + length); + cur_ptr++) { + if (memcmp(cur_ptr, iBFTSTR,strlen(iBFTSTR))) + continue; + + ibft_hdr = (struct ibft_table_hdr *)cur_ptr; + /* Make sure it's correct version. */ + if (ibft_hdr->revision != iBFT_REV) + continue; + + /* Make sure that length is valid. */ + if ((cur_ptr + ibft_hdr->length) <= (start + length)) { + /* Let verify the checksum */ + for (i = 0, check_sum = 0; i < ibft_hdr->length; i++) + check_sum += cur_ptr[i]; + + if (check_sum == 0) + return (char *)cur_ptr; + } + } + return NULL; +} + +int +fwparam_ibft(struct boot_context *context, const char *filepath) +{ + int fd, ret; + char *filebuf, *ibft_loc; + int start = 512 * 1024; /* 512k */ + int end_search = (1024 * 1024) - start; /* 512k */ + struct stat buf; + + if (filepath) + strlcpy(filename, filepath, FILENAMESZ); + else + strlcpy(filename, X86_DEFAULT_FILENAME, FILENAMESZ); + + fd = open(filename, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Could not open %s: %s (%d)\n", + filename, strerror(errno), errno); + return -1; + } + + /* Find the size. */ + if (stat(filename, &buf)!=0) { + fprintf(stderr, "Could not stat file %s: %s (%d)\n", + filename, strerror(errno), errno); + return -1; + } + /* And if not zero use that size */ + if (buf.st_size > 0) { + start = 0; + end_search=buf.st_size; + } + /* + * XXX Possibly warn and exit if start > filesize(fd), or if start + + * end_search > filesize(fd). Else, we will get a bus error for + * small files (with memmap, and for testing at least, it would + * be hard to find a system with less than 1024k). + */ + filebuf = mmap(NULL, end_search, PROT_READ, MAP_PRIVATE, fd, start); + if (filebuf == MAP_FAILED) { + fprintf(stderr, "Could not mmap %s: %s (%d)\n", + filename, strerror(errno), errno); + ret = -1; + goto done; + } + + ibft_loc = search_ibft((unsigned char *)filebuf, end_search); + if (ibft_loc) + ret = dump_ibft(ibft_loc, context); + else { + printf("Could not find iBFT.\n"); + ret = -1; + } + munmap(filebuf, end_search); +done: + close(fd); + return ret; +} diff --git a/utils/fwparam_ibft/fwparam_ibft.h b/utils/fwparam_ibft/fwparam_ibft.h new file mode 100644 index 0000000..92968b3 --- /dev/null +++ b/utils/fwparam_ibft/fwparam_ibft.h @@ -0,0 +1,149 @@ +/* + * 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * Copyright (C) IBM Corporation, 2006,2007 + * + * Authors: Doug Maxey + * Patrick Mansfield + * + */ + +#ifndef FWPARAM_IBFT_H_ +#define FWPARAM_IBFT_H_ + +/* #include */ +#include +#include "fw_context.h" + +/* + * Structures here are is based on Doug's original code, and Patrick's + * interpretation of the IBM internal design document title the "iSCSI + * Boot Firmware Table (iBFT)". + */ +#define iBFTSTR "iBFT" +#define iBFT_SIG { 'i','B','F','T' } + +#define iBFT_REV 1 + +/* + * These macros are lower case to make the verify_hdr macro easier. + */ +#define version_control 1 +#define version_initiator 1 +#define version_nic 1 +#define version_target 1 +#define version_extensions 1 + +enum ibft_id { + id_control = 1, + id_initiator, + id_nic, + id_target, + id_extensions, +}; + +struct ibft_hdr { + uint8_t id; + uint8_t version; + uint16_t length; + uint8_t ind; + uint8_t flags; +}; + +struct ibft_table_hdr { + uint8_t signature[4]; + uint32_t length; + uint8_t revision; + uint8_t checksum; + uint8_t oemid[6]; + uint8_t oem_table_id[8]; + uint8_t rsvd1[24]; +} __attribute__((__packed__)); + +struct ibft_control { + struct ibft_hdr hdr; + uint16_t extensions; + uint16_t initiator_off; + uint16_t nic0_off; + uint16_t tgt0_off; + uint16_t nic1_off; + uint16_t tgt1_off; +} __attribute__((__packed__)); + +struct ibft_initiator { +#define INIT_FLAG_VALID 1 +#define INIT_FLAG_FW_SEL_BOOT 2 + struct ibft_hdr hdr; + uint8_t isns_server[16]; + uint8_t slp_server[16]; + uint8_t pri_radius_server[16]; + uint8_t sec_radius_server[16]; + uint16_t initiator_name_len; + uint16_t initiator_name_off; +} __attribute__((__packed__)); + +struct ibft_nic { +#define NIC_FLAG_VALID 1 +#define NIC_FLAG_FW_SEL_BOOT 2 + struct ibft_hdr hdr; + uint8_t ip_addr[16]; + uint8_t subnet_mask_prefix; + uint8_t origin; + uint8_t gateway[16]; + uint8_t primary_dns[16]; + uint8_t secondary_dns[16]; + uint8_t dhcp[16]; + uint16_t vlan; + uint8_t mac[6]; + uint16_t pci_bdf; + uint16_t hostname_len; + uint16_t hostname_off; +} __attribute__((__packed__)); + +struct ibft_tgt { +#define TGT_FLAG_VALID 1 +#define TGT_FLAG_FW_SEL_BOOT 2 +#define TGT_FLAG_USE_RADIUS_CHAT 4 +#define TGT_FLAG_USE_RADIUS_RCHAT 8 + struct ibft_hdr hdr; + uint8_t ip_addr[16]; + uint16_t port; + uint8_t lun[8]; +#define TGT_CHAP 1 +#define TGT_MUTUAL_CHAP 2 + uint8_t chap_type; + uint8_t nic_assoc; + uint16_t tgt_name_len; + uint16_t tgt_name_off; + uint16_t chap_name_len; + uint16_t chap_name_off; + uint16_t chap_secret_len; + uint16_t chap_secret_off; + uint16_t rev_chap_name_len; + uint16_t rev_chap_name_off; + uint16_t rev_chap_secret_len; + uint16_t rev_chap_secret_off; +} __attribute__((__packed__)); + +/* Common variables */ +#define FILENAMESZ (1024) +extern char filename[FILENAMESZ]; +#define X86_DEFAULT_FILENAME "/dev/mem" +extern int debug; +extern int dev_count; + +extern int fwparam_ibft(struct boot_context *context, const char *filepath); +#endif /* FWPARAM_IBFT_H_ */ diff --git a/utils/fwparam_ibft/fwparam_ppc.c b/utils/fwparam_ibft/fwparam_ppc.c new file mode 100644 index 0000000..429d45c --- /dev/null +++ b/utils/fwparam_ibft/fwparam_ppc.c @@ -0,0 +1,573 @@ +/* + * Copyright (C) IBM Corporation. 2007 + * Author: Doug Maxey + * + * 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, see . + */ + +#define _XOPEN_SOURCE 500 +#include +#include +#include +#include +#include +#include +#include + +#include "fwparam.h" +#include "fw_context.h" +#include "iscsi_obp.h" +#include "prom_parse.h" +#include "sysdeps.h" +#include "iscsi_err.h" + +void* yy_scan_string(const char *str); +int yyparse(struct ofw_dev *ofwdev); + +#define BOOTPATH "/chosen/bootpath" +#define DT_TOP "/proc/device-tree" +#define LOCAL_MAC_FILE "/local-mac-address" + +static int devtree_offset; +static char *bootpath_val; +static int bytes_read; +#define OFWDEV_MAX (10) +static struct ofw_dev *ofwdevs[OFWDEV_MAX]; +static char *niclist[OFWDEV_MAX]; +static int nic_count; +static int debug; +static int dev_count; + +static void cp_param(char *dest, const char *name, struct ofw_dev *dev, + enum obp_param item, int len) +{ + if (dev->param[item]) + strlcpy(dest, dev->param[item]->val, len); +} + +static void cp_int_param(int *dest, const char *name, struct ofw_dev *dev, + enum obp_param item) +{ + if (dev->param[item]) + *dest = strtol(dev->param[item]->val, NULL, 10); +} + +static char *find_devtree(const char *filename) +{ + char *devtree = strdup(filename); + char *chop_at; + struct stat dt_stat; + int error; + + /* + * What is the path to the device-tree? The only valid + * directories to locate the property are under /aliases or + * /chosen. + */ + + chop_at = strstr(devtree, "/chosen"); + if (!chop_at) + chop_at = strstr(devtree, "/aliases"); + + if (!chop_at) { + char *vdev = malloc(strlen(filename) + strlen("/vdevice") + 1); + + /* + * test to see if there is /vdevice dir + */ + if (vdev) { + sprintf(vdev, "%s%s", filename, "/vdevice"); + error = stat(vdev, &dt_stat); + free(vdev); + if (error) { + free(devtree); + return NULL; + } + } + } else + devtree[chop_at - devtree] = 0; + + if (devtree) + devtree_offset = strlen(devtree); + + return devtree; +} + +/* + * Take the path to the property under chosen, and swizzle to make that + * the base for the device path discovered. + */ +static int locate_mac(const char *devtree, struct ofw_dev *ofwdev) +{ + int error = 0; + int mac_path_len = strlen(ofwdev->dev_path) + strlen(LOCAL_MAC_FILE) + + 2; + char *mac_file; + int mac_fd; + + mac_path_len += strlen(devtree); + mac_file = malloc(mac_path_len); + if (!mac_file) { + error = ENOMEM; + fprintf(stderr, "%s: malloc , %s\n", __func__, + strerror(errno)); + goto lpm_bail; + } + + snprintf(mac_file, mac_path_len, "%s%s%s", devtree, ofwdev->dev_path, + LOCAL_MAC_FILE); + mac_fd = open(mac_file, O_RDONLY); + if (mac_fd < 0) { + error = errno; + fprintf(stderr, "%s: open %s, %s\n", __func__, mac_file, + strerror(errno)); + free(mac_file); + goto lpm_bail; + } + + bytes_read = read(mac_fd, ofwdev->mac, 6); + if (bytes_read != 6) { + error = EIO; + fprintf(stderr, "%s: read %s, %s\n", __func__, mac_file, + strerror(errno)); + } + free(mac_file); + close(mac_fd); + +lpm_bail: + return error; +} + +const char *obp_qual_set(struct ofw_dev *ofwdev, const char *qual) +{ + if (!strcmp("bootp", qual)) + ofwdev->quals[ofwdev->qual_count++] = OBP_QUAL_BOOTP; + else if (!strcmp("dhcpv6", qual)) + ofwdev->quals[ofwdev->qual_count++] = OBP_QUAL_DHCPV6; + else if (!strcmp("ipv6", qual)) + ofwdev->quals[ofwdev->qual_count++] = OBP_QUAL_IPV6; + else if (!strcmp("iscsi", qual)) { + ofwdev->type = OFW_DT_ISCSI; + ofwdev->quals[ofwdev->qual_count++] = OBP_QUAL_ISCSI; + } else if (!strcmp("ping", qual)) + ofwdev->quals[ofwdev->qual_count++] = OBP_QUAL_PING; + else + printf("%s: %s UNKNOWN\n", __func__, qual); + return qual; +} + +void add_obp_parm(struct ofw_dev *ofwdev, enum obp_param parm, const char *str) +{ + int psz = sizeof(struct ofw_obp_param) + strlen(str); + + ofwdev->param[parm] = malloc(psz); + if (ofwdev->param[parm] == NULL) { + printf("%s: ENOMEM!\n", __func__); + return; + } + memset(ofwdev->param[parm], 0, psz); + ofwdev->param[parm]->len = psz; + strcpy(ofwdev->param[parm]->val, str); +} + +void obp_parm_addr(struct ofw_dev *ofwdev, const char *parm, const char *addr) +{ + if (!strcmp("ciaddr", parm)) + add_obp_parm(ofwdev, OBP_PARAM_CIADDR, addr); + else if (!strcmp("dhcp", parm)) + add_obp_parm(ofwdev, OBP_PARAM_DHCP, addr); + else if (!strcmp("giaddr", parm)) + add_obp_parm(ofwdev, OBP_PARAM_GIADDR, addr); + else if (!strcmp("isns", parm)) + add_obp_parm(ofwdev, OBP_PARAM_ISNS, addr); + else if (!strcmp("siaddr", parm)) + add_obp_parm(ofwdev, OBP_PARAM_SIADDR, addr); + else if (!strcmp("slp", parm)) + add_obp_parm(ofwdev, OBP_PARAM_SLP, addr); + else if (!strcmp("subnet-mask", parm)) + add_obp_parm(ofwdev, OBP_PARAM_SUBNET_MASK, addr); + else + printf("%s: %s UNKNOWN\n", __func__, parm); +} + +void obp_parm_iqn(struct ofw_dev *ofwdev, const char *parm, const char *iqn) +{ + if (!strcmp("itname", parm)) + add_obp_parm(ofwdev, OBP_PARAM_ITNAME, iqn); + else if (!strcmp("iname", parm)) + add_obp_parm(ofwdev, OBP_PARAM_INAME, iqn); + else + printf("%s: %s UNKNOWN\n", __func__, parm); +} + +void obp_parm_hexnum(struct ofw_dev *ofwdev, const char *parm, + const char *numstr) +{ + if (!strcmp("bootp-retries", parm)) + add_obp_parm(ofwdev, OBP_PARAM_BOOTP_RETRIES, numstr); + else if (!strcmp("tftp-retries", parm)) + add_obp_parm(ofwdev, OBP_PARAM_TFTP_RETRIES, numstr); + else if (!strcmp("iport", parm)) + add_obp_parm(ofwdev, OBP_PARAM_IPORT, numstr); + else if (!strcmp("ilun", parm)) + add_obp_parm(ofwdev, OBP_PARAM_ILUN, numstr); + else if (!strcmp("isid", parm)) + add_obp_parm(ofwdev, OBP_PARAM_ISID, numstr); + else + printf("%s: %s UNKNOWN <%s>\n", __func__, parm, numstr); +} + +void obp_parm_str(struct ofw_dev *ofwdev, const char *parm, const char *str) +{ + if (!strcmp("filename", parm)) + add_obp_parm(ofwdev, OBP_PARAM_FILENAME, str); + else if (!strcmp("ichapid", parm)) + add_obp_parm(ofwdev, OBP_PARAM_ICHAPID, str); + else if (!strcmp("ichappw", parm)) + add_obp_parm(ofwdev, OBP_PARAM_ICHAPPW, str); + else if (!strcmp("chapid", parm)) + add_obp_parm(ofwdev, OBP_PARAM_CHAPID, str); + else if (!strcmp("chappw", parm)) + add_obp_parm(ofwdev, OBP_PARAM_CHAPPW, str); + else + printf("%s: %s UNKNOWN <%s>\n", __func__, parm, str); +} + +void yyerror(struct ofw_dev *ofwdev, const char *msg) +{ + fprintf(stderr, "%s: error in <%s> at l%d.c%d\n", "fwparam_ppc", + ofwdev->prop_path, yylloc.last_line, yylloc.last_column); +} + +static int parse_params(const char *buf, struct ofw_dev *ofwdev) +{ + int error = 0; +#if YYDEBUG + yydebug = 1; +#endif + + + if (yy_scan_string(buf)) + error = yyparse(ofwdev); + + return error; +} + +static int find_file(const char *filename) +{ + int error, fd; + struct stat bootpath_stat; + + error = stat(filename, &bootpath_stat); + if (error < 0) { + fprintf(stderr, "%s: stat %s, %s\n", __func__, filename, + strerror(errno)); + return error; + } + + bootpath_val = malloc(bootpath_stat.st_size); + if (!bootpath_val) { + error = ENOMEM; + fprintf(stderr, "%s: Could not open %s: %s (%d)\n", + __func__, filename, strerror(error), error); + return -1; + } + + fd = open(filename, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "%s: Could not open %s: %s (%d)\n", + __func__, filename, strerror(errno), errno); + free(bootpath_val); + return -1; + } + + bytes_read = read(fd, bootpath_val, bootpath_stat.st_size); + close(fd); + free(bootpath_val); + if (bytes_read != bootpath_stat.st_size) { + fprintf(stderr, "%s: Could not open %s: %s (%d)\n", + __func__, filename, strerror(EIO), EIO); + return -1; + } + + return 1; +} + +static int find_nics(const char *fpath, const struct stat *sb, int tflag, + struct FTW *ftw) +{ + if (tflag == FTW_D && + (strstr(fpath + ftw->base, "iscsi-toe") || + strstr(fpath + ftw->base, "ethernet"))) { + + if (nic_count < OFWDEV_MAX) + niclist[nic_count++] = strdup(fpath + devtree_offset); + + } + return 0; +} + +static int nic_cmp(const void *a, const void *b) +{ + return strcmp(a, b); +} + +static int find_initiator(const char *fpath, const struct stat *sb, int tflag, + struct FTW *ftw) +{ + struct ofw_dev *dev; + + if (tflag == FTW_F && (strstr(fpath + ftw->base, + "/aliases/iscsi-disk"))) { + + if (dev_count < OFWDEV_MAX) { + ofwdevs[dev_count++] = dev = + calloc(sizeof(struct ofw_dev), 1); + dev->prop_path = strdup(fpath + devtree_offset); + } + } + return 0; +} + +static int loop_devs(const char *devtree) +{ + int error; + int i; + char prefix[256]; + + nic_count = 0; + error = nftw(devtree, find_nics, 20, 0); + if (error) + return error; + + /* + * Sort the nics into "natural" order. The proc fs + * device-tree has them in somewhat random, or reversed order. + */ + qsort(niclist, nic_count, sizeof(char *), nic_cmp); + + snprintf(prefix, sizeof(prefix), "%s/%s", devtree, "aliases"); + dev_count = 0; + error = nftw(prefix, find_initiator, 20, 0); + if (error) + return error; + + for (i = 0; i < dev_count; i++) { + snprintf(prefix, sizeof(prefix), "%s%s", devtree, + ofwdevs[i]->prop_path); + if (find_file(prefix) > 0) { + error = parse_params(bootpath_val, ofwdevs[i]); + if (!error) + error = locate_mac(devtree, ofwdevs[i]); + + } + } + return error; +} + +#define set_context(fld,abrv,item) \ + cp_param(context->fld, (abrv), ofwdev, (item), sizeof(context->fld)) +#define set_int_context(fld,abrv,item) \ + cp_int_param(&context->fld, (abrv), ofwdev, (item)) + +static void fill_context(struct boot_context *context, struct ofw_dev *ofwdev) +{ + int ndx; + + memset(context, 0, sizeof(*context)); + + set_context(initiatorname, "NAME", OBP_PARAM_ITNAME); + + snprintf(context->mac, sizeof(context->mac), + "%02x:%02x:%02x:%02x:%02x:%02x", + ofwdev->mac[0], ofwdev->mac[1], ofwdev->mac[2], + ofwdev->mac[3], ofwdev->mac[4], ofwdev->mac[5]); + + /* + * nic parameters + */ + for (ndx = 0; ndx < nic_count; ndx++) { + if (!strcmp(niclist[ndx], ofwdev->dev_path)) { + snprintf(context->iface, sizeof(context->iface), + "eth%d", ndx); + break; + } + } + + set_context(ipaddr, "IPADDR", OBP_PARAM_CIADDR); + set_context(mask, "MASK", OBP_PARAM_SUBNET_MASK); + + /* + * target parameters + */ + set_context(target_ipaddr, "IPADDR", OBP_PARAM_SIADDR); + set_int_context(target_port, "PORT", OBP_PARAM_IPORT); + set_context(lun, "LUN", OBP_PARAM_ILUN); + set_context(targetname, "NAME", OBP_PARAM_INAME); + set_context(isid, "ISID", OBP_PARAM_ISID); + + /* + * chap stuff is always associated with the target + */ + set_context(chap_name, "CHAP_NAME", OBP_PARAM_ICHAPID); + set_context(chap_password, "CHAP_PASSWORD", OBP_PARAM_ICHAPPW); + set_context(chap_name_in, "CHAP_NAME_IN", OBP_PARAM_CHAPID); + set_context(chap_password_in, "CHAP_PASSWORD_IN", OBP_PARAM_CHAPPW); + +} + +int fwparam_ppc_boot_info(struct boot_context *context) +{ + char filename[FILENAMESZ]; + int error; + char *devtree; + + /* + * For powerpc, our operations are fundamentally to locate + * either the one boot target (the singleton disk), or to find + * the nics that support iscsi boot. The only nics in IBM + * systems that can support iscsi are the ones that provide + * the appropriate FCODE with a load method. + */ + memset(filename, 0, FILENAMESZ); + snprintf(filename, FILENAMESZ, "%s%s", DT_TOP, BOOTPATH); + + if (debug) + fprintf(stderr, "%s: file:%s; debug:%d\n", __func__, filename, + debug); + + devtree = find_devtree(filename); + if (!devtree) + return ISCSI_ERR_INVAL; + + /* + * Always search the device-tree to find the capable nic devices. + */ + error = loop_devs(devtree); + if (error) + goto free_devtree; + + if (find_file(filename) < 1) + error = ISCSI_ERR_NO_OBJS_FOUND; + else { + if (debug) + printf("%s:\n%s\n\n", filename, bootpath_val); + /* + * We find *almost* everything we need in the + * bootpath, save the mac-address. + */ + + if (!strstr(bootpath_val, "iscsi")) { + error = ISCSI_ERR_INVAL; + goto free_devtree; + } + ofwdevs[0] = calloc(1, sizeof(struct ofw_dev)); + if (!ofwdevs[0]) { + error = ISCSI_ERR_NOMEM; + goto free_devtree; + } + + error = parse_params(bootpath_val, ofwdevs[0]); + if (!error) + error = locate_mac(devtree, ofwdevs[0]); + if (!error) { + if (!context) + error = ISCSI_ERR_NOMEM; + else + fill_context(context, ofwdevs[0]); + } + free(ofwdevs[0]); + } + +free_devtree: + free(devtree); + return error; +} + +/* + * Due to lack of time this is just fwparam_ppc_boot_info which + * adds the target used for boot to the list. It does not add + * all possible targets (IBM please add). + */ +int fwparam_ppc_get_targets(struct list_head *list) +{ + char filename[FILENAMESZ]; + struct boot_context *context; + int error; + char *devtree; + + /* + * For powerpc, our operations are fundamentally to locate + * either the one boot target (the singleton disk), or to find + * the nics that support iscsi boot. The only nics in IBM + * systems that can support iscsi are the ones that provide + * the appropriate FCODE with a load method. + */ + memset(filename, 0, FILENAMESZ); + snprintf(filename, FILENAMESZ, "%s%s", DT_TOP, BOOTPATH); + + if (debug) + fprintf(stderr, "%s: file:%s; debug:%d\n", __func__, filename, + debug); + + devtree = find_devtree(filename); + if (!devtree) + return ISCSI_ERR_INVAL; + + /* + * Always search the device-tree to find the capable nic devices. + */ + error = loop_devs(devtree); + if (error) + goto free_devtree; + + if (find_file(filename) < 1) + error = ISCSI_ERR_NO_OBJS_FOUND; + else { + if (debug) + printf("%s:\n%s\n\n", filename, bootpath_val); + /* + * We find *almost* everything we need in the + * bootpath, save the mac-address. + */ + + if (!strstr(bootpath_val, "iscsi")) { + error = ISCSI_ERR_INVAL; + goto free_devtree; + } + ofwdevs[0] = calloc(1, sizeof(struct ofw_dev)); + if (!ofwdevs[0]) { + error = ISCSI_ERR_NOMEM; + goto free_devtree; + } + + error = parse_params(bootpath_val, ofwdevs[0]); + if (!error) + error = locate_mac(devtree, ofwdevs[0]); + if (!error) { + context = calloc(1, sizeof(*context)); + if (!context) + error = ISCSI_ERR_NOMEM; + else { + fill_context(context, ofwdevs[0]); + list_add_tail(&context->list, list); + } + } + free(ofwdevs[0]); + } + +free_devtree: + free(devtree); + return error; +} diff --git a/utils/fwparam_ibft/fwparam_sysfs.c b/utils/fwparam_ibft/fwparam_sysfs.c new file mode 100644 index 0000000..a0cd1c7 --- /dev/null +++ b/utils/fwparam_ibft/fwparam_sysfs.c @@ -0,0 +1,528 @@ +/* + * Copyright (C) IBM Corporation. 2007 + * Author: Konrad Rzeszutek + * Copyright (C) Red Hat, Inc. All rights reserved. 2008 - 2010 + * Copyright (C) Mike Christie 2008 - 2010 + * + * 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, see . + */ + +#define _XOPEN_SOURCE 500 +#define _DEFAULT_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sysfs.h" +#include "fw_context.h" +#include "fwparam.h" +#include "sysdeps.h" +#include "iscsi_net_util.h" +#include "iscsi_err.h" + +#define ISCSI_BOOT_MAX 255 +#define IBFT_SYSFS_ROOT "/sys/firmware/ibft/" +#define IBFT_SUBSYS "ibft" + +#define ISCSI_LLD_ROOT "/sys/firmware/" +#define ISCSI_LLD_SUBSYS_PREFIX "iscsi_boot" + +static char *target_list[ISCSI_BOOT_MAX]; +static char *nic_list[ISCSI_BOOT_MAX]; +static int nic_cnt; +static int tgt_cnt; + +static int file_exist(const char *file) +{ + struct stat bootpath_stat; + + return !stat(file, &bootpath_stat); +} + +/* + * Finds the etherrnetX and targetX under the sysfs directory. + */ +static int find_sysfs_dirs(const char *fpath, const struct stat *sb, + int tflag, struct FTW *ftw) +{ + if (tflag == FTW_D && (strstr(fpath + ftw->base, "target"))) { + if (tgt_cnt == ISCSI_BOOT_MAX) { + printf("Too many targets found in iSCSI boot data." + "Max number of targets %d\n", ISCSI_BOOT_MAX); + return 0; + } + target_list[tgt_cnt++] = strdup(strstr(fpath, "target")); + } + + if (tflag == FTW_D && (strstr(fpath + ftw->base, "ethernet"))) { + if (nic_cnt == ISCSI_BOOT_MAX) { + printf("Too many nics found in iSCSI boot data." + "Max number of nics %d\n", ISCSI_BOOT_MAX); + return 0; + } + nic_list[nic_cnt++] = strdup(strstr(fpath, "ethernet")); + } + + return 0; +} + +static int get_iface_from_device(char *id, struct boot_context *context) +{ + char dev_dir[FILENAMESZ]; + int rc = ENODEV; + DIR *dirfd; + struct dirent *dent; + + memset(dev_dir, 0, FILENAMESZ); + snprintf(dev_dir, FILENAMESZ, IBFT_SYSFS_ROOT"/%s/device", id); + + if (!file_exist(dev_dir)) + return 0; + + dirfd = opendir(dev_dir); + if (!dirfd) + return errno; + + while ((dent = readdir(dirfd))) { + if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..") || + strncmp(dent->d_name, "net:", 4)) + continue; + + if (!strncmp(dent->d_name, "net:", 4)) { + if ((strlen(dent->d_name) - 4) > + (sizeof(context->iface) - 1)) { + rc = EINVAL; + printf("Net device %s too big for iface " + "buffer.\n", dent->d_name); + break; + } + + if (sscanf(dent->d_name, "net:%s", context->iface) != 1) + rc = EINVAL; + rc = 0; + break; + } else { + printf("Could not read ethernet to net link.\n"); + rc = EOPNOTSUPP; + break; + } + } + + closedir(dirfd); + + if (rc != ENODEV) + return rc; + + /* If not found try again with newer kernel networkdev sysfs layout */ + strlcat(dev_dir, "/net", FILENAMESZ); + + if (!file_exist(dev_dir)) + return rc; + + dirfd = opendir(dev_dir); + if (!dirfd) + return errno; + + while ((dent = readdir(dirfd))) { + if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) + continue; + + /* Take the first "regular" directory entry */ + if (strlen(dent->d_name) > (sizeof(context->iface) - 1)) { + rc = EINVAL; + printf("Net device %s too big for iface buffer.\n", + dent->d_name); + break; + } + + strcpy(context->iface, dent->d_name); + rc = 0; + break; + } + + closedir(dirfd); + return rc; +} + +/* + * Routines to fill in the context values. + */ +static int fill_nic_context(char *subsys, char *id, + struct boot_context *context) +{ + int rc; + + rc = sysfs_get_int(id, subsys, "flags", &context->nic_flags); + /* + * Per spec we would need to check against Bit 0 + * (Block Valid Flag), but some firmware only + * sets Bit 1 (Firmware Booting Selected). + * So any setting is deemed okay. + */ + if (!rc && (context->nic_flags == 0)) + rc = ENODEV; + if (rc) + return rc; + + rc = sysfs_get_str(id, subsys, "mac", context->mac, + sizeof(context->mac)); + if (rc) + return rc; + + /* + * Some offload cards like bnx2i use different MACs for the net and + * iscsi functions, so we have to follow the sysfs links. + * + * Other ibft implementations may not be tied to a pci function, + * so there will not be any device/net link, so we drop down to + * the MAC matching. + * + * And finally, some cards like be2iscsi and qla4xxx do not have + * any linux network subsys representation. These hosts will + * not have the ibft subsys. Instead the subsys is the scsi host + * number. + */ + if (!strcmp(IBFT_SUBSYS, subsys)) { + rc = get_iface_from_device(id, context); + if (rc) { + rc = net_get_netdev_from_hwaddress(context->mac, + context->iface); + if (rc) + return rc; + } + } else + strlcpy(context->scsi_host_name, subsys, + sizeof(context->scsi_host_name)); + + memset(&context->boot_nic, 0, sizeof(context->boot_nic)); + snprintf(context->boot_nic, sizeof(context->boot_nic), "%s", id); + + sysfs_get_str(id, subsys, "ip-addr", context->ipaddr, + sizeof(context->ipaddr)); + sysfs_get_str(id, subsys, "vlan", context->vlan, + sizeof(context->vlan)); + sysfs_get_str(id, subsys, "subnet-mask", context->mask, + sizeof(context->mask)); + sysfs_get_int(id, subsys, "prefix-len", &context->prefix); + sysfs_get_str(id, subsys, "gateway", context->gateway, + sizeof(context->gateway)); + sysfs_get_str(id, subsys, "primary-dns", context->primary_dns, + sizeof(context->primary_dns)); + sysfs_get_str(id, subsys, "secondary-dns", context->secondary_dns, + sizeof(context->secondary_dns)); + sysfs_get_str(id, subsys, "dhcp", context->dhcp, + sizeof(context->dhcp)); + sysfs_get_int(id, subsys, "origin", (int *)&context->origin); + return 0; +} + +static void fill_initiator_context(char *subsys, struct boot_context *context) +{ + sysfs_get_str("initiator", subsys, "initiator-name", + context->initiatorname, + sizeof(context->initiatorname)); + sysfs_get_str("initiator", subsys, "isid", context->isid, + sizeof(context->isid)); + + strlcpy(context->boot_root, subsys, sizeof(context->boot_root)); +} +static int fill_tgt_context(char *subsys, char *id, + struct boot_context *context) +{ + int rc; + + rc = sysfs_get_int(id, subsys, "flags", &context->target_flags); + /* + * Per spec we would need to check against Bit 0 + * (Block Valid Flag), but some firmware only + * sets Bit 1 (Firmware Booting Selected). + * So any setting is deemed okay. + */ + if (!rc && (context->target_flags == 0)) + rc = ENODEV; + if (rc) + return rc; + + rc = sysfs_get_str(id, subsys, "target-name", context->targetname, + sizeof(context->targetname)); + if (rc) + return rc; + + rc = sysfs_get_str(id, subsys, "ip-addr", context->target_ipaddr, + sizeof(context->target_ipaddr)); + if (rc) + return rc; + + memset(&context->boot_target, 0, sizeof(context->boot_target)); + snprintf(context->boot_target, sizeof(context->boot_target), "%s", id); + + /* + * We can live without the rest of they do not exist. If we + * failed to get them we will figure it out when we login. + */ + if (sysfs_get_int(id, subsys, "port", &context->target_port)) + context->target_port = ISCSI_LISTEN_PORT; + + sysfs_get_str(id, subsys, "lun", context->lun, + sizeof(context->lun)); + sysfs_get_str(id, subsys, "chap-name", context->chap_name, + sizeof(context->chap_name)); + sysfs_get_str(id, subsys, "chap-secret", context->chap_password, + sizeof(context->chap_password)); + sysfs_get_str(id, subsys, "rev-chap-name", context->chap_name_in, + sizeof(context->chap_name_in)); + sysfs_get_str(id, subsys, "rev-chap-name-secret", + context->chap_password_in, + sizeof(context->chap_password_in)); + return 0; +} + +#define IBFT_SYSFS_FLAG_FW_SEL_BOOT 2 + +static int find_boot_flag(char *subsys, char *list[], ssize_t size, + int *boot_idx) +{ + int rc = ENODEV; + int i, flag = 0; + + for (i = 0; i < size; i++, flag = -1) { + rc = sysfs_get_int(list[i], subsys, "flags", &flag); + if (rc) + continue; + + if (flag & IBFT_SYSFS_FLAG_FW_SEL_BOOT) { + *boot_idx = i; + rc = 0; + break; + } + rc = ENODEV; + flag = 0; + + } + + return rc; +} + +static void deallocate_lists(void) +{ + int i; + + for (i = 0; i < nic_cnt; i++) + free(nic_list[i]); + + nic_cnt = 0; + for (i = 0; i < tgt_cnt; i++) + free(target_list[i]); + + tgt_cnt = 0; + +} + +static int get_boot_info(struct boot_context *context, char *rootdir, + char *subsys) +{ + char initiator_dir[FILENAMESZ]; + int rc = ENODEV; + int nic_idx = -1, tgt_idx = -1; + + memset(&initiator_dir, 0 , FILENAMESZ); + snprintf(initiator_dir, FILENAMESZ, "%sinitiator", rootdir); + + nic_cnt = 0; + tgt_cnt = 0; + if (file_exist(initiator_dir)) { + /* Find the targets and the ethernets */ + rc = nftw(rootdir, find_sysfs_dirs, 20, 1); + + /* Find wihch target and which ethernet have + the boot flag set. */ + rc = find_boot_flag(subsys, nic_list, nic_cnt, &nic_idx); + if (rc) + goto free; + + rc = find_boot_flag(subsys, target_list, tgt_cnt, &tgt_idx); + if (rc) + goto free; + + /* Fill in the context values */ + rc = fill_nic_context(subsys, nic_list[nic_idx], context); + rc |= fill_tgt_context(subsys, target_list[tgt_idx], context); + fill_initiator_context(subsys, context); + } +free: + deallocate_lists(); + return rc; +} + +int fwparam_sysfs_boot_info(struct boot_context *context) +{ + struct dirent *dent; + DIR *dirfd; + int rc = 0; + + if (!get_boot_info(context, IBFT_SYSFS_ROOT, IBFT_SUBSYS)) + return 0; + /* + * We could have multiple iscsi llds and each lld could have + * multiple targets/ethernet ports + */ + dirfd = opendir(ISCSI_LLD_ROOT); + if (!dirfd) + return ISCSI_ERR_SYSFS_LOOKUP; + + while ((dent = readdir(dirfd))) { + char lld_root[FILENAMESZ]; + + memset(&lld_root, 0 , FILENAMESZ); + + if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) + continue; + + if (strncmp(dent->d_name, ISCSI_LLD_SUBSYS_PREFIX, 10)) + continue; + + snprintf(lld_root, FILENAMESZ, ISCSI_LLD_ROOT"%s/", + dent->d_name); + if (!get_boot_info(context, lld_root, dent->d_name)) + goto done; + } + rc = ISCSI_ERR_NO_OBJS_FOUND; +done: + closedir(dirfd); + return rc; +} + +static int get_targets(struct list_head *list, char *rootdir, char *subsys) +{ + struct boot_context *context; + int rc = 0, i, nic_idx, nic; + char initiator_dir[FILENAMESZ]; + + memset(&initiator_dir, 0 , FILENAMESZ); + snprintf(initiator_dir, FILENAMESZ, "%sinitiator", rootdir); + + if (!file_exist(initiator_dir)) + return ENODEV; + + nic_cnt = 0; + tgt_cnt = 0; + + /* Find the targets and the ethernets */ + nftw(rootdir, find_sysfs_dirs, 20, 1); + for (i = 0; i < tgt_cnt; i++) { + context = calloc(1, sizeof(*context)); + if (!context) { + rc = ENOMEM; + break; + } + + rc = fill_tgt_context(subsys, target_list[i], context); + if (rc) + goto cleanup; + + rc = sysfs_get_int(target_list[i], subsys, "nic-assoc", + &nic_idx); + if (rc) + goto cleanup; + + for (nic = 0; nic < nic_cnt; nic++) { + int id; + + rc = sysfs_get_int(nic_list[nic], subsys, "index", + &id); + if (!rc && (id == nic_idx)) + break; + } + + if (nic == nic_cnt) { + printf("Invalid nic-assoc of %d. Max id %d.\n", + nic_idx, nic_cnt); + goto cleanup; + } + + rc = fill_nic_context(subsys, nic_list[nic], context); + if (rc) + goto cleanup; + + fill_initiator_context(subsys, context); + list_add_tail(&context->list, list); + continue; +cleanup: + free(context); + context = NULL; + } + + if (rc) { + if (context) + free(context); + /* + * If there are some valid targets return them. Most likely, + * the driver/ibft-implementation reported partial info + * for targets/initiators that were not used for boot. + */ + if (!list_empty(list)) + rc = 0; + } + + deallocate_lists(); + return rc; +} + +int fwparam_sysfs_get_targets(struct list_head *list) +{ + struct dirent *dent; + DIR *dirfd; + int rc = 0; + + /* ibft only has one instance */ + get_targets(list, IBFT_SYSFS_ROOT, IBFT_SUBSYS); + /* + * We could have multiple iscsi llds and each lld could have + * multiple targets/ethernet ports + */ + dirfd = opendir(ISCSI_LLD_ROOT); + if (!dirfd) { + rc = ISCSI_ERR_SYSFS_LOOKUP; + goto done; + } + + while ((dent = readdir(dirfd))) { + char lld_root[FILENAMESZ]; + + memset(&lld_root, 0 , FILENAMESZ); + if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) + continue; + + if (strncmp(dent->d_name, ISCSI_LLD_SUBSYS_PREFIX, 10)) + continue; + + snprintf(lld_root, FILENAMESZ, ISCSI_LLD_ROOT"%s/", + dent->d_name); + get_targets(list, lld_root, dent->d_name); + } + closedir(dirfd); +done: + if (!rc && list_empty(list)) + rc = ISCSI_ERR_NO_OBJS_FOUND; + if (rc) + fw_free_targets(list); + return rc; +} diff --git a/utils/fwparam_ibft/iscsi_obp.h b/utils/fwparam_ibft/iscsi_obp.h new file mode 100644 index 0000000..8580052 --- /dev/null +++ b/utils/fwparam_ibft/iscsi_obp.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) IBM Corporation. 2007 + * Author: Doug Maxey + * + * 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, see . + */ + +#ifndef ISCSI_OBP_H_ +#define ISCSI_OBP_H_ + +enum ofw_dev_type { + OFW_DT_NONE, + OFW_DT_BLOCK, + OFW_DT_NETWORK, + OFW_DT_ISCSI, +}; + +enum obp_tftp_qual { + OBP_QUAL_NONE, + OBP_QUAL_BOOTP, + OBP_QUAL_DHCPV6, + OBP_QUAL_IPV6, + OBP_QUAL_ISCSI, + OBP_QUAL_PING, + OBP_QUAL_COUNT, /* Numnber of defined OBP qualifiers */ +}; + +enum obp_param { + /* + * Defined iscsi boot parameters. + */ + OBP_PARAM_NONE, + OBP_PARAM_BLKSIZE, /* default is 512 */ + OBP_PARAM_BOOTP_RETRIES, /* default 5 */ + OBP_PARAM_CHAPID, /* target chap id */ + OBP_PARAM_CHAPPW, /* target chap password */ + OBP_PARAM_CIADDR, /* client (my) ip addr */ + OBP_PARAM_DHCP, /* dhcp server address */ + OBP_PARAM_FILENAME, /* boot filename */ + OBP_PARAM_GIADDR, /* gateway addr */ + OBP_PARAM_ICHAPID, /* initiator chapid */ + OBP_PARAM_ICHAPPW, /* initiator chap password */ + OBP_PARAM_ILUN, /* misnomer, really the target lun */ + OBP_PARAM_INAME, /* NB: target iqn */ + OBP_PARAM_IPORT, /* initiator port, defaults to 3260 */ + OBP_PARAM_ISID, /* session id */ + OBP_PARAM_ISNS, /* sns server address */ + OBP_PARAM_ITNAME, /* NB: Initiator iqn */ + OBP_PARAM_SIADDR, /* iscsi server ip address. */ + OBP_PARAM_SLP, /* slp server address */ + OBP_PARAM_SUBNET_MASK, + OBP_PARAM_TFTP_RETRIES, /* default 5 */ + OBP_PARAM_TIMEOUT, /* ping timeout period. */ + + OBP_PARAM_COUNT, /* number of defined OBP_PARAMs */ +}; + +struct ofw_obp_param { + unsigned char len; /* length of value string. */ + char val[1]; /* string value from the property */ +}; + +struct ofw_dev { + char *prop_path; /* where we found these properties. */ + enum ofw_dev_type type; /* known type of boot device. */ + int qual_count; /* count of qualifiers. */ + enum obp_tftp_qual quals[OBP_QUAL_COUNT]; + struct ofw_obp_param *param[OBP_PARAM_COUNT]; + int cfg_part; /* boot partition number. */ + char *dev_path; /* path to this ofw device. */ + unsigned char mac[6]; /* The binary mac address. */ +}; + +const char *obp_qual_set(struct ofw_dev *ofwdev, const char *qual); +void add_obp_parm(struct ofw_dev *ofwdev, enum obp_param parm, const char *str); +void obp_parm_addr(struct ofw_dev *ofwdev, const char *parm, const char *addr); +void obp_parm_iqn(struct ofw_dev *ofwdev, const char *parm, const char *iqn); +void obp_parm_hexnum(struct ofw_dev *ofwdev, const char *parm, + const char *numstr); +void obp_parm_str(struct ofw_dev *ofwdev, const char *parm, const char *str); + +#endif /* ISCSI_OBP_H_ */ diff --git a/utils/fwparam_ibft/prom_lex.c b/utils/fwparam_ibft/prom_lex.c new file mode 100644 index 0000000..c8ed9cb --- /dev/null +++ b/utils/fwparam_ibft/prom_lex.c @@ -0,0 +1,2298 @@ + +#line 3 "" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 5 +#define YY_FLEX_SUBMINOR_VERSION 35 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +#ifdef __cplusplus + +/* The "const" storage-class-modifier is valid. */ +#define YY_USE_CONST + +#else /* ! __cplusplus */ + +/* C99 requires __STDC__ to be defined as 1. */ +#if defined (__STDC__) + +#define YY_USE_CONST + +#endif /* defined (__STDC__) */ +#endif /* ! __cplusplus */ + +#ifdef YY_USE_CONST +#define yyconst const +#else +#define yyconst +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an unsigned + * integer for use as an array index. If the signed char is negative, + * we want to instead treat it as an 8-bit unsigned char, hence the + * double cast. + */ +#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN (yy_start) = 1 + 2 * + +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START (((yy_start) - 1) / 2) +#define YYSTATE YY_START + +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) + +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart(yyin ) + +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +extern int yyleng; + +extern FILE *yyin, *yyout; + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = (yy_hold_char); \ + YY_RESTORE_YY_MORE_OFFSET \ + (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) + +#define unput(c) yyunput( c, (yytext_ptr) ) + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + yy_size_t yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* Stack of input buffers. */ +static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */ +static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */ +static YY_BUFFER_STATE * yy_buffer_stack = 0; /**< Stack as an array. */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \ + ? (yy_buffer_stack)[(yy_buffer_stack_top)] \ + : NULL) + +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)] + +/* yy_hold_char holds the character lost when yytext is formed. */ +static char yy_hold_char; +static int yy_n_chars; /* number of characters read into yy_ch_buf */ +int yyleng; + +/* Points to current character in buffer. */ +static char *yy_c_buf_p = (char *) 0; +static int yy_init = 0; /* whether we need to initialize */ +static int yy_start = 0; /* start state number */ + +/* Flag which is used to allow yywrap()'s to do buffer switches + * instead of setting up a fresh yyin. A bit of a hack ... + */ +static int yy_did_buffer_switch_on_eof; + +void yyrestart (FILE *input_file ); +void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ); +YY_BUFFER_STATE yy_create_buffer (FILE *file,int size ); +void yy_delete_buffer (YY_BUFFER_STATE b ); +void yy_flush_buffer (YY_BUFFER_STATE b ); +void yypush_buffer_state (YY_BUFFER_STATE new_buffer ); +void yypop_buffer_state (void ); + +static void yyensure_buffer_stack (void ); +static void yy_load_buffer_state (void ); +static void yy_init_buffer (YY_BUFFER_STATE b,FILE *file ); + +#define YY_FLUSH_BUFFER yy_flush_buffer(YY_CURRENT_BUFFER ) + +YY_BUFFER_STATE yy_scan_buffer (char *base,yy_size_t size ); +YY_BUFFER_STATE yy_scan_string (yyconst char *yy_str ); +YY_BUFFER_STATE yy_scan_bytes (yyconst char *bytes,int len ); + +void *yyalloc (yy_size_t ); +void *yyrealloc (void *,yy_size_t ); +void yyfree (void * ); + +#define yy_new_buffer yy_create_buffer + +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer(yyin,YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } + +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer(yyin,YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } + +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +#define yywrap(n) 1 +#define YY_SKIP_YYWRAP + +typedef unsigned char YY_CHAR; + +FILE *yyin = (FILE *) 0, *yyout = (FILE *) 0; + +typedef int yy_state_type; + +extern int yylineno; + +int yylineno = 1; + +extern char yytext[]; + +static yy_state_type yy_get_previous_state (void ); +static yy_state_type yy_try_NUL_trans (yy_state_type current_state ); +static int yy_get_next_buffer (void ); +static void yy_fatal_error (yyconst char msg[] ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + (yytext_ptr) = yy_bp; \ + yyleng = (size_t) (yy_cp - yy_bp); \ + (yy_hold_char) = *yy_cp; \ + *yy_cp = '\0'; \ + if ( yyleng >= YYLMAX ) \ + YY_FATAL_ERROR( "token too large, exceeds YYLMAX" ); \ + yy_flex_strncpy( yytext, (yytext_ptr), yyleng + 1 ); \ + (yy_c_buf_p) = yy_cp; + +#define YY_NUM_RULES 17 +#define YY_END_OF_BUFFER 18 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static yyconst flex_int16_t yy_accept[444] = + { 0, + 0, 0, 18, 16, 15, 15, 12, 12, 16, 12, + 12, 12, 12, 12, 12, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 15, 0, 12, 12, 14, + 0, 0, 0, 12, 0, 0, 12, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, + 0, 0, 0, 0, 0, 0, 12, 12, 14, 7, + 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, + + 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, + 11, 0, 0, 0, 0, 0, 0, 0, 6, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 13, 0, 0, 6, 0, 0, 0, 0, 0, 0, + 0, 3, 0, 9, 6, 0, 0, 5, 0, 0, + 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, + 0, 0, 0, 9, 0, 0, 0, 0, 0, 8, + 0, 13, 0, 0, 0, 0, 0, 9, 0, 0, + 0, 0, 0, 0, 2, 8, 13, 1, 0, 9, + 0, 0, 0, 0, 0, 0, 8, 13, 0, 9, + + 0, 0, 10, 0, 0, 0, 13, 0, 9, 0, + 0, 0, 0, 0, 13, 0, 9, 0, 0, 0, + 13, 0, 9, 0, 0, 13, 9, 0, 0, 13, + 9, 13, 9, 13, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 0 + } ; + +static yyconst flex_int32_t yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 4, 5, 1, 6, 6, 7, + 6, 6, 6, 8, 6, 6, 6, 9, 1, 1, + 1, 1, 1, 1, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 1, 12, 1, 1, 1, 1, 13, 14, 15, 16, + + 17, 18, 19, 20, 21, 11, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 11, + 11, 35, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +static yyconst flex_int32_t yy_meta[36] = + { 0, + 1, 1, 1, 2, 3, 4, 4, 4, 5, 4, + 2, 6, 4, 4, 4, 4, 4, 4, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2 + } ; + +static yyconst flex_int16_t yy_base[680] = + { 0, + 0, 0, 1198, 1199, 34, 36, 35, 1192, 0, 39, + 40, 41, 47, 42, 44, 29, 67, 1176, 1182, 1179, + 1180, 86, 1174, 1161, 1174, 51, 69, 73, 1184, 0, + 1175, 1165, 1160, 43, 1172, 1171, 51, 1168, 1152, 1161, + 1157, 1166, 1163, 1162, 1156, 1158, 1142, 1160, 25, 1147, + 85, 1146, 1153, 1139, 1147, 1133, 1135, 1135, 1199, 1151, + 1136, 1148, 1130, 1146, 1142, 80, 1153, 1152, 0, 1199, + 1126, 1124, 1128, 1126, 1136, 1199, 1124, 1128, 1132, 1131, + 1131, 1116, 1132, 1119, 1119, 1113, 1133, 1135, 1109, 1122, + 1107, 1123, 1122, 1130, 1112, 1119, 1110, 1114, 1199, 1104, + + 1101, 1094, 97, 106, 0, 1105, 42, 1101, 94, 1108, + 1090, 1093, 1096, 1104, 1098, 1091, 1100, 1085, 1199, 0, + 1094, 1090, 1099, 1086, 1095, 1093, 1105, 1087, 117, 1102, + 0, 1071, 1076, 104, 1088, 1069, 1073, 1093, 1075, 1086, + 1069, 1199, 99, 0, 1093, 1079, 1069, 1199, 1065, 1062, + 1063, 1076, 121, 125, 0, 1073, 1070, 1059, 1056, 1069, + 1061, 1068, 1049, 0, 120, 1056, 1077, 1063, 1062, 131, + 1073, 0, 1047, 1059, 1055, 1043, 1056, 0, 1046, 1050, + 1044, 1038, 1044, 1036, 1199, 134, 0, 1199, 1035, 0, + 1039, 1034, 1046, 1046, 1048, 1031, 1199, 0, 1030, 0, + + 1027, 1035, 1199, 1039, 1025, 1033, 0, 1032, 0, 1039, + 137, 1018, 1028, 1032, 0, 1031, 0, 1018, 1025, 1015, + 0, 1014, 0, 145, 142, 0, 0, 120, 132, 0, + 0, 0, 0, 1199, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1199, 1199, 149, 152, 156, 159, 163, 144, 166, + 143, 170, 142, 174, 131, 178, 115, 182, 112, 186, + 92, 190, 89, 194, 87, 198, 85, 202, 67, 206, + 56, 210, 214, 218, 222, 226, 230, 234, 238, 242, + 246, 250, 254, 258, 262, 266, 270, 274, 278, 282, + 286, 290, 294, 298, 302, 306, 310, 314, 318, 322, + + 326, 330, 334, 338, 342, 346, 350, 354, 358, 362, + 366, 370, 374, 378, 382, 386, 390, 394, 398, 402, + 406, 410, 414, 418, 422, 426, 430, 434, 438, 442, + 446, 450, 454, 458, 462, 466, 470, 474, 478, 482, + 486, 490, 494, 498, 502, 506, 510, 514, 518, 522, + 526, 530, 534, 538, 542, 546, 550, 554, 558, 562, + 566, 570, 574, 578, 582, 586, 590, 594, 598, 602, + 606, 610, 614, 618, 622, 626, 630, 634, 638, 642, + 646, 650, 654, 658, 662, 666, 670, 674, 678, 682, + 686, 690, 694, 698, 702, 706, 710, 714, 718, 722, + + 726, 730, 734, 738, 742, 746, 750, 754, 758, 762, + 766, 770, 774, 778, 782, 786, 790, 794, 798, 802, + 806, 810, 814, 818, 822, 826, 830, 834, 838, 842, + 846, 850, 854, 858, 862, 866, 870, 874, 878, 882, + 886, 890, 894, 898, 902, 906, 910, 914, 918, 922, + 926, 930, 934, 938, 942, 946, 950, 954, 958, 962, + 966, 970, 974, 978, 982, 986, 990, 994, 998, 1002, + 1006, 1010, 1014, 1018, 1022, 1026, 1030, 1034, 1038 + } ; + +static yyconst flex_int16_t yy_def[680] = + { 0, + 443, 1, 443, 443, 443, 443, 444, 444, 445, 444, + 444, 444, 444, 444, 444, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 446, 446, 447, + 443, 443, 443, 446, 443, 443, 446, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 448, 448, 447, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + + 443, 443, 443, 443, 449, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 450, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 451, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 452, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 453, 443, 443, 443, 443, 443, + 443, 443, 443, 454, 443, 443, 443, 443, 443, 443, + 443, 455, 443, 443, 443, 443, 443, 456, 443, 443, + 443, 443, 443, 443, 443, 443, 457, 443, 443, 458, + 443, 443, 443, 443, 443, 443, 443, 459, 443, 460, + + 443, 443, 443, 443, 443, 443, 461, 443, 462, 443, + 443, 443, 443, 443, 463, 443, 464, 443, 443, 443, + 465, 443, 466, 443, 443, 467, 468, 443, 443, 469, + 470, 471, 472, 443, 473, 474, 475, 476, 477, 478, + 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, + 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, + 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, + 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, + 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, + 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, + + 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, + 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, + 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, + 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, + 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, + 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, + 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, + 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, + 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, + 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, + + 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, + 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, + 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, + 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, + 679, 443, 0, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443 + } ; + +static yyconst flex_int16_t yy_nxt[1235] = + { 0, + 4, 5, 6, 4, 4, 7, 7, 7, 4, 8, + 4, 9, 10, 11, 12, 13, 14, 15, 16, 4, + 17, 4, 18, 4, 19, 4, 20, 4, 21, 22, + 23, 24, 25, 4, 4, 26, 26, 26, 26, 27, + 28, 28, 28, 443, 443, 443, 443, 443, 443, 42, + 86, 443, 26, 26, 133, 443, 34, 87, 43, 234, + 35, 36, 32, 37, 41, 33, 38, 39, 134, 31, + 232, 73, 40, 44, 66, 66, 66, 27, 67, 67, + 67, 45, 46, 76, 103, 104, 104, 104, 230, 47, + 226, 48, 221, 49, 50, 215, 51, 52, 57, 89, + + 58, 59, 129, 129, 129, 90, 60, 158, 61, 91, + 103, 130, 130, 130, 135, 207, 159, 62, 198, 162, + 136, 153, 154, 154, 154, 163, 170, 170, 170, 153, + 171, 171, 171, 179, 187, 180, 186, 186, 186, 197, + 197, 197, 203, 203, 203, 172, 155, 131, 188, 188, + 181, 29, 29, 30, 30, 30, 229, 30, 68, 68, + 69, 69, 69, 228, 69, 105, 105, 144, 144, 144, + 144, 164, 164, 164, 164, 178, 178, 178, 178, 190, + 190, 190, 190, 200, 200, 200, 200, 209, 209, 209, + 209, 217, 217, 217, 217, 223, 223, 223, 223, 227, + + 227, 227, 227, 231, 231, 231, 231, 233, 233, 233, + 233, 235, 235, 235, 235, 236, 236, 236, 236, 237, + 237, 237, 237, 238, 238, 238, 238, 239, 239, 239, + 239, 240, 240, 240, 240, 241, 241, 241, 241, 242, + 242, 242, 242, 243, 243, 243, 243, 244, 244, 244, + 244, 245, 245, 245, 245, 246, 246, 246, 246, 247, + 247, 247, 247, 248, 248, 248, 248, 249, 249, 249, + 249, 250, 250, 250, 250, 251, 251, 251, 251, 252, + 252, 252, 252, 253, 253, 253, 253, 254, 254, 254, + 254, 255, 255, 255, 255, 256, 256, 256, 256, 257, + + 257, 257, 257, 258, 258, 258, 258, 259, 259, 259, + 259, 260, 260, 260, 260, 261, 261, 261, 261, 262, + 262, 262, 262, 263, 263, 263, 263, 264, 264, 264, + 264, 265, 265, 265, 265, 266, 266, 266, 266, 267, + 267, 267, 267, 268, 268, 268, 268, 269, 269, 269, + 269, 270, 270, 270, 270, 271, 271, 271, 271, 272, + 272, 272, 272, 273, 273, 273, 273, 274, 274, 274, + 274, 275, 275, 275, 275, 276, 276, 276, 276, 277, + 277, 277, 277, 278, 278, 278, 278, 279, 279, 279, + 279, 280, 280, 280, 280, 281, 281, 281, 281, 282, + + 282, 282, 282, 283, 283, 283, 283, 284, 284, 284, + 284, 285, 285, 285, 285, 286, 286, 286, 286, 287, + 287, 287, 287, 288, 288, 288, 288, 289, 289, 289, + 289, 290, 290, 290, 290, 291, 291, 291, 291, 292, + 292, 292, 292, 293, 293, 293, 293, 294, 294, 294, + 294, 295, 295, 295, 295, 296, 296, 296, 296, 297, + 297, 297, 297, 298, 298, 298, 298, 299, 299, 299, + 299, 300, 300, 300, 300, 301, 301, 301, 301, 302, + 302, 302, 302, 303, 303, 303, 303, 304, 304, 304, + 304, 305, 305, 305, 305, 306, 306, 306, 306, 307, + + 307, 307, 307, 308, 308, 308, 308, 309, 309, 309, + 309, 310, 310, 310, 310, 311, 311, 311, 311, 312, + 312, 312, 312, 313, 313, 313, 313, 314, 314, 314, + 314, 315, 315, 315, 315, 316, 316, 316, 316, 317, + 317, 317, 317, 318, 318, 318, 318, 319, 319, 319, + 319, 320, 320, 320, 320, 321, 321, 321, 321, 322, + 322, 322, 322, 323, 323, 323, 323, 324, 324, 324, + 324, 325, 325, 325, 325, 326, 326, 326, 326, 327, + 327, 327, 327, 328, 328, 328, 328, 329, 329, 329, + 329, 330, 330, 330, 330, 331, 331, 331, 331, 332, + + 332, 332, 332, 333, 333, 333, 333, 334, 334, 334, + 334, 335, 335, 335, 335, 336, 336, 336, 336, 337, + 337, 337, 337, 338, 338, 338, 338, 339, 339, 339, + 339, 340, 340, 340, 340, 341, 341, 341, 341, 342, + 342, 342, 342, 343, 343, 343, 343, 344, 344, 344, + 344, 345, 345, 345, 345, 346, 346, 346, 346, 347, + 347, 347, 347, 348, 348, 348, 348, 349, 349, 349, + 349, 350, 350, 350, 350, 351, 351, 351, 351, 352, + 352, 352, 352, 353, 353, 353, 353, 354, 354, 354, + 354, 355, 355, 355, 355, 356, 356, 356, 356, 357, + + 357, 357, 357, 358, 358, 358, 358, 359, 359, 359, + 359, 360, 360, 360, 360, 361, 361, 361, 361, 362, + 362, 362, 362, 363, 363, 363, 363, 364, 364, 364, + 364, 365, 365, 365, 365, 366, 366, 366, 366, 367, + 367, 367, 367, 368, 368, 368, 368, 369, 369, 369, + 369, 370, 370, 370, 370, 371, 371, 371, 371, 372, + 372, 372, 372, 373, 373, 373, 373, 374, 374, 374, + 374, 375, 375, 375, 375, 376, 376, 376, 376, 377, + 377, 377, 377, 378, 378, 378, 378, 379, 379, 379, + 379, 380, 380, 380, 380, 381, 381, 381, 381, 382, + + 382, 382, 382, 383, 383, 383, 383, 384, 384, 384, + 384, 385, 385, 385, 385, 386, 386, 386, 386, 387, + 387, 387, 387, 388, 388, 388, 388, 389, 389, 389, + 389, 390, 390, 390, 390, 391, 391, 391, 391, 392, + 392, 392, 392, 393, 393, 393, 393, 394, 394, 394, + 394, 395, 395, 395, 395, 396, 396, 396, 396, 397, + 397, 397, 397, 398, 398, 398, 398, 399, 399, 399, + 399, 400, 400, 400, 400, 401, 401, 401, 401, 402, + 402, 402, 402, 403, 403, 403, 403, 404, 404, 404, + 404, 405, 405, 405, 405, 406, 406, 406, 406, 407, + + 407, 407, 407, 408, 408, 408, 408, 409, 409, 409, + 409, 410, 410, 410, 410, 411, 411, 411, 411, 412, + 412, 412, 412, 413, 413, 413, 413, 414, 414, 414, + 414, 415, 415, 415, 415, 416, 416, 416, 416, 417, + 417, 417, 417, 418, 418, 418, 418, 419, 419, 419, + 419, 420, 420, 420, 420, 421, 421, 421, 421, 422, + 422, 422, 422, 423, 423, 423, 423, 424, 424, 424, + 424, 425, 425, 425, 425, 426, 426, 426, 426, 427, + 427, 427, 427, 428, 428, 428, 428, 429, 429, 429, + 429, 430, 430, 430, 430, 431, 431, 431, 431, 432, + + 432, 432, 432, 433, 433, 433, 433, 434, 434, 434, + 434, 435, 435, 435, 435, 436, 436, 436, 436, 437, + 437, 437, 437, 438, 438, 438, 438, 439, 439, 439, + 439, 440, 440, 440, 440, 441, 441, 441, 441, 442, + 442, 442, 442, 99, 99, 225, 224, 222, 220, 99, + 219, 218, 216, 214, 213, 212, 211, 210, 208, 206, + 205, 204, 203, 202, 201, 199, 196, 195, 194, 193, + 192, 191, 99, 59, 188, 189, 188, 153, 185, 184, + 183, 182, 99, 99, 177, 176, 175, 174, 173, 99, + 169, 168, 167, 99, 166, 99, 165, 99, 161, 160, + + 119, 99, 99, 99, 157, 156, 103, 152, 151, 150, + 149, 148, 147, 146, 145, 99, 99, 143, 142, 141, + 140, 139, 138, 137, 59, 132, 128, 127, 126, 125, + 70, 70, 124, 123, 70, 122, 99, 99, 121, 120, + 119, 118, 117, 99, 116, 115, 114, 113, 112, 59, + 111, 110, 109, 108, 107, 106, 443, 27, 102, 70, + 101, 100, 99, 98, 97, 96, 95, 70, 94, 93, + 92, 88, 85, 84, 70, 83, 70, 82, 81, 80, + 79, 78, 77, 75, 74, 72, 71, 70, 443, 65, + 64, 63, 56, 55, 54, 53, 443, 443, 3, 443, + + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443 + } ; + +static yyconst flex_int16_t yy_chk[1235] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 5, 5, 6, 6, 7, + 7, 7, 7, 10, 11, 12, 14, 34, 15, 16, + 49, 13, 26, 26, 107, 37, 12, 49, 16, 471, + 12, 12, 11, 13, 15, 11, 13, 13, 107, 10, + 469, 34, 14, 17, 27, 27, 27, 28, 28, 28, + 28, 17, 17, 37, 66, 66, 66, 66, 467, 17, + 465, 17, 463, 17, 17, 461, 17, 17, 22, 51, + + 22, 22, 103, 103, 103, 51, 22, 134, 22, 51, + 104, 104, 104, 104, 109, 459, 134, 22, 457, 143, + 109, 129, 129, 129, 129, 143, 153, 153, 153, 154, + 154, 154, 154, 165, 455, 165, 170, 170, 170, 186, + 186, 186, 211, 211, 211, 453, 451, 449, 229, 228, + 165, 444, 444, 445, 445, 445, 225, 445, 446, 446, + 447, 447, 447, 224, 447, 448, 448, 450, 450, 450, + 450, 452, 452, 452, 452, 454, 454, 454, 454, 456, + 456, 456, 456, 458, 458, 458, 458, 460, 460, 460, + 460, 462, 462, 462, 462, 464, 464, 464, 464, 466, + + 466, 466, 466, 468, 468, 468, 468, 470, 470, 470, + 470, 472, 472, 472, 472, 473, 473, 473, 473, 474, + 474, 474, 474, 475, 475, 475, 475, 476, 476, 476, + 476, 477, 477, 477, 477, 478, 478, 478, 478, 479, + 479, 479, 479, 480, 480, 480, 480, 481, 481, 481, + 481, 482, 482, 482, 482, 483, 483, 483, 483, 484, + 484, 484, 484, 485, 485, 485, 485, 486, 486, 486, + 486, 487, 487, 487, 487, 488, 488, 488, 488, 489, + 489, 489, 489, 490, 490, 490, 490, 491, 491, 491, + 491, 492, 492, 492, 492, 493, 493, 493, 493, 494, + + 494, 494, 494, 495, 495, 495, 495, 496, 496, 496, + 496, 497, 497, 497, 497, 498, 498, 498, 498, 499, + 499, 499, 499, 500, 500, 500, 500, 501, 501, 501, + 501, 502, 502, 502, 502, 503, 503, 503, 503, 504, + 504, 504, 504, 505, 505, 505, 505, 506, 506, 506, + 506, 507, 507, 507, 507, 508, 508, 508, 508, 509, + 509, 509, 509, 510, 510, 510, 510, 511, 511, 511, + 511, 512, 512, 512, 512, 513, 513, 513, 513, 514, + 514, 514, 514, 515, 515, 515, 515, 516, 516, 516, + 516, 517, 517, 517, 517, 518, 518, 518, 518, 519, + + 519, 519, 519, 520, 520, 520, 520, 521, 521, 521, + 521, 522, 522, 522, 522, 523, 523, 523, 523, 524, + 524, 524, 524, 525, 525, 525, 525, 526, 526, 526, + 526, 527, 527, 527, 527, 528, 528, 528, 528, 529, + 529, 529, 529, 530, 530, 530, 530, 531, 531, 531, + 531, 532, 532, 532, 532, 533, 533, 533, 533, 534, + 534, 534, 534, 535, 535, 535, 535, 536, 536, 536, + 536, 537, 537, 537, 537, 538, 538, 538, 538, 539, + 539, 539, 539, 540, 540, 540, 540, 541, 541, 541, + 541, 542, 542, 542, 542, 543, 543, 543, 543, 544, + + 544, 544, 544, 545, 545, 545, 545, 546, 546, 546, + 546, 547, 547, 547, 547, 548, 548, 548, 548, 549, + 549, 549, 549, 550, 550, 550, 550, 551, 551, 551, + 551, 552, 552, 552, 552, 553, 553, 553, 553, 554, + 554, 554, 554, 555, 555, 555, 555, 556, 556, 556, + 556, 557, 557, 557, 557, 558, 558, 558, 558, 559, + 559, 559, 559, 560, 560, 560, 560, 561, 561, 561, + 561, 562, 562, 562, 562, 563, 563, 563, 563, 564, + 564, 564, 564, 565, 565, 565, 565, 566, 566, 566, + 566, 567, 567, 567, 567, 568, 568, 568, 568, 569, + + 569, 569, 569, 570, 570, 570, 570, 571, 571, 571, + 571, 572, 572, 572, 572, 573, 573, 573, 573, 574, + 574, 574, 574, 575, 575, 575, 575, 576, 576, 576, + 576, 577, 577, 577, 577, 578, 578, 578, 578, 579, + 579, 579, 579, 580, 580, 580, 580, 581, 581, 581, + 581, 582, 582, 582, 582, 583, 583, 583, 583, 584, + 584, 584, 584, 585, 585, 585, 585, 586, 586, 586, + 586, 587, 587, 587, 587, 588, 588, 588, 588, 589, + 589, 589, 589, 590, 590, 590, 590, 591, 591, 591, + 591, 592, 592, 592, 592, 593, 593, 593, 593, 594, + + 594, 594, 594, 595, 595, 595, 595, 596, 596, 596, + 596, 597, 597, 597, 597, 598, 598, 598, 598, 599, + 599, 599, 599, 600, 600, 600, 600, 601, 601, 601, + 601, 602, 602, 602, 602, 603, 603, 603, 603, 604, + 604, 604, 604, 605, 605, 605, 605, 606, 606, 606, + 606, 607, 607, 607, 607, 608, 608, 608, 608, 609, + 609, 609, 609, 610, 610, 610, 610, 611, 611, 611, + 611, 612, 612, 612, 612, 613, 613, 613, 613, 614, + 614, 614, 614, 615, 615, 615, 615, 616, 616, 616, + 616, 617, 617, 617, 617, 618, 618, 618, 618, 619, + + 619, 619, 619, 620, 620, 620, 620, 621, 621, 621, + 621, 622, 622, 622, 622, 623, 623, 623, 623, 624, + 624, 624, 624, 625, 625, 625, 625, 626, 626, 626, + 626, 627, 627, 627, 627, 628, 628, 628, 628, 629, + 629, 629, 629, 630, 630, 630, 630, 631, 631, 631, + 631, 632, 632, 632, 632, 633, 633, 633, 633, 634, + 634, 634, 634, 635, 635, 635, 635, 636, 636, 636, + 636, 637, 637, 637, 637, 638, 638, 638, 638, 639, + 639, 639, 639, 640, 640, 640, 640, 641, 641, 641, + 641, 642, 642, 642, 642, 643, 643, 643, 643, 644, + + 644, 644, 644, 645, 645, 645, 645, 646, 646, 646, + 646, 647, 647, 647, 647, 648, 648, 648, 648, 649, + 649, 649, 649, 650, 650, 650, 650, 651, 651, 651, + 651, 652, 652, 652, 652, 653, 653, 653, 653, 654, + 654, 654, 654, 655, 655, 655, 655, 656, 656, 656, + 656, 657, 657, 657, 657, 658, 658, 658, 658, 659, + 659, 659, 659, 660, 660, 660, 660, 661, 661, 661, + 661, 662, 662, 662, 662, 663, 663, 663, 663, 664, + 664, 664, 664, 665, 665, 665, 665, 666, 666, 666, + 666, 667, 667, 667, 667, 668, 668, 668, 668, 669, + + 669, 669, 669, 670, 670, 670, 670, 671, 671, 671, + 671, 672, 672, 672, 672, 673, 673, 673, 673, 674, + 674, 674, 674, 675, 675, 675, 675, 676, 676, 676, + 676, 677, 677, 677, 677, 678, 678, 678, 678, 679, + 679, 679, 679, 222, 220, 219, 218, 216, 214, 213, + 212, 210, 208, 206, 205, 204, 202, 201, 199, 196, + 195, 194, 193, 192, 191, 189, 184, 183, 182, 181, + 180, 179, 177, 176, 175, 174, 173, 171, 169, 168, + 167, 166, 163, 162, 161, 160, 159, 158, 157, 156, + 152, 151, 150, 149, 147, 146, 145, 141, 140, 139, + + 138, 137, 136, 135, 133, 132, 130, 128, 127, 126, + 125, 124, 123, 122, 121, 118, 117, 116, 115, 114, + 113, 112, 111, 110, 108, 106, 102, 101, 100, 98, + 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, + 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, + 77, 75, 74, 73, 72, 71, 68, 67, 65, 64, + 63, 62, 61, 60, 58, 57, 56, 55, 54, 53, + 52, 50, 48, 47, 46, 45, 44, 43, 42, 41, + 40, 39, 38, 36, 35, 33, 32, 31, 29, 25, + 24, 23, 21, 20, 19, 18, 8, 3, 443, 443, + + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + 443, 443, 443, 443 + } ; + +static yy_state_type yy_last_accepting_state; +static char *yy_last_accepting_cpos; + +extern int yy_flex_debug; +int yy_flex_debug = 0; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +#ifndef YYLMAX +#define YYLMAX 8192 +#endif + +char yytext[YYLMAX]; +char *yytext_ptr; +#line 1 "prom_lex.l" +/* + * Copyright (C) IBM Corporation. 2007 + * Author: Doug Maxey + * + * 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, see . + */ +/* definitions */ +#line 23 "prom_lex.l" +#include "prom_parse.h" + +#undef LEXDEBUG +#ifdef LEXDEBUG +#define dbg(a) dbgprint((a)) +#else +#define dbg(a) do {} while (0) +#endif /* LEXDEBUG */ + +#define upval(d) \ + dbg(#d); \ + yylval.str[0] = 0; \ + strcat(yylval.str, yytext); \ + yylloc.first_column = yylloc.last_column; \ + yylloc.last_column += yyleng; \ + return d + +void dbgprint(const char *item) { fprintf(stderr, "%s: \"%s\" len=%d ", item, yytext, yyleng);} + +#define YY_NO_INPUT 1 +/* CHOSEN uses only boot related paths. */ +#line 975 "" + +#define INITIAL 0 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +static int yy_init_globals (void ); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy (void ); + +int yyget_debug (void ); + +void yyset_debug (int debug_flag ); + +YY_EXTRA_TYPE yyget_extra (void ); + +void yyset_extra (YY_EXTRA_TYPE user_defined ); + +FILE *yyget_in (void ); + +void yyset_in (FILE * in_str ); + +FILE *yyget_out (void ); + +void yyset_out (FILE * out_str ); + +int yyget_leng (void ); + +char *yyget_text (void ); + +int yyget_lineno (void ); + +void yyset_lineno (int line_number ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap (void ); +#else +extern int yywrap (void ); +#endif +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy (char *,yyconst char *,int ); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * ); +#endif + +#ifndef YY_NO_INPUT + +#ifdef __cplusplus +static int yyinput (void ); +#else +static int input (void ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO do { if (fwrite( yytext, yyleng, 1, yyout )) {} } while (0) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + size_t n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg ) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex (void); + +#define YY_DECL int yylex (void) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + register yy_state_type yy_current_state; + register char *yy_cp, *yy_bp; + register int yy_act; + +#line 65 "prom_lex.l" + + +#line 1163 "" + + if ( !(yy_init) ) + { + (yy_init) = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! (yy_start) ) + (yy_start) = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + yyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer(yyin,YY_BUF_SIZE ); + } + + yy_load_buffer_state( ); + } + + while ( 1 ) /* loops until end-of-file is reached */ + { + yy_cp = (yy_c_buf_p); + + /* Support of yytext. */ + *yy_cp = (yy_hold_char); + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = (yy_start); +yy_match: + do + { + register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)]; + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 444 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + ++yy_cp; + } + while ( yy_current_state != 443 ); + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = (yy_hold_char); + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + goto yy_find_action; + +case 1: +YY_RULE_SETUP +#line 67 "prom_lex.l" +{ upval(CHOSEN); } + YY_BREAK +case 2: +YY_RULE_SETUP +#line 68 "prom_lex.l" +{ upval(VDEVICE); } + YY_BREAK +case 3: +YY_RULE_SETUP +#line 69 "prom_lex.l" +{ upval(VDEVINST); } + YY_BREAK +case 4: +YY_RULE_SETUP +#line 70 "prom_lex.l" +{ upval(VDEVDEV); } + YY_BREAK +case 5: +YY_RULE_SETUP +#line 71 "prom_lex.l" +{ upval(VDEVRAW); } + YY_BREAK +case 6: +YY_RULE_SETUP +#line 72 "prom_lex.l" +{ upval(OBPQUAL); } + YY_BREAK +case 7: +YY_RULE_SETUP +#line 73 "prom_lex.l" +{ upval(BUSNAME); } + YY_BREAK +case 8: +YY_RULE_SETUP +#line 74 "prom_lex.l" +{ upval(IPV4); } + YY_BREAK +case 9: +YY_RULE_SETUP +#line 75 "prom_lex.l" +{ upval(IQN); } + YY_BREAK +case 10: +YY_RULE_SETUP +#line 76 "prom_lex.l" +{ upval(BOOTDEV); } + YY_BREAK +case 11: +YY_RULE_SETUP +#line 77 "prom_lex.l" +{ upval(OBPPARM); } + YY_BREAK +case 12: +YY_RULE_SETUP +#line 78 "prom_lex.l" +{ upval(HEX4); } + YY_BREAK +case 13: +YY_RULE_SETUP +#line 79 "prom_lex.l" +{ upval(HEX16); } + YY_BREAK +case 14: +YY_RULE_SETUP +#line 80 "prom_lex.l" +{ upval(FILENAME); } + YY_BREAK +case 15: +/* rule 15 can match eol */ +YY_RULE_SETUP +#line 81 "prom_lex.l" +{ /* eat all whitespace. */ + yylloc.first_column = yylloc.last_column; + yylloc.last_column += yyleng; +} + YY_BREAK +case 16: +YY_RULE_SETUP +#line 85 "prom_lex.l" +{ /* any other single char. */ + dbg("??"); + yylloc.first_column = yylloc.last_column; + yylloc.last_column += yyleng; + return *yytext; +} + YY_BREAK +case YY_STATE_EOF(INITIAL): +#line 92 "prom_lex.l" +yyterminate(); + YY_BREAK +case 17: +YY_RULE_SETUP +#line 93 "prom_lex.l" +ECHO; + YY_BREAK +#line 1340 "" + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = (yy_hold_char); + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state ); + + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++(yy_c_buf_p); + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_END_OF_FILE: + { + (yy_did_buffer_switch_on_eof) = 0; + + if ( yywrap( ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = + (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + (yy_c_buf_p) = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)]; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ +} /* end of yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (void) +{ + register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + register char *source = (yytext_ptr); + register int number_to_move, i; + int ret_val; + + if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr)) - 1; + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0; + + else + { + int num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER; + + int yy_c_buf_p_offset = + (int) ((yy_c_buf_p) - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yyrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = 0; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + (yy_n_chars), (size_t) num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + if ( (yy_n_chars) == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart(yyin ); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if ((yy_size_t) ((yy_n_chars) + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + yy_size_t new_size = (yy_n_chars) + number_to_move + ((yy_n_chars) >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc((void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf,new_size ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + } + + (yy_n_chars) += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR; + + (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (void) +{ + register yy_state_type yy_current_state; + register char *yy_cp; + + yy_current_state = (yy_start); + + for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp ) + { + register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 444 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state ) +{ + register int yy_is_jam; + register char *yy_cp = (yy_c_buf_p); + + register YY_CHAR yy_c = 1; + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 444 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + yy_is_jam = (yy_current_state == 443); + + return yy_is_jam ? 0 : yy_current_state; +} + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (void) +#else + static int input (void) +#endif + +{ + int c; + + *(yy_c_buf_p) = (yy_hold_char); + + if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + /* This was really a NUL. */ + *(yy_c_buf_p) = '\0'; + + else + { /* need more input */ + int offset = (yy_c_buf_p) - (yytext_ptr); + ++(yy_c_buf_p); + + switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart(yyin ); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap( ) ) + return EOF; + + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(); +#else + return input(); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = (yytext_ptr) + offset; + break; + } + } + } + + c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */ + *(yy_c_buf_p) = '\0'; /* preserve yytext */ + (yy_hold_char) = *++(yy_c_buf_p); + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * + * @note This function does not reset the start condition to @c INITIAL . + */ + void yyrestart (FILE * input_file ) +{ + + if ( ! YY_CURRENT_BUFFER ){ + yyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer(yyin,YY_BUF_SIZE ); + } + + yy_init_buffer(YY_CURRENT_BUFFER,input_file ); + yy_load_buffer_state( ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * + */ + void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ) +{ + + /* TODO. We should be able to replace this entire function body + * with + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); + */ + yyensure_buffer_stack (); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yy_load_buffer_state( ); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + (yy_did_buffer_switch_on_eof) = 1; +} + +static void yy_load_buffer_state (void) +{ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + (yy_hold_char) = *(yy_c_buf_p); +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * + * @return the allocated buffer state. + */ + YY_BUFFER_STATE yy_create_buffer (FILE * file, int size ) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yyalloc(sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yyalloc(b->yy_buf_size + 2 ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer(b,file ); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with yy_create_buffer() + * + */ + void yy_delete_buffer (YY_BUFFER_STATE b ) +{ + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yyfree((void *) b->yy_ch_buf ); + + yyfree((void *) b ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yyrestart() or at EOF. + */ + static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file ) + +{ + int oerrno = errno; + + yy_flush_buffer(b ); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * + */ + void yy_flush_buffer (YY_BUFFER_STATE b ) +{ + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + yy_load_buffer_state( ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * + */ +void yypush_buffer_state (YY_BUFFER_STATE new_buffer ) +{ + if (new_buffer == NULL) + return; + + yyensure_buffer_stack(); + + /* This block is copied from yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + (yy_buffer_stack_top)++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * + */ +void yypop_buffer_state (void) +{ + if (!YY_CURRENT_BUFFER) + return; + + yy_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + if ((yy_buffer_stack_top) > 0) + --(yy_buffer_stack_top); + + if (YY_CURRENT_BUFFER) { + yy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void yyensure_buffer_stack (void) +{ + int num_to_alloc; + + if (!(yy_buffer_stack)) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; + (yy_buffer_stack) = (struct yy_buffer_state**)yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + ); + if ( ! (yy_buffer_stack) ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + (yy_buffer_stack_max) = num_to_alloc; + (yy_buffer_stack_top) = 0; + return; + } + + if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + int grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = (yy_buffer_stack_max) + grow_size; + (yy_buffer_stack) = (struct yy_buffer_state**)yyrealloc + ((yy_buffer_stack), + num_to_alloc * sizeof(struct yy_buffer_state*) + ); + if ( ! (yy_buffer_stack) ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*)); + (yy_buffer_stack_max) = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size ) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return 0; + + b = (YY_BUFFER_STATE) yyalloc(sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); + + b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = 0; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yy_switch_to_buffer(b ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to yylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * yy_scan_bytes() instead. + */ +YY_BUFFER_STATE yy_scan_string (yyconst char * yystr ) +{ + + return yy_scan_bytes(yystr,strlen(yystr) ); +} + +/** Setup the input buffer state to scan the given bytes. The next call to yylex() will + * scan from a @e copy of @a bytes. + * @param yybytes the byte buffer to scan + * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_bytes (yyconst char * yybytes, int _yybytes_len ) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = _yybytes_len + 2; + buf = (char *) yyalloc(n ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer(buf,n ); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yy_fatal_error (yyconst char* msg ) +{ + (void) fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = (yy_hold_char); \ + (yy_c_buf_p) = yytext + yyless_macro_arg; \ + (yy_hold_char) = *(yy_c_buf_p); \ + *(yy_c_buf_p) = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the current line number. + * + */ +int yyget_lineno (void) +{ + + return yylineno; +} + +/** Get the input stream. + * + */ +FILE *yyget_in (void) +{ + return yyin; +} + +/** Get the output stream. + * + */ +FILE *yyget_out (void) +{ + return yyout; +} + +/** Get the length of the current token. + * + */ +int yyget_leng (void) +{ + return yyleng; +} + +/** Get the current token. + * + */ + +char *yyget_text (void) +{ + return yytext; +} + +/** Set the current line number. + * @param line_number + * + */ +void yyset_lineno (int line_number ) +{ + + yylineno = line_number; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param in_str A readable stream. + * + * @see yy_switch_to_buffer + */ +void yyset_in (FILE * in_str ) +{ + yyin = in_str ; +} + +void yyset_out (FILE * out_str ) +{ + yyout = out_str ; +} + +int yyget_debug (void) +{ + return yy_flex_debug; +} + +void yyset_debug (int bdebug ) +{ + yy_flex_debug = bdebug ; +} + +static int yy_init_globals (void) +{ + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from yylex_destroy(), so don't allocate here. + */ + + (yy_buffer_stack) = 0; + (yy_buffer_stack_top) = 0; + (yy_buffer_stack_max) = 0; + (yy_c_buf_p) = (char *) 0; + (yy_init) = 0; + (yy_start) = 0; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = (FILE *) 0; + yyout = (FILE *) 0; +#endif + + /* For future reference: Set errno on error, since we are called by + * yylex_init() + */ + return 0; +} + +/* yylex_destroy is for both reentrant and non-reentrant scanners. */ +int yylex_destroy (void) +{ + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + yy_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + yypop_buffer_state(); + } + + /* Destroy the stack itself. */ + yyfree((yy_buffer_stack) ); + (yy_buffer_stack) = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * yylex() is called, initialization will occur. */ + yy_init_globals( ); + + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, yyconst char * s2, int n ) +{ + register int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * s ) +{ + register int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *yyalloc (yy_size_t size ) +{ + return (void *) malloc( size ); +} + +void *yyrealloc (void * ptr, yy_size_t size ) +{ + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return (void *) realloc( (char *) ptr, size ); +} + +void yyfree (void * ptr ) +{ + free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +#line 93 "prom_lex.l" + + + diff --git a/utils/fwparam_ibft/prom_lex.l b/utils/fwparam_ibft/prom_lex.l new file mode 100644 index 0000000..e70c790 --- /dev/null +++ b/utils/fwparam_ibft/prom_lex.l @@ -0,0 +1,93 @@ +/* + * Copyright (C) IBM Corporation. 2007 + * Author: Doug Maxey + * + * 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, see . + */ + +/* definitions */ +%option array + +%{ +#include "prom_parse.h" + +#undef LEXDEBUG +#ifdef LEXDEBUG +#define dbg(a) dbgprint((a)) +#else +#define dbg(a) do {} while (0) +#endif /* LEXDEBUG */ + +#define upval(d) \ + dbg(#d); \ + yylval.str[0] = 0; \ + strcat(yylval.str, yytext); \ + yylloc.first_column = yylloc.last_column; \ + yylloc.last_column += yyleng; \ + return d + +void dbgprint(const char *item) { fprintf(stderr, "%s: \"%s\" len=%d ", item, yytext, yyleng);} + +%} + +%option noyywrap +%option never-interactive +%option nounput +%option noinput + +VDEVICE vdevice +VDEVINST gscsi +VDEVDEV dev +VDEVRAW rawio + /* CHOSEN uses only boot related paths. */ +CHOSEN bootpath|bootargs|iscsi-bootargs|nas-bootdevice +BUSNAME ata|i2c|ide|pci|sata|scsi|usb|lhea +BOOTDEV cdrom|disk|ethernet|iscsi-(disk[0-9]|toe)|sd +HEX4 [[:xdigit:]]{1,4} +HEX16 [[:xdigit:]]{5,16} +IPV4 [0-9]{1,3}(\.[0-9]{1,3}){3} +IQN iqn\.[-[:alnum:]:.]{1,219} +OBPQUAL bootp|ipv6|iscsi|dhcpv6 +OBPPARM blksize|bootp-retries|chapid|chappw|ciaddr|dhcp|filename|giaddr|ichapid|ichappw|ilun|iname|iport|isid|isns|itname|siaddr|slp|subnet-mask|tftp-retries +FILENAME \\[-[:alnum:]\\\.]{1,} + +%% /* rules */ + +{CHOSEN} { upval(CHOSEN); } +{VDEVICE} { upval(VDEVICE); } +{VDEVINST} { upval(VDEVINST); } +{VDEVDEV} { upval(VDEVDEV); } +{VDEVRAW} { upval(VDEVRAW); } +{OBPQUAL} { upval(OBPQUAL); } +{BUSNAME} { upval(BUSNAME); } +{IPV4} { upval(IPV4); } +{IQN} { upval(IQN); } +{BOOTDEV} { upval(BOOTDEV); } +{OBPPARM} { upval(OBPPARM); } +{HEX4} { upval(HEX4); } +{HEX16} { upval(HEX16); } +{FILENAME} { upval(FILENAME); } +[ \t\n]+ { /* eat all whitespace. */ + yylloc.first_column = yylloc.last_column; + yylloc.last_column += yyleng; +} +. { /* any other single char. */ + dbg("??"); + yylloc.first_column = yylloc.last_column; + yylloc.last_column += yyleng; + return *yytext; +} + +<> yyterminate(); +%% /* user code */ diff --git a/utils/fwparam_ibft/prom_parse.h b/utils/fwparam_ibft/prom_parse.h new file mode 100644 index 0000000..00cffff --- /dev/null +++ b/utils/fwparam_ibft/prom_parse.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) IBM Corporation. 2007 + * Author: Doug Maxey + * + * 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, see . + */ + +#ifndef PROM_PARSE_H_ +#define PROM_PARSE_H_ + +#include +#include +#include "iscsi_obp.h" + +struct ofw_dev; +void yyerror(struct ofw_dev *ofwdev, const char *msg); +extern int yyleng; +extern int yydebug; +#include +extern FILE *yyin; +extern char yytext[]; +int yylex(void); + +#define YY_NO_UNPUT 1 /* match this with %option never-interactive. */ +#include "prom_parse.tab.h" + + +#endif /* PROM_PARSE_H_ */ diff --git a/utils/fwparam_ibft/prom_parse.tab.c b/utils/fwparam_ibft/prom_parse.tab.c new file mode 100644 index 0000000..6275961 --- /dev/null +++ b/utils/fwparam_ibft/prom_parse.tab.c @@ -0,0 +1,2063 @@ +/* A Bison parser, made by GNU Bison 3.0.4. */ + +/* Bison implementation for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc. + + 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 3 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, see . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "3.0.4" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 0 + +/* Push parsers. */ +#define YYPUSH 0 + +/* Pull parsers. */ +#define YYPULL 1 + + + + +/* Copy the first part of user declarations. */ +#line 21 "prom_parse.y" /* yacc.c:339 */ + + /* literal block. include lines, decls, defns. */ +//#define YYDEBUG 1 +#if YYDEBUG +#define DPRINT(fmt,...) printf(fmt,__VA_ARGS__) +#else +#define DPRINT(fmt,...) do {} while(0) +#endif +#include "prom_parse.h" +#include "iscsi_obp.h" + + + +#line 80 "prom_parse.tab.c" /* yacc.c:339 */ + +# ifndef YY_NULLPTR +# if defined __cplusplus && 201103L <= __cplusplus +# define YY_NULLPTR nullptr +# else +# define YY_NULLPTR 0 +# endif +# endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 0 +#endif + +/* In a future release of Bison, this section will be replaced + by #include "prom_parse.tab.h". */ +#ifndef YY_YY_PROM_PARSE_TAB_H_INCLUDED +# define YY_YY_PROM_PARSE_TAB_H_INCLUDED +/* Debug traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif +#if YYDEBUG +extern int yydebug; +#endif + +/* Token type. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + enum yytokentype + { + BUSNAME = 258, + BOOTDEV = 259, + IPV4 = 260, + IQN = 261, + OBPPARM = 262, + OBPQUAL = 263, + HEX4 = 264, + HEX16 = 265, + VDEVICE = 266, + VDEVINST = 267, + VDEVDEV = 268, + VDEVRAW = 269, + CHOSEN = 270, + FILENAME = 271 + }; +#endif + +/* Value type. */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED + +union YYSTYPE +{ +#line 34 "prom_parse.y" /* yacc.c:355 */ + +#define STR_LEN 16384 + char str[STR_LEN]; + +#line 142 "prom_parse.tab.c" /* yacc.c:355 */ +}; + +typedef union YYSTYPE YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define YYSTYPE_IS_DECLARED 1 +#endif + +/* Location type. */ +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE YYLTYPE; +struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +}; +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + +extern YYSTYPE yylval; +extern YYLTYPE yylloc; +int yyparse (struct ofw_dev *ofwdev); + +#endif /* !YY_YY_PROM_PARSE_TAB_H_INCLUDED */ + +/* Copy the second part of user declarations. */ + +#line 173 "prom_parse.tab.c" /* yacc.c:358 */ + +#ifdef short +# undef short +#endif + +#ifdef YYTYPE_UINT8 +typedef YYTYPE_UINT8 yytype_uint8; +#else +typedef unsigned char yytype_uint8; +#endif + +#ifdef YYTYPE_INT8 +typedef YYTYPE_INT8 yytype_int8; +#else +typedef signed char yytype_int8; +#endif + +#ifdef YYTYPE_UINT16 +typedef YYTYPE_UINT16 yytype_uint16; +#else +typedef unsigned short int yytype_uint16; +#endif + +#ifdef YYTYPE_INT16 +typedef YYTYPE_INT16 yytype_int16; +#else +typedef short int yytype_int16; +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif ! defined YYSIZE_T +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned int +# endif +#endif + +#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_(Msgid) dgettext ("bison-runtime", Msgid) +# endif +# endif +# ifndef YY_ +# define YY_(Msgid) Msgid +# endif +#endif + +#ifndef YY_ATTRIBUTE +# if (defined __GNUC__ \ + && (2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__))) \ + || defined __SUNPRO_C && 0x5110 <= __SUNPRO_C +# define YY_ATTRIBUTE(Spec) __attribute__(Spec) +# else +# define YY_ATTRIBUTE(Spec) /* empty */ +# endif +#endif + +#ifndef YY_ATTRIBUTE_PURE +# define YY_ATTRIBUTE_PURE YY_ATTRIBUTE ((__pure__)) +#endif + +#ifndef YY_ATTRIBUTE_UNUSED +# define YY_ATTRIBUTE_UNUSED YY_ATTRIBUTE ((__unused__)) +#endif + +#if !defined _Noreturn \ + && (!defined __STDC_VERSION__ || __STDC_VERSION__ < 201112) +# if defined _MSC_VER && 1200 <= _MSC_VER +# define _Noreturn __declspec (noreturn) +# else +# define _Noreturn YY_ATTRIBUTE ((__noreturn__)) +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(E) ((void) (E)) +#else +# define YYUSE(E) /* empty */ +#endif + +#if defined __GNUC__ && 407 <= __GNUC__ * 100 + __GNUC_MINOR__ +/* Suppress an incorrect diagnostic about yylval being uninitialized. */ +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")\ + _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") +# define YY_IGNORE_MAYBE_UNINITIALIZED_END \ + _Pragma ("GCC diagnostic pop") +#else +# define YY_INITIAL_VALUE(Value) Value +#endif +#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_END +#endif +#ifndef YY_INITIAL_VALUE +# define YY_INITIAL_VALUE(Value) /* Nothing. */ +#endif + + +#if ! defined yyoverflow || YYERROR_VERBOSE + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS +# include /* INFRINGES ON USER NAME SPACE */ + /* Use EXIT_SUCCESS as a witness for stdlib.h. */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's 'empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined EXIT_SUCCESS \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined EXIT_SUCCESS +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined EXIT_SUCCESS +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ + + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL \ + && defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yytype_int16 yyss_alloc; + YYSTYPE yyvs_alloc; + YYLTYPE yyls_alloc; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE) + sizeof (YYLTYPE)) \ + + 2 * YYSTACK_GAP_MAXIMUM) + +# define YYCOPY_NEEDED 1 + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack_alloc, Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ + Stack = &yyptr->Stack_alloc; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (0) + +#endif + +#if defined YYCOPY_NEEDED && YYCOPY_NEEDED +/* Copy COUNT objects from SRC to DST. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(Dst, Src, Count) \ + __builtin_memcpy (Dst, Src, (Count) * sizeof (*(Src))) +# else +# define YYCOPY(Dst, Src, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (Dst)[yyi] = (Src)[yyi]; \ + } \ + while (0) +# endif +# endif +#endif /* !YYCOPY_NEEDED */ + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 8 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 103 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 24 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 19 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 51 +/* YYNSTATES -- Number of states. */ +#define YYNSTATES 93 + +/* YYTRANSLATE[YYX] -- Symbol number corresponding to YYX as returned + by yylex, with out-of-bounds checking. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 273 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM + as returned by yylex, without out-of-bounds checking. */ +static const yytype_uint8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 19, 2, 2, 17, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 20, 2, + 2, 21, 2, 2, 18, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 22, 23 +}; + +#if YYDEBUG + /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ +static const yytype_uint16 yyrline[] = +{ + 0, 60, 60, 63, 66, 74, 82, 89, 96, 99, + 104, 107, 110, 113, 116, 122, 125, 128, 133, 138, + 141, 144, 149, 154, 157, 160, 165, 168, 173, 177, + 181, 185, 191, 194, 199, 202, 207, 210, 215, 220, + 223, 228, 231, 234, 237, 242, 245, 250, 253, 256, + 261, 264 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE || 0 +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "$end", "error", "$undefined", "BUSNAME", "BOOTDEV", "IPV4", "IQN", + "OBPPARM", "OBPQUAL", "HEX4", "HEX16", "VDEVICE", "VDEVINST", "VDEVDEV", + "VDEVRAW", "CHOSEN", "FILENAME", "'/'", "'@'", "','", "':'", "'='", + "\"::\"", "\":\"", "$accept", "devpath", "busses", "bus", "bootdev", + "vdevice", "vdev_parms", "vdev_parm", "obp_params", "obp_param", + "obp_quals", "obp_qual", "ipaddr", "ipv4", "ipv6", "hexpart", "hexseq", + "disklabel", "diskpart", YY_NULLPTR +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[NUM] -- (External) token number corresponding to the + (internal) symbol number NUM (which must be that of a token). */ +static const yytype_uint16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 47, 64, 44, + 58, 61, 272, 273 +}; +# endif + +#define YYPACT_NINF -73 + +#define yypact_value_is_default(Yystate) \ + (!!((Yystate) == (-73))) + +#define YYTABLE_NINF -46 + +#define yytable_value_is_error(Yytable_value) \ + 0 + + /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +static const yytype_int8 yypact[] = +{ + -15, 19, 13, 20, 18, 23, -73, 25, -73, 39, + 51, 50, 49, 16, 46, 43, 42, -73, 47, -73, + -9, -73, -73, 44, 45, 58, 59, -73, 52, -73, + -73, -73, 56, 24, 61, 62, 64, -73, -73, 60, + 55, 57, 38, 8, -73, 41, 52, -73, -73, 37, + -73, 68, 63, 65, -73, -73, -73, 3, -73, -73, + -73, 8, 69, -73, 44, -73, -2, 44, -73, -73, + -73, 67, -73, -73, -73, 36, -73, -73, 71, -73, + -73, -73, 11, 66, -73, -73, 66, 76, 71, 73, + -73, 66, -73 +}; + + /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. + Performed when YYTABLE does not specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 0, 2, 0, 10, 0, 0, 8, 0, 1, 0, + 0, 0, 0, 3, 0, 0, 11, 13, 0, 18, + 0, 9, 34, 0, 0, 0, 0, 35, 0, 32, + 4, 47, 0, 0, 0, 0, 0, 15, 48, 0, + 0, 50, 0, 5, 19, 0, 0, 12, 14, 0, + 22, 0, 0, 0, 26, 23, 33, 0, 6, 21, + 20, 0, 0, 16, 0, 51, 0, 26, 24, 25, + 7, 0, 49, 38, 29, 30, 27, 31, 0, 28, + 36, 37, 39, 41, 17, 45, 44, 0, 42, 0, + 40, 43, 46 +}; + + /* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -73, -73, -73, 72, 78, -73, -73, -27, 48, 26, + 70, 53, -73, 1, -73, -73, -72, -42, -23 +}; + + /* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + -1, 2, 5, 6, 13, 7, 33, 27, 43, 55, + 28, 29, 79, 80, 81, 82, 83, 30, 31 +}; + + /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule whose + number is the opposite. If YYTABLE_NINF, syntax error. */ +static const yytype_int8 yytable[] = +{ + 38, 58, 1, 73, 74, 44, 86, 75, 76, 36, + 53, 37, 67, 8, 77, 69, 91, 23, 60, 70, + 78, 25, 3, 26, 22, 23, 25, 57, 26, 24, + 4, 87, 22, 88, 25, 11, 26, 24, 9, 10, + 12, 72, 14, 45, 38, 53, 22, 54, 16, 17, + 20, 24, 3, 20, 24, 59, 62, 63, -45, -45, + 18, 34, 19, 32, 26, 35, 39, 40, 41, 24, + 47, 42, 48, 49, 51, 50, 52, 64, 71, 65, + 85, 73, 92, 68, 21, 15, 66, 84, 90, 89, + 0, 0, 0, 0, 61, 56, 0, 0, 0, 0, + 0, 0, 0, 46 +}; + +static const yytype_int8 yycheck[] = +{ + 23, 43, 17, 5, 6, 32, 78, 9, 10, 18, + 7, 20, 9, 0, 16, 57, 88, 9, 45, 61, + 22, 18, 3, 20, 8, 9, 18, 19, 20, 13, + 11, 20, 8, 22, 18, 17, 20, 13, 18, 19, + 17, 64, 17, 19, 67, 7, 8, 9, 9, 10, + 4, 13, 3, 4, 13, 14, 19, 20, 22, 23, + 9, 19, 12, 20, 20, 18, 21, 9, 9, 13, + 9, 19, 10, 9, 19, 15, 19, 9, 9, 16, + 9, 5, 9, 57, 12, 7, 21, 20, 87, 23, + -1, -1, -1, -1, 46, 42, -1, -1, -1, -1, + -1, -1, -1, 33 +}; + + /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint8 yystos[] = +{ + 0, 17, 25, 3, 11, 26, 27, 29, 0, 18, + 19, 17, 17, 28, 17, 28, 9, 10, 9, 12, + 4, 27, 8, 9, 13, 18, 20, 31, 34, 35, + 41, 42, 20, 30, 19, 18, 18, 20, 42, 21, + 9, 9, 19, 32, 31, 19, 34, 9, 10, 9, + 15, 19, 19, 7, 9, 33, 35, 19, 41, 14, + 31, 32, 19, 20, 9, 16, 21, 9, 33, 41, + 41, 9, 42, 5, 6, 9, 10, 16, 22, 36, + 37, 38, 39, 40, 20, 9, 40, 20, 22, 23, + 37, 40, 9 +}; + + /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_uint8 yyr1[] = +{ + 0, 24, 25, 25, 25, 25, 25, 25, 26, 26, + 27, 27, 27, 27, 27, 28, 28, 28, 29, 30, + 30, 30, 31, 32, 32, 32, 33, 33, 33, 33, + 33, 33, 34, 34, 35, 35, 36, 36, 37, 38, + 38, 39, 39, 39, 39, 40, 40, 41, 41, 41, + 42, 42 +}; + + /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */ +static const yytype_uint8 yyr2[] = +{ + 0, 2, 1, 3, 4, 5, 6, 7, 1, 3, + 1, 3, 5, 3, 5, 3, 5, 7, 3, 2, + 3, 3, 3, 2, 3, 3, 1, 3, 3, 3, + 3, 3, 1, 3, 1, 1, 1, 1, 1, 1, + 3, 1, 2, 3, 2, 1, 3, 1, 2, 5, + 2, 4 +}; + + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + YYPOPSTACK (yylen); \ + yystate = *yyssp; \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (ofwdev, YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ +while (0) + +/* Error token number */ +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (N) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (0) +#endif + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) + + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (0) + + +/* YY_LOCATION_PRINT -- Print the location on the stream. + This macro was not mandated originally: define only if we know + we won't break user code: when these are the locations we know. */ + +#ifndef YY_LOCATION_PRINT +# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL + +/* Print *YYLOCP on YYO. Private, do not rely on its existence. */ + +YY_ATTRIBUTE_UNUSED +static unsigned +yy_location_print_ (FILE *yyo, YYLTYPE const * const yylocp) +{ + unsigned res = 0; + int end_col = 0 != yylocp->last_column ? yylocp->last_column - 1 : 0; + if (0 <= yylocp->first_line) + { + res += YYFPRINTF (yyo, "%d", yylocp->first_line); + if (0 <= yylocp->first_column) + res += YYFPRINTF (yyo, ".%d", yylocp->first_column); + } + if (0 <= yylocp->last_line) + { + if (yylocp->first_line < yylocp->last_line) + { + res += YYFPRINTF (yyo, "-%d", yylocp->last_line); + if (0 <= end_col) + res += YYFPRINTF (yyo, ".%d", end_col); + } + else if (0 <= end_col && yylocp->first_column < end_col) + res += YYFPRINTF (yyo, "-%d", end_col); + } + return res; + } + +# define YY_LOCATION_PRINT(File, Loc) \ + yy_location_print_ (File, &(Loc)) + +# else +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif +#endif + + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value, Location, ofwdev); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (0) + + +/*----------------------------------------. +| Print this symbol's value on YYOUTPUT. | +`----------------------------------------*/ + +static void +yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, struct ofw_dev *ofwdev) +{ + FILE *yyo = yyoutput; + YYUSE (yyo); + YYUSE (yylocationp); + YYUSE (ofwdev); + if (!yyvaluep) + return; +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# endif + YYUSE (yytype); +} + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +static void +yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp, struct ofw_dev *ofwdev) +{ + YYFPRINTF (yyoutput, "%s %s (", + yytype < YYNTOKENS ? "token" : "nterm", yytname[yytype]); + + YY_LOCATION_PRINT (yyoutput, *yylocationp); + YYFPRINTF (yyoutput, ": "); + yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, ofwdev); + YYFPRINTF (yyoutput, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +static void +yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop) +{ + YYFPRINTF (stderr, "Stack now"); + for (; yybottom <= yytop; yybottom++) + { + int yybot = *yybottom; + YYFPRINTF (stderr, " %d", yybot); + } + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (0) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +static void +yy_reduce_print (yytype_int16 *yyssp, YYSTYPE *yyvsp, YYLTYPE *yylsp, int yyrule, struct ofw_dev *ofwdev) +{ + unsigned long int yylno = yyrline[yyrule]; + int yynrhs = yyr2[yyrule]; + int yyi; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + YYFPRINTF (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, + yystos[yyssp[yyi + 1 - yynrhs]], + &(yyvsp[(yyi + 1) - (yynrhs)]) + , &(yylsp[(yyi + 1) - (yynrhs)]) , ofwdev); + YYFPRINTF (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyssp, yyvsp, yylsp, Rule, ofwdev); \ +} while (0) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +static YYSIZE_T +yystrlen (const char *yystr) +{ + YYSIZE_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +# endif + +# ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +static char * +yystpcpy (char *yydest, const char *yysrc) +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +# ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYSIZE_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + YYSIZE_T yyn = 0; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (! yyres) + return yystrlen (yystr); + + return yystpcpy (yyres, yystr) - yyres; +} +# endif + +/* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message + about the unexpected token YYTOKEN for the state stack whose top is + YYSSP. + + Return 0 if *YYMSG was successfully written. Return 1 if *YYMSG is + not large enough to hold the message. In that case, also set + *YYMSG_ALLOC to the required number of bytes. Return 2 if the + required number of bytes is too large to store. */ +static int +yysyntax_error (YYSIZE_T *yymsg_alloc, char **yymsg, + yytype_int16 *yyssp, int yytoken) +{ + YYSIZE_T yysize0 = yytnamerr (YY_NULLPTR, yytname[yytoken]); + YYSIZE_T yysize = yysize0; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + /* Internationalized format string. */ + const char *yyformat = YY_NULLPTR; + /* Arguments of yyformat. */ + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + /* Number of reported tokens (one for the "unexpected", one per + "expected"). */ + int yycount = 0; + + /* There are many possibilities here to consider: + - If this state is a consistent state with a default action, then + the only way this function was invoked is if the default action + is an error action. In that case, don't check for expected + tokens because there are none. + - The only way there can be no lookahead present (in yychar) is if + this state is a consistent state with a default action. Thus, + detecting the absence of a lookahead is sufficient to determine + that there is no unexpected or expected token to report. In that + case, just report a simple "syntax error". + - Don't assume there isn't a lookahead just because this state is a + consistent state with a default action. There might have been a + previous inconsistent state, consistent state with a non-default + action, or user semantic action that manipulated yychar. + - Of course, the expected token list depends on states to have + correct lookahead information, and it depends on the parser not + to perform extra reductions after fetching a lookahead from the + scanner and before detecting a syntax error. Thus, state merging + (from LALR or IELR) and default reductions corrupt the expected + token list. However, the list is correct for canonical LR with + one exception: it will still contain any token that will not be + accepted due to an error action in a later state. + */ + if (yytoken != YYEMPTY) + { + int yyn = yypact[*yyssp]; + yyarg[yycount++] = yytname[yytoken]; + if (!yypact_value_is_default (yyn)) + { + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. In other words, skip the first -YYN actions for + this state because they are default actions. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yyx; + + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR + && !yytable_value_is_error (yytable[yyx + yyn])) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + break; + } + yyarg[yycount++] = yytname[yyx]; + { + YYSIZE_T yysize1 = yysize + yytnamerr (YY_NULLPTR, yytname[yyx]); + if (! (yysize <= yysize1 + && yysize1 <= YYSTACK_ALLOC_MAXIMUM)) + return 2; + yysize = yysize1; + } + } + } + } + + switch (yycount) + { +# define YYCASE_(N, S) \ + case N: \ + yyformat = S; \ + break + YYCASE_(0, YY_("syntax error")); + YYCASE_(1, YY_("syntax error, unexpected %s")); + YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s")); + YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s")); + YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s")); + YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s")); +# undef YYCASE_ + } + + { + YYSIZE_T yysize1 = yysize + yystrlen (yyformat); + if (! (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM)) + return 2; + yysize = yysize1; + } + + if (*yymsg_alloc < yysize) + { + *yymsg_alloc = 2 * yysize; + if (! (yysize <= *yymsg_alloc + && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM)) + *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM; + return 1; + } + + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + { + char *yyp = *yymsg; + int yyi = 0; + while ((*yyp = *yyformat) != '\0') + if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyformat += 2; + } + else + { + yyp++; + yyformat++; + } + } + return 0; +} +#endif /* YYERROR_VERBOSE */ + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, YYLTYPE *yylocationp, struct ofw_dev *ofwdev) +{ + YYUSE (yyvaluep); + YYUSE (yylocationp); + YYUSE (ofwdev); + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + YYUSE (yytype); + YY_IGNORE_MAYBE_UNINITIALIZED_END +} + + + + +/* The lookahead symbol. */ +int yychar; + +/* The semantic value of the lookahead symbol. */ +YYSTYPE yylval; +/* Location data for the lookahead symbol. */ +YYLTYPE yylloc +# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL + = { 1, 1, 1, 1 } +# endif +; +/* Number of syntax errors so far. */ +int yynerrs; + + +/*----------. +| yyparse. | +`----------*/ + +int +yyparse (struct ofw_dev *ofwdev) +{ + int yystate; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + + /* The stacks and their tools: + 'yyss': related to states. + 'yyvs': related to semantic values. + 'yyls': related to locations. + + Refer to the stacks through separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + yytype_int16 yyssa[YYINITDEPTH]; + yytype_int16 *yyss; + yytype_int16 *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs; + YYSTYPE *yyvsp; + + /* The location stack. */ + YYLTYPE yylsa[YYINITDEPTH]; + YYLTYPE *yyls; + YYLTYPE *yylsp; + + /* The locations where the error started and ended. */ + YYLTYPE yyerror_range[3]; + + YYSIZE_T yystacksize; + + int yyn; + int yyresult; + /* Lookahead token as an internal (translated) token number. */ + int yytoken = 0; + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + YYLTYPE yyloc; + +#if YYERROR_VERBOSE + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYSIZE_T yymsg_alloc = sizeof yymsgbuf; +#endif + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N), yylsp -= (N)) + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + yyssp = yyss = yyssa; + yyvsp = yyvs = yyvsa; + yylsp = yyls = yylsa; + yystacksize = YYINITDEPTH; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + yylsp[0] = yylloc; + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; + YYLTYPE *yyls1 = yyls; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yyls1, yysize * sizeof (*yylsp), + &yystacksize); + + yyls = yyls1; + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyexhaustedlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss_alloc, yyss); + YYSTACK_RELOCATE (yyvs_alloc, yyvs); + YYSTACK_RELOCATE (yyls_alloc, yyls); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + yylsp = yyls + yysize - 1; + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + if (yystate == YYFINAL) + YYACCEPT; + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + + /* Do appropriate processing given the current state. Read a + lookahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to lookahead token. */ + yyn = yypact[yystate]; + if (yypact_value_is_default (yyn)) + goto yydefault; + + /* Not known => get a lookahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = yylex (); + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yytable_value_is_error (yyn)) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the lookahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the shifted token. */ + yychar = YYEMPTY; + + yystate = yyn; + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + *++yylsp = yylloc; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + '$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + /* Default location. */ + YYLLOC_DEFAULT (yyloc, (yylsp - yylen), yylen); + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 2: +#line 60 "prom_parse.y" /* yacc.c:1646 */ + { + DPRINT("****rootonly: \"%s\"\n", "/"); + } +#line 1410 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 3: +#line 63 "prom_parse.y" /* yacc.c:1646 */ + { + DPRINT("****devpath busses:\n/%s/%s\n", (yyvsp[-1].str), (yyvsp[0].str)); + } +#line 1418 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 4: +#line 66 "prom_parse.y" /* yacc.c:1646 */ + { + ofwdev->dev_path = malloc(strlen((yyvsp[-2].str)) + + strlen((yyvsp[-1].str)) + 3); + sprintf(ofwdev->dev_path, "/%s%s", (yyvsp[-2].str), (yyvsp[-1].str)); + DPRINT("****devpath busses bootdev " + "disklabel:\n/%s/%s%s\n", + (yyvsp[-2].str), (yyvsp[-1].str), (yyvsp[0].str)); + } +#line 1431 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 5: +#line 74 "prom_parse.y" /* yacc.c:1646 */ + { + ofwdev->dev_path = malloc(strlen((yyvsp[-3].str)) + + strlen((yyvsp[-2].str)) + 3); + sprintf(ofwdev->dev_path, "/%s%s", (yyvsp[-3].str), (yyvsp[-2].str)); + DPRINT("****busses bootdev obp_quals obp_parms:\n" + "/%s/%s:%s%s\n", + (yyvsp[-3].str), (yyvsp[-2].str), (yyvsp[-1].str), (yyvsp[0].str)); + } +#line 1444 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 6: +#line 82 "prom_parse.y" /* yacc.c:1646 */ + { + ofwdev->dev_path = malloc(strlen((yyvsp[-4].str)) + + strlen((yyvsp[-3].str)) + 3); + sprintf(ofwdev->dev_path, "/%s%s", (yyvsp[-4].str), (yyvsp[-3].str)); + DPRINT("****busses bootdev obp_quals obp_parms " + "disklabel:\n/%s:%s%s%s\n", (yyvsp[-4].str), (yyvsp[-2].str), (yyvsp[-1].str), (yyvsp[0].str)); + } +#line 1456 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 7: +#line 89 "prom_parse.y" /* yacc.c:1646 */ + { + DPRINT("****vdevice bootdev obp_parms " + "disklabel:\n/%s:%s%s%s%s\n", + (yyvsp[-5].str), (yyvsp[-3].str), (yyvsp[-2].str), (yyvsp[-1].str), (yyvsp[0].str)); + } +#line 1466 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 8: +#line 96 "prom_parse.y" /* yacc.c:1646 */ + { + strcpy((yyval.str), (yyvsp[0].str)); + } +#line 1474 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 9: +#line 99 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s/%s", (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1482 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 10: +#line 104 "prom_parse.y" /* yacc.c:1646 */ + { + strcpy((yyval.str), (yyvsp[0].str)); + } +#line 1490 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 11: +#line 107 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s@%s", (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1498 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 12: +#line 110 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s@%s,%s", (yyvsp[-4].str), (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1506 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 13: +#line 113 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s@%s", (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1514 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 14: +#line 116 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s,%s@%s", (yyvsp[-4].str), (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1522 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 15: +#line 122 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "/%s", (yyvsp[-1].str)); + } +#line 1530 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 16: +#line 125 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "/%s@%s", (yyvsp[-3].str), (yyvsp[-1].str)); + } +#line 1538 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 17: +#line 128 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "/%s@%s,%s", (yyvsp[-5].str), (yyvsp[-3].str), (yyvsp[-1].str)); + } +#line 1546 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 18: +#line 133 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s/%s", (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1554 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 19: +#line 138 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, ":%s", (yyvsp[0].str)); + } +#line 1562 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 20: +#line 141 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s,%s", (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1570 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 21: +#line 144 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s,%s", (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1578 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 22: +#line 149 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s=%s", (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1586 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 23: +#line 154 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, ",%s", (yyvsp[0].str)); + } +#line 1594 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 24: +#line 157 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s,%s", (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1602 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 25: +#line 160 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s,%s", (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1610 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 26: +#line 165 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s", (yyvsp[0].str)); + } +#line 1618 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 27: +#line 168 "prom_parse.y" /* yacc.c:1646 */ + { + /* luns > 0 are the SAM-3+ hex representation. */ + obp_parm_hexnum(ofwdev, (yyvsp[-2].str), (yyvsp[0].str)); + snprintf((yyval.str), STR_LEN, "%s=%s", (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1628 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 28: +#line 173 "prom_parse.y" /* yacc.c:1646 */ + { + obp_parm_addr(ofwdev, (yyvsp[-2].str), (yyvsp[0].str)); + snprintf((yyval.str), STR_LEN, "%s=%s", (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1637 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 29: +#line 177 "prom_parse.y" /* yacc.c:1646 */ + { + obp_parm_iqn(ofwdev, (yyvsp[-2].str), (yyvsp[0].str)); + snprintf((yyval.str), STR_LEN, "%s=%s", (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1646 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 30: +#line 181 "prom_parse.y" /* yacc.c:1646 */ + { + obp_parm_hexnum(ofwdev, (yyvsp[-2].str), (yyvsp[0].str)); + snprintf((yyval.str), STR_LEN, "%s=%s", (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1655 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 31: +#line 185 "prom_parse.y" /* yacc.c:1646 */ + { + obp_parm_str(ofwdev, (yyvsp[-2].str), (yyvsp[0].str)); + snprintf((yyval.str), STR_LEN, "%s=%s", (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1664 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 32: +#line 191 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s", (yyvsp[0].str)); + } +#line 1672 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 33: +#line 194 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s,%s", (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1680 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 34: +#line 199 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s", obp_qual_set(ofwdev, (yyvsp[0].str))); + } +#line 1688 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 35: +#line 202 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s", (yyvsp[0].str)); + } +#line 1696 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 36: +#line 207 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s", (yyvsp[0].str)); + } +#line 1704 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 37: +#line 210 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s", (yyvsp[0].str)); + } +#line 1712 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 38: +#line 215 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s", (yyvsp[0].str)); + } +#line 1720 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 39: +#line 220 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s", (yyvsp[0].str)); + } +#line 1728 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 40: +#line 223 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s:%s", (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1736 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 41: +#line 228 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s", (yyvsp[0].str)); + } +#line 1744 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 42: +#line 231 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s::", (yyvsp[-1].str)); + } +#line 1752 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 43: +#line 234 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s::%s", (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1760 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 44: +#line 237 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "::%s", (yyvsp[0].str)); + } +#line 1768 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 45: +#line 242 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s", (yyvsp[0].str)); + } +#line 1776 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 46: +#line 245 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s:%s", (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1784 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 47: +#line 250 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s", (yyvsp[0].str)); + } +#line 1792 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 48: +#line 253 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "%s%s", (yyvsp[-1].str), (yyvsp[0].str)); + } +#line 1800 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 49: +#line 256 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, "@%s,%s%s", (yyvsp[-3].str), (yyvsp[-1].str), (yyvsp[0].str)); + } +#line 1808 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 50: +#line 261 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, ":%s", (yyvsp[0].str)); + } +#line 1816 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + case 51: +#line 264 "prom_parse.y" /* yacc.c:1646 */ + { + snprintf((yyval.str), STR_LEN, ":%s,%s", (yyvsp[-2].str), (yyvsp[0].str)); + } +#line 1824 "prom_parse.tab.c" /* yacc.c:1646 */ + break; + + +#line 1828 "prom_parse.tab.c" /* yacc.c:1646 */ + default: break; + } + /* User semantic actions sometimes alter yychar, and that requires + that yytoken be updated with the new translation. We take the + approach of translating immediately before every use of yytoken. + One alternative is translating here after every semantic action, + but that translation would be missed if the semantic action invokes + YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or + if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an + incorrect destructor might then be invoked immediately. In the + case of YYERROR or YYBACKUP, subsequent parser actions might lead + to an incorrect destructor call or verbose syntax error message + before the lookahead is translated. */ + YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + *++yylsp = yyloc; + + /* Now 'shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*--------------------------------------. +| yyerrlab -- here on detecting error. | +`--------------------------------------*/ +yyerrlab: + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = yychar == YYEMPTY ? YYEMPTY : YYTRANSLATE (yychar); + + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if ! YYERROR_VERBOSE + yyerror (ofwdev, YY_("syntax error")); +#else +# define YYSYNTAX_ERROR yysyntax_error (&yymsg_alloc, &yymsg, \ + yyssp, yytoken) + { + char const *yymsgp = YY_("syntax error"); + int yysyntax_error_status; + yysyntax_error_status = YYSYNTAX_ERROR; + if (yysyntax_error_status == 0) + yymsgp = yymsg; + else if (yysyntax_error_status == 1) + { + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yymsg_alloc); + if (!yymsg) + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + yysyntax_error_status = 2; + } + else + { + yysyntax_error_status = YYSYNTAX_ERROR; + yymsgp = yymsg; + } + } + yyerror (ofwdev, yymsgp); + if (yysyntax_error_status == 2) + goto yyexhaustedlab; + } +# undef YYSYNTAX_ERROR +#endif + } + + yyerror_range[1] = yylloc; + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse lookahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval, &yylloc, ofwdev); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse lookahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (/*CONSTCOND*/ 0) + goto yyerrorlab; + + yyerror_range[1] = yylsp[1-yylen]; + /* Do not reclaim the symbols of the rule whose action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (!yypact_value_is_default (yyn)) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + yyerror_range[1] = *yylsp; + yydestruct ("Error: popping", + yystos[yystate], yyvsp, yylsp, ofwdev); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + + yyerror_range[2] = yylloc; + /* Using YYLLOC is tempting, but would change the location of + the lookahead. YYLOC is available though. */ + YYLLOC_DEFAULT (yyloc, yyerror_range, 2); + *++yylsp = yyloc; + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +#if !defined yyoverflow || YYERROR_VERBOSE +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (ofwdev, YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: + if (yychar != YYEMPTY) + { + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = YYTRANSLATE (yychar); + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval, &yylloc, ofwdev); + } + /* Do not reclaim the symbols of the rule whose action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + yystos[*yyssp], yyvsp, yylsp, ofwdev); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif +#if YYERROR_VERBOSE + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); +#endif + return yyresult; +} +#line 269 "prom_parse.y" /* yacc.c:1906 */ + diff --git a/utils/fwparam_ibft/prom_parse.tab.h b/utils/fwparam_ibft/prom_parse.tab.h new file mode 100644 index 0000000..4897b23 --- /dev/null +++ b/utils/fwparam_ibft/prom_parse.tab.h @@ -0,0 +1,102 @@ +/* A Bison parser, made by GNU Bison 3.0.4. */ + +/* Bison interface for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc. + + 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 3 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, see . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +#ifndef YY_YY_PROM_PARSE_TAB_H_INCLUDED +# define YY_YY_PROM_PARSE_TAB_H_INCLUDED +/* Debug traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif +#if YYDEBUG +extern int yydebug; +#endif + +/* Token type. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + enum yytokentype + { + BUSNAME = 258, + BOOTDEV = 259, + IPV4 = 260, + IQN = 261, + OBPPARM = 262, + OBPQUAL = 263, + HEX4 = 264, + HEX16 = 265, + VDEVICE = 266, + VDEVINST = 267, + VDEVDEV = 268, + VDEVRAW = 269, + CHOSEN = 270, + FILENAME = 271 + }; +#endif + +/* Value type. */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED + +union YYSTYPE +{ +#line 34 "prom_parse.y" /* yacc.c:1909 */ + +#define STR_LEN 16384 + char str[STR_LEN]; + +#line 76 "prom_parse.tab.h" /* yacc.c:1909 */ +}; + +typedef union YYSTYPE YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define YYSTYPE_IS_DECLARED 1 +#endif + +/* Location type. */ +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE YYLTYPE; +struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +}; +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + +extern YYSTYPE yylval; +extern YYLTYPE yylloc; +int yyparse (struct ofw_dev *ofwdev); + +#endif /* !YY_YY_PROM_PARSE_TAB_H_INCLUDED */ diff --git a/utils/fwparam_ibft/prom_parse.y b/utils/fwparam_ibft/prom_parse.y new file mode 100644 index 0000000..efe1578 --- /dev/null +++ b/utils/fwparam_ibft/prom_parse.y @@ -0,0 +1,269 @@ +/* + * Copyright (C) IBM Corporation. 2007 + * Author: Doug Maxey + * + * 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, see . + */ + +/* - DEFINITION section. */ + +%{ + /* literal block. include lines, decls, defns. */ +//#define YYDEBUG 1 +#if YYDEBUG +#define DPRINT(fmt,...) printf(fmt,__VA_ARGS__) +#else +#define DPRINT(fmt,...) do {} while(0) +#endif +#include "prom_parse.h" +#include "iscsi_obp.h" + + +%} +%union { +#define STR_LEN 16384 + char str[STR_LEN]; +} + +/* definitions. */ +%token BUSNAME BOOTDEV +%token IPV4 IQN +%token OBPPARM OBPQUAL +%token HEX4 HEX16 +%token VDEVICE VDEVINST VDEVDEV VDEVRAW +%token CHOSEN +%token FILENAME + +%type devpath busses bus bootdev +%type disklabel diskpart +%type vdevice vdev_parms vdev_parm +%type obp_quals obp_qual obp_params obp_param +%type ipaddr ipv4 ipv6 +%type hexpart hexseq + +%locations +%parse-param {struct ofw_dev *ofwdev} + +%% + +devpath: '/' { + DPRINT("****rootonly: \"%s\"\n", "/"); + } + | '/' busses bootdev { + DPRINT("****devpath busses:\n/%s/%s\n", $2, $3); + } + | '/' busses bootdev disklabel { + ofwdev->dev_path = malloc(strlen($2) + + strlen($3) + 3); + sprintf(ofwdev->dev_path, "/%s%s", $2, $3); + DPRINT("****devpath busses bootdev " + "disklabel:\n/%s/%s%s\n", + $2, $3, $4); + } + | '/' busses bootdev obp_quals obp_params { + ofwdev->dev_path = malloc(strlen($2) + + strlen($3) + 3); + sprintf(ofwdev->dev_path, "/%s%s", $2, $3); + DPRINT("****busses bootdev obp_quals obp_parms:\n" + "/%s/%s:%s%s\n", + $2, $3, $4, $5); + } + | '/' busses bootdev obp_quals obp_params disklabel { + ofwdev->dev_path = malloc(strlen($2) + + strlen($3) + 3); + sprintf(ofwdev->dev_path, "/%s%s", $2, $3); + DPRINT("****busses bootdev obp_quals obp_parms " + "disklabel:\n/%s:%s%s%s\n", $2, $4, $5, $6); + } + | '/' vdevice bootdev vdev_parms obp_quals obp_params disklabel { + DPRINT("****vdevice bootdev obp_parms " + "disklabel:\n/%s:%s%s%s%s\n", + $2, $4, $5, $6, $7); + } + ; + +busses: bus { + strcpy($$, $1); + } + | busses '/' bus { + snprintf($$, STR_LEN, "%s/%s", $1, $3); + } + ; + +bus: BUSNAME { + strcpy($$, $1); + } + | BUSNAME '@' HEX4 { + snprintf($$, STR_LEN, "%s@%s", $1, $3); + } + | BUSNAME '@' HEX4 ',' HEX4 { + snprintf($$, STR_LEN, "%s@%s,%s", $1, $3, $5); + } + | BUSNAME '@' HEX16 { + snprintf($$, STR_LEN, "%s@%s", $1, $3); + } + | BUSNAME ',' HEX4 '@' HEX16 { + snprintf($$, STR_LEN, "%s,%s@%s", $1, $3, $5); + } + ; + + +bootdev: '/' BOOTDEV ':' { + snprintf($$, STR_LEN, "/%s", $2); + } + | '/' BOOTDEV '@' HEX4 ':' { + snprintf($$, STR_LEN, "/%s@%s", $2, $4); + } + | '/' BOOTDEV '@' HEX4 ',' HEX4 ':' { + snprintf($$, STR_LEN, "/%s@%s,%s", $2, $4, $6); + } + ; + +vdevice: VDEVICE '/' VDEVINST { + snprintf($$, STR_LEN, "%s/%s", $1, $3); + } + ; + +vdev_parms: ':' vdev_parm { + snprintf($$, STR_LEN, ":%s", $2); + } + | vdev_parms ',' vdev_parm { + snprintf($$, STR_LEN, "%s,%s", $1, $3); + } + | vdev_parms ',' VDEVRAW { + snprintf($$, STR_LEN, "%s,%s", $1, $3); + } + ; + +vdev_parm: VDEVDEV '=' CHOSEN { + snprintf($$, STR_LEN, "%s=%s", $1, $3); + } + ; + +obp_params: ',' obp_param { + snprintf($$, STR_LEN, ",%s", $2); + } + | obp_params ',' obp_param { + snprintf($$, STR_LEN, "%s,%s", $1, $3); + } + | obp_params ',' disklabel { + snprintf($$, STR_LEN, "%s,%s", $1, $3); + } + ; + +obp_param: HEX4 { + snprintf($$, STR_LEN, "%s", $1); + } + | OBPPARM '=' HEX16 { + /* luns > 0 are the SAM-3+ hex representation. */ + obp_parm_hexnum(ofwdev, $1, $3); + snprintf($$, STR_LEN, "%s=%s", $1, $3); + } + | OBPPARM '=' ipaddr { + obp_parm_addr(ofwdev, $1, $3); + snprintf($$, STR_LEN, "%s=%s", $1, $3); + } + | OBPPARM '=' IQN { + obp_parm_iqn(ofwdev, $1, $3); + snprintf($$, STR_LEN, "%s=%s", $1, $3); + } + | OBPPARM '=' HEX4 { + obp_parm_hexnum(ofwdev, $1, $3); + snprintf($$, STR_LEN, "%s=%s", $1, $3); + } + | OBPPARM '=' FILENAME { + obp_parm_str(ofwdev, $1, $3); + snprintf($$, STR_LEN, "%s=%s", $1, $3); + } + ; + +obp_quals: obp_qual { + snprintf($$, STR_LEN, "%s", $1); + } + | obp_quals ',' obp_qual { + snprintf($$, STR_LEN, "%s,%s", $1, $3); + } + ; + +obp_qual: OBPQUAL { + snprintf($$, STR_LEN, "%s", obp_qual_set(ofwdev, $1)); + } + | vdev_parm { + snprintf($$, STR_LEN, "%s", $1); + } + ; + +ipaddr: ipv4 { + snprintf($$, STR_LEN, "%s", $1); + } + | ipv6 { + snprintf($$, STR_LEN, "%s", $1); + } + ; + +ipv4: IPV4 { + snprintf($$, STR_LEN, "%s", $1); + } + ; + +ipv6: hexpart { + snprintf($$, STR_LEN, "%s", $1); + } + | hexpart ':' ipv4 { + snprintf($$, STR_LEN, "%s:%s", $1, $3); + } + ; + +hexpart: hexseq { + snprintf($$, STR_LEN, "%s", $1); + } + | hexpart "::" { + snprintf($$, STR_LEN, "%s::", $1); + } + | hexpart "::" hexseq { + snprintf($$, STR_LEN, "%s::%s", $1, $3); + } + | "::" hexseq { + snprintf($$, STR_LEN, "::%s", $2); + } + ; + +hexseq: HEX4 { + snprintf($$, STR_LEN, "%s", $1); + } + | hexseq ":" HEX4 { + snprintf($$, STR_LEN, "%s:%s", $1, $3); + } + ; + +disklabel: diskpart { + snprintf($$, STR_LEN, "%s", $1); + } + | HEX4 diskpart { + snprintf($$, STR_LEN, "%s%s", $1, $2); + } + | '@' HEX4 ',' HEX4 diskpart { + snprintf($$, STR_LEN, "@%s,%s%s", $2, $4, $5); + } + ; + +diskpart: ':' HEX4 { + snprintf($$, STR_LEN, ":%s", $2); + } + | ':' HEX4 ',' FILENAME { + snprintf($$, STR_LEN, ":%s,%s", $2, $4); + } + ; + +%% diff --git a/utils/iscsi-gen-initiatorname b/utils/iscsi-gen-initiatorname new file mode 100755 index 0000000..88bd43b --- /dev/null +++ b/utils/iscsi-gen-initiatorname @@ -0,0 +1,73 @@ +#!/bin/bash +# +# /sbin/iscsi-gen-initiatorname +# +# Generate a default iSCSI Initiatorname for SUSE installations. +# +# Copyright (c) 2011 Hannes Reinecke, SUSE Labs +# This script is licensed under the GPL. +# + +if [ "$1" ] ; then + if [ "$1" = "-f" ] ; then + FORCE=1 + else + echo "Invalid option $1" + echo "Usage: $0 [-f]" + exit 1 + fi +fi + +if [ -d /sys/firmware/ibft/initiator ] ; then + read iSCSI_INITIATOR_NAME < /sys/firmware/ibft/initiator/initiator-name +fi + +if [ -f /etc/iscsi/initiatorname.iscsi -a -z "$FORCE" ] ; then + if [ "$iSCSI_INITIATOR_NAME" ] ; then + eval $(cat /etc/iscsi/initiatorname.iscsi | sed -e '/^#/d') + if [ "$iSCSI_INITIATOR_NAME" != "$InitiatorName" ] ; then + echo "iSCSI Initiatorname from iBFT is different from the current setting." + echo "Please call '/sbin/iscsi-gen-initiatorname -f' to update the iSCSI Initiatorname." + exit 1 + fi + fi +fi + +if [ "$iSCSI_INITIATOR_NAME" ] ; then + cat << EOF >> /etc/iscsi/initiatorname.iscsi +## +## /etc/iscsi/iscsi.initiatorname +## +## iSCSI Initiatorname taken from iBFT BIOS tables. +## +## DO NOT EDIT OR REMOVE THIS FILE! +## If you remove this file, the iSCSI daemon will not start. +## Any change here will not be reflected to the iBFT BIOS tables. +## If a different initiatorname is required please change the +## initiatorname in the BIOS setup and call +## /sbin/iscsi-gen-initiatorname -f +## to recreate an updated version of this file. +## +InitiatorName=$iSCSI_INITIATOR_NAME +EOF +fi + +if [ ! -f /etc/iscsi/initiatorname.iscsi ] ; then + cat << EOF >> /etc/iscsi/initiatorname.iscsi +## +## /etc/iscsi/iscsi.initiatorname +## +## Default iSCSI Initiatorname. +## +## DO NOT EDIT OR REMOVE THIS FILE! +## If you remove this file, the iSCSI daemon will not start. +## If you change the InitiatorName, existing access control lists +## may reject this initiator. The InitiatorName must be unique +## for each iSCSI initiator. Do NOT duplicate iSCSI InitiatorNames. +EOF + ISSUEDATE="1996-04" + INAME=$(/sbin/iscsi-iname -p iqn.$ISSUEDATE.de.suse:01) + printf "InitiatorName=$INAME\n" >>/etc/iscsi/initiatorname.iscsi + chmod 0600 /etc/iscsi/initiatorname.iscsi +fi + diff --git a/utils/iscsi-iname.c b/utils/iscsi-iname.c new file mode 100644 index 0000000..da850dc --- /dev/null +++ b/utils/iscsi-iname.c @@ -0,0 +1,145 @@ +/* + * iSCSI InitiatorName creation utility + * Copyright (C) 2001 Cisco Systems, Inc. + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * 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. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-iname.c,v 1.1.2.3 2005/03/15 06:33:44 wysochanski Exp $ + * + * iscsi-iname.c - Compute an iSCSI InitiatorName for this host. + * Note that to ensure uniqueness, the system time is + * a factor. This name must be cached and only regenerated + * if there is no cached value. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "md5.h" + +#define RANDOM_NUM_GENERATOR "/dev/urandom" + +/* iSCSI names have a maximum length of 223 characters, we reserve 13 to append + * a seperator and 12 characters (6 random bytes in hex representation) */ +#define PREFIX_MAX_LEN 210 + +int +main(int argc, char *argv[]) +{ + struct timeval time; + struct utsname system_info; + long hostid; + struct MD5Context context; + unsigned char digest[16]; + unsigned char *bytes = digest; + unsigned char entropy[16]; + int e; + int fd; + char *prefix; + + /* initialize */ + memset(digest, 0, sizeof (digest)); + memset(&context, 0, sizeof (context)); + MD5Init(&context); + + /* take a prefix if given, otherwise use a default. */ + if (argc > 1 && argv[1]) { + prefix = argv[1]; + if (( strcmp(prefix, "-h") == 0 ) || + ( strcmp(prefix, "--help") == 0 )) { + printf("\nGenerates a unique iSCSI node name " + "on every invocation.\n"); + exit(0); + } else if ( strcmp(prefix, "-p") == 0 ) { + prefix = argv[2]; + if (strnlen(prefix, PREFIX_MAX_LEN + 1) > PREFIX_MAX_LEN) { + printf("Error: Prefix cannot exceed %d " + "characters.\n", PREFIX_MAX_LEN); + exit(1); + } + } else { + printf("\nUsage: iscsi-iname [-h | --help | " + "-p ]\n"); + exit(0); + } + } else { + prefix = "iqn.2016-04.com.open-iscsi"; + } + + /* try to feed some entropy from the pool to MD5 in order to get + * uniqueness properties + */ + + if ((fd = open(RANDOM_NUM_GENERATOR, O_RDONLY))) { + e = read(fd, &entropy, 16); + if (e >= 1) + MD5Update(&context, (md5byte *)entropy, e); + close(fd); + } + + /* time the name is created is a factor in order to get + * uniqueness properties + */ + if (gettimeofday(&time, NULL) < 0) { + perror("error: gettimeofday failed"); + return 1; + } + MD5Update(&context, (md5byte *) & time.tv_sec, sizeof (time.tv_sec)); + MD5Update(&context, (md5byte *) & time.tv_usec, sizeof (time.tv_usec)); + + /* hostid */ + hostid = gethostid(); + MD5Update(&context, (md5byte *) & hostid, sizeof (hostid)); + + /* get the hostname and system name */ + if (uname(&system_info) < 0) { + perror("error: uname failed"); + return 1; + } + MD5Update(&context, (md5byte *) system_info.sysname, + sizeof (system_info.sysname)); + MD5Update(&context, (md5byte *) system_info.nodename, + sizeof (system_info.nodename)); + MD5Update(&context, (md5byte *) system_info.release, + sizeof (system_info.release)); + MD5Update(&context, (md5byte *) system_info.version, + sizeof (system_info.version)); + MD5Update(&context, (md5byte *) system_info.machine, + sizeof (system_info.machine)); + + /* compute the md5 hash of all the bits we just collected */ + MD5Final(digest, &context); + + /* vary which md5 bytes we pick (though we probably don't need to do + * this, since hopefully MD5 produces results such that each byte is as + * good as any other). + */ + + if ((fd = open(RANDOM_NUM_GENERATOR, O_RDONLY))) { + if (read(fd, entropy, 1) == 1) + bytes = &digest[(entropy[0] % (sizeof(digest) - 6))]; + close(fd); + } + + /* print the prefix followed by 6 bytes of the MD5 hash */ + printf("%s:%x%x%x%x%x%x\n", prefix, + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5]); + return 0; +} diff --git a/utils/iscsi_discovery b/utils/iscsi_discovery new file mode 100755 index 0000000..be2f390 --- /dev/null +++ b/utils/iscsi_discovery @@ -0,0 +1,195 @@ +#!/bin/bash +# +# Copyright (C) Voltaire Ltd. 2006. 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 +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Author: Dan Bar Dov + +# iscsi_discovery: +# * does a send-targets discovery to the given IP +# * set the transport type to the preferred transport (or tcp is -t flag is not used) +# * tries to login +# * if succeeds, +# o logout, +# o mark record autmatic (unless -m flag is used) +# * else +# o reset transport type to TCP +# o try to login +# o if succeeded +# + logout +# + mark record automatic (unless -m flag is used) +# + +usage() +{ + echo "Usage: $0 [-p ] [-d] [-t [-f]] [-m] [-l]" + echo "Options:" + echo "-p set the port number (default is 3260)." + echo "-d print debugging information" + echo "-t set transport (default is tcp)." + echo "-f force specific transport -disable the fallback to tcp (default is fallback enabled)." + echo " force the transport specified by the argument of the -t flag." + echo "-m manual startup - will set manual startup (default is automatic startup)." + echo "-l login to the new discovered nodes (default is false)." +} + +dbg() +{ + $debug && echo $@ +} + +initialize() +{ + trap "exit" 2 + debug=false + force="0" + log_out="1" + startup_manual="0" + #set default transport to tcp + transport=tcp + #set default port to 3260 + port=3260; +} + +parse_cmdline() +{ + if [ $# -lt 1 ]; then + usage + exit 1 + fi + + # check if the IP address is valid + ip=`echo $1 | awk -F'.' '$1 != "" && $1 <=255 && $2 != "" && $2 <= 255 && $3 != "" && $3 <= 255 && $4 != "" && $4 <= 255 {print $0}'` + if [ -z "$ip" ]; then + echo "$1 is not a vaild IP address!" + exit 1 + fi + shift + while getopts "dfmlt:p:" options; do + case $options in + d ) debug=true;; + f ) force="1";; + t ) transport=$OPTARG;; + p ) port=$OPTARG;; + m ) startup_manual="1";; + l ) log_out=0;; + \? ) usage + exit 1;; + * ) usage + exit 1;; + esac + done +} + +discover() +{ + # If open-iscsi is already logged in to the portal, exit + if [ $(iscsiadm -m session | grep -c ${ip}:${port}) -ne 0 ]; then + echo "Please logout from all targets on ${ip}:${port} before trying to run discovery on that portal" + exit 2 + fi + + connected=0 + discovered=0 + + dbg "starting discovery to $ip" + disc="$(iscsiadm -m discovery --type sendtargets --portal ${ip}:${port})" + echo "${disc}" | while read portal target + do + portal=${portal%,*} + select_transport + done + + discovered=$(echo "${disc}" | wc -l) + if [ ${discovered} = 0 ]; then + echo "failed to discover targets at ${ip}" + exit 2 + else + echo "discovered ${discovered} targets at ${ip}" + fi +} + +try_login() +{ + if [ "$startup_manual" != "1" ]; then + iscsiadm -m node --targetname ${target} --portal ${portal} --op update -n node.conn[0].startup -v automatic + fi + iscsiadm -m node --targetname ${target} --portal ${portal} --login >/dev/null 2>&1 + ret=$? + if [ ${ret} = 0 ]; then + echo "Set target ${target} to automatic login over ${transport} to portal ${portal}" + ((connected++)) + if [ "$log_out" = "1" ]; then + iscsiadm -m node --targetname ${target} --portal ${portal} --logout + fi + else + echo "Cannot login over ${transport} to portal ${portal}" + iscsiadm -m node --targetname ${target} --portal ${portal} --op update -n node.conn[0].startup -v manual + fi + return ${ret} +} + +set_transport() +{ + transport=$1 + case "$transport" in + iser) + # iSER does not use digest + iscsiadm -m node --targetname ${target} --portal ${portal} \ + --op update -n node.conn[0].iscsi.HeaderDigest -v None + iscsiadm -m node --targetname ${target} --portal ${portal} \ + --op update -n node.conn[0].iscsi.DataDigest -v None + ;; + cxgb3i) + # cxgb3i supports <= 16K packet (BHS + AHS + pdu payload + digests) + iscsiadm -m node --targetname ${target} --portal ${portal} \ + --op update -n node.conn[0].iscsi.MaxRecvDataSegmentLength \ + -v 8192 + ;; + esac + transport_name=`iscsiadm -m node -p ${portal} -T ${target} |awk '/transport_name/ {print $1}'` + iscsiadm -m node --targetname ${target} --portal ${portal} \ + --op update -n ${transport_name} -v ${transport} +} + +select_transport() +{ + set_transport $transport + dbg "Testing $transport-login to target ${target} portal ${portal}" + try_login; + if [ $? != 0 -a "$force" = "0" ]; then + set_transport tcp + dbg "starting to test tcp-login to target ${target} portal ${portal}" + try_login; + fi +} + +check_iscsid() +{ + #check if iscsid is running + pidof iscsid &>/dev/null + ret=$? + if [ $ret -ne 0 ]; then + echo "iscsid is not running" + echo "Exiting..." + exit 1 + fi +} + +check_iscsid +initialize +parse_cmdline "$@" +discover diff --git a/utils/iscsi_fw_login b/utils/iscsi_fw_login new file mode 100644 index 0000000..1312ed3 --- /dev/null +++ b/utils/iscsi_fw_login @@ -0,0 +1,12 @@ +#!/bin/bash +# +# iscsi_fw_login -- login to iscsi firmware targets, if any +# +# This script is called when udev discovers a new iscsi +# firmware target +# + +ARGS="-m fw -l" +ISCSIADM="/sbin/iscsiadm" + +$ISCSIADM $ARGS diff --git a/utils/iscsi_offload b/utils/iscsi_offload new file mode 100755 index 0000000..7cd1dad --- /dev/null +++ b/utils/iscsi_offload @@ -0,0 +1,378 @@ +#!/bin/bash +# +# iscsi_offload +# +# Configure iSCSI offload engines for use with open-iscsi +# Usage: +# iscsi_offload [-d | -f | -i | -t ] +# +# Copyright (c) 2011 Hannes Reinecke, SUSE Labs +# This script is licensed under the GPL. +# +# The script creates an open-iscsi interface definition +# in the style -, where matches the +# network interface passed on the commandline. +# If '-t' (test mode) is passed as an option, the script +# will not create nor modify any setting but just print +# the currently active ones. +# +# Currently the script works with Broadcom (bnx2i) and +# Chelsio T3 (cxgbi) iSCSI offload engines. +# Should work with Chelsio T4, but has not been tested. +# ServerEngines (be2iscsi) and QLogic (qla4xxx) can only +# be configured via BIOS, open-iscsi support is still in +# development. +# + +# +# Return codes: +# 0: Success +# 1: Invalid command line parameter +# 2: iSCSI offloading not supported +# 3: Error during module loading +# 4: Cannot configure interface via iscsiadm, use BIOS setup +# 5: internal error running iscsiadm +# +# Output: +# [none|dhcp|ip |ibft] +# where +# : MAC Address of the iSCSI offload engine +# none: No IP configuration set for the iSCSI offload engine +# dhcp: iSCSI offload engine configured for DHCP +# ip: iSCSI offload engine configured with static IP address +# ibft: iSCSI offload engine configured from iBFT values +# + +# +# Figure out the MAC address of the iSCSI offload engine +# corresponding to a NIC from a given PCI device. +# bnx2 is using one PCI device per port for both network and iSCSI offloading +# cxgb3 is using one PCI device for everything. +# +iscsi_macaddress_from_pcidevice() +{ + local path=$1 + local if=$2 + local h + local host + + for h in $path/host* ; do + if [ -d "$h" ] ; then + host=${h##*/} + read netdev < /sys/class/iscsi_host/$host/netdev + if [ "$netdev" = "$IFNAME" ] ; then + read mac < /sys/class/iscsi_host/$host/hwaddress + if [ "$mac" != "00:00:00:00:00:00" ] ; then + echo "$mac" + fi + break; + fi + fi + done +} + +# +# Figure out the MAC address of the iSCSI offload engine +# corresponding to a NIC from a given PCI function. +# It is assumed that the MAC address of the iSCSI offload +# engine is equal of the MAC address of the NIC plus one. +# Suitable for be2iscsi and qla4xxx +# +iscsi_macaddress_from_pcifn() +{ + local path=$1 + local if=$2 + local h + local host + local ifmac + + ifmac=$(ip addr show dev $if | sed -n 's/ *link\/ether \(.*\) brd.*/\1/p') + m5=$(( 0x${ifmac##*:} )) + m5=$(( $m5 + 1 )) + ifmac=$(printf "%s:%02x" ${ifmac%:*} $m5) + for host in /sys/class/iscsi_host/host* ; do + if [ -L "$host" ] ; then + read mac < $host/hwaddress + if [ "$mac" = "$ifmac" ] ; then + echo "$mac" + break; + fi + fi + done +} + +update_iface_setting() { + local iface="$1" + local name="$2" + local value="$3" + + iface_value=$(iscsiadm -m iface -I $iface | sed -n "s/$name = \(.*\)/\1/p") + if [ "$iface_value" = "" ] ; then + iface_value= + fi + if [ "$iface_value" != "$value" ] ; then + if ! iscsiadm -m iface -I $iface -o update -n "$name" -v "$value" ; then + return 1 + fi + fi + return 0 +} + +while getopts di:t options ; do + case $options in + d ) mode=dhcp;; + i ) mode=static + optaddr=$OPTARG + ;; + f ) mode=firmware;; + t ) dry_run=1;; + ?) printf "Usage: %s [-d|-t|-i ipaddr|-f] ifname\n" $0 + exit 1;; + esac +done +shift $(($OPTIND - 1)) + +IFNAME=$1 +ibft_mode="none" + +if [ -z "$IFNAME" ] ; then + echo "No interface specified" + exit 1 +fi + +if [ "$dry_run" ] ; then + if [ "$mode" = "dhcp" ] ; then + echo "'-t' specified, ignoring '-d'" + mode= + elif [ "$mode" = "static" ] ; then + echo "'-t' specified, ignoring '-s'" + mode= + fi +fi + +if [ ! -L /sys/class/net/$IFNAME ] ; then + echo "Interface $IFNAME not found" + exit 1 +fi + +if [ "$optaddr" ] && ! ip route get $optaddr ; then + echo "Invalid IP address $optaddr" + exit 1 +fi +if [ "$dry_run" ] ; then + mode= +fi + + +ifpath=$(cd -P /sys/class/net/$IFNAME; echo $PWD) +pcipath=$(cd -P $ifpath/device; echo $PWD) + +if [ -d $pcipath ] ; then + drvlink=$(readlink $pcipath/driver) + driver=${drvlink##*/} +fi + +if [ -z "$driver" ] ; then + echo "No driver found for interface $IFNAME" + exit 1 +fi + +case "$driver" in + bnx2*) + mod=bnx2i + ;; + cxgb*) + mod=cxgb3i + ;; + be2*) + mod=be2iscsi + ;; + qla*) + mod=qla4xxx + ;; +esac + +if [ -z "$mod" ] ; then + echo "iSCSI offloading not supported on interface $IFNAME" + exit 2 +fi + +# Check if the required modules are already loaded +loaded=$(sed -n "/^$mod/p" /proc/modules) +if [ -z "$loaded" ] ; then + modprobe $mod +fi + +loaded=$(sed -n "/^$mod/p" /proc/modules) +if [ -z "$loaded" ] ; then + echo "Loading of $mod.ko failed, please check dmesg" + exit 3 +fi + +# Get the correct MAC address for the various devices +if [ "$mod" = "bnx2i" ] ; then + mac=$(iscsi_macaddress_from_pcidevice $pcipath $IFNAME) +elif [ "$mod" = "cxgb3i" ] ; then + mac=$(iscsi_macaddress_from_pcidevice $pcipath $IFNAME) +elif [ "$mod" = "be2iscsi" ] ; then + mac=$(iscsi_macaddress_from_pcifn $pcipath $IFNAME) +elif [ "$mod" = "qla4xxx" ] ; then + mac=$(iscsi_macaddress_from_pcifn $pcipath $IFNAME) +fi + +if [ -z "$mac" ] ; then + echo "iSCSI offloading not supported on interface $IFNAME" + exit 2 +fi + +gen_iface="$mod.$mac" +ioe_iface="${IFNAME}-${mod}" + +# Get existing settings +if iscsiadm -m iface -I $ioe_iface > /dev/null 2>&1 ; then + ioe_mac=$(iscsiadm -m iface -I $ioe_iface 2> /dev/null| sed -n "s/iface\.hwaddress = \(.*\)/\1/p") + ioe_mod=$(iscsiadm -m iface -I $ioe_iface 2> /dev/null| sed -n "s/iface\.transport_name = \(.*\)/\1/p") + ipaddr=$(iscsiadm -m iface -I $ioe_iface 2> /dev/null| sed -n "s/iface\.ipaddress = \(.*\)/\1/p") + if [ "$ipaddr" == "" ] ; then + ipaddr= + fi +elif [ "$mod" = "be2iscsi" ] ; then + ioe_mac=$mac + ioe_mod=$mod +else + # Create new interface + iscsiadm -m iface -I $ioe_iface --op=new 2> /dev/null + ioe_mac= + ioe_mod= + ipaddr= +fi + +if [ -z "$dry_run" ] ; then + if [ "$ioe_mac" != "$mac" ] ; then + if [ -n "$ioe_mac" ] ; then + echo "Warning: Updating MAC address on iface $ioe_iface" + fi + update_iface_setting $ioe_iface iface.hwaddress "$mac" + fi + + if [ "$ioe_mod" != "$mod" ] ; then + if [ -n "$ioe_mod" ] ; then + echo "Warning: Update transport on iface $ioe_iface" + fi + update_iface_setting $ioe_iface iface.transport_name "$mod" + fi +elif [ -z "$ipaddr" ] ; then + ipaddr=$(iscsiadm -m iface -I $gen_iface 2> /dev/null| sed -n "s/iface\.ipaddress = \(.*\)/\1/p") + if [ "$ipaddr" = "" ] ; then + ipaddr= + fi +elif [ "$ioe_mod" != "$mod" ] ; then + echo "Warning: Transport mismatch on iface $ioe_iface: $ioe_mod should be $mod" +fi + +# Check iBFT setting +for d in /sys/firmware/* ; do + [ -d $d ] || continue + [ -d $d/ethernet0 ] || continue + iboot_dir=$d +done +if [ -n "$iboot_dir" ] && [ -d "$iboot_dir" ] ; then + for if in ${iboot_dir}/ethernet* ; do + read ibft_mac < $if/mac + [ "$ibft_mac" = "$mac" ] || continue + ibft_origin=0 + [ -f ${if}/origin ] && read ibft_origin < $if/origin + if [ "$ibft_origin" -eq 1 ] ; then + ibft_mode="static" + elif [ "$ibft_origin" -eq 3 ] ; then + ibft_mode="dhcp" + fi + [ -f $if/dhcp ] && read ibft_dhcp < $if/dhcp + if [ -n "$ibft_dhcp" -a "$ibft_mode" != "dhcp" ] ; then + ibft_mode=dhcp + fi + if [ "$ibft_mode" = "dhcp" ] ; then + ibft_ipaddr="0.0.0.0" + ibft_gateway= + ibft_mask= + break + fi + [ -f $if/ip-addr ] && read ibft_ipaddr < $if/ip-addr + [ -f $if/gateway ] && read ibft_gateway < $if/gateway + [ -f $if/subnet-mask ] && read ibft_mask < $if/subnet-mask + break + done +fi + +if [ -z "$optaddr" ] && [ "$ibft_ipaddr" ] ; then + optaddr=$ibft_ipaddr +fi + +# Check if the interface needs to be configured +if [ -z "$mode" ] ; then + if [ "$ibft_mode" != "none" ] ; then + echo "$mac ibft" + mode="ibft" + elif [ -z "$ipaddr" ] ; then + echo "$mac none" + mode="none" + elif [ "$ipaddr" = "0.0.0.0" ] ; then + echo "$mac dhcp" + ipaddr= + mode="dhcp" + else + echo "$mac ip $ipaddr" + mode="static" + fi + [ "$dry_run" ] && exit 0 +elif [ "$mode" = "dhcp" ] ; then + if [ "$ipaddr" = "0.0.0.0" ] ; then + echo "$mac dhcp" + exit 0 + fi + optaddr="0.0.0.0" +elif [ "$mode" = "static" ] && [ "$ipaddr" = "$optaddr" ] ; then + echo "$mac ip $ipaddr" + exit 0 +fi + +if [ "$mod" = "be2iscsi" ] ; then + exit 4 +fi + +if ! update_iface_setting $ioe_iface iface.ipaddress "$optaddr" ; then + echo "Failed to set IP address: $?" + exit 1 +fi +if ! update_iface_setting $gen_iface iface.ipaddress "$optaddr" ; then + echo "Failed to set IP address for generic interface: $?" + exit 1 +fi + +if ! update_iface_setting $ioe_iface iface.gateway "$ibft_gateway" ; then + echo "Failed to set gateway address: $?" + exit 1 +fi + +if ! update_iface_setting $gen_iface iface.gateway "$ibft_gateway" ; then + echo "Failed to set gateway address for generic interface: $?" + exit 1 +fi + +if ! update_iface_setting $ioe_iface iface.subnet_mask "$ibft_mask" ; then + echo "Failed to set subnet mask: $?" + exit 1 +fi + +if ! update_iface_setting $gen_iface iface.subnet_mask "$ibft_mask" ; then + echo "Failed to set subnet mask for generic interface: $?" + exit 1 +fi + +if [ "$mod" = "qla4xxx" ] ; then + iscsiadm -m iface -H $mac -o applyall +fi +ip link set dev $IFNAME up + +exit 0 + diff --git a/utils/md5.c b/utils/md5.c new file mode 100644 index 0000000..cbe8d08 --- /dev/null +++ b/utils/md5.c @@ -0,0 +1,242 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + * + * Changed so as no longer to depend on Colin Plumb's `usual.h' header + * definitions; now uses stuff from dpkg's config.h. + * - Ian Jackson . + * Still in the public domain. + */ + +#include + +#include "md5.h" + +#if (__BYTE_ORDER == __BIG_ENDIAN) +/* + * we can compile this away for little endian since + * it does not do anything on those archs + */ +void +byteSwap(uint32_t * buf, unsigned words) +{ + md5byte *p = (md5byte *) buf; + + do { + *buf++ = (uint32_t) ((unsigned) p[3] << 8 | p[2]) << 16 | + ((unsigned) p[1] << 8 | p[0]); + p += 4; + } while (--words); +} +#else +#define byteSwap(buf,words) +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void +MD5Init(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bytes[0] = 0; + ctx->bytes[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void +MD5Update(struct MD5Context *ctx, md5byte const *buf, unsigned len) +{ + uint32_t t; + + /* Update byte count */ + + t = ctx->bytes[0]; + if ((ctx->bytes[0] = t + len) < t) + ctx->bytes[1]++; /* Carry from low to high */ + + t = 64 - (t & 0x3f); /* Space available in ctx->in (at least 1) */ + if (t > len) { + memcpy((md5byte *) ctx->in + 64 - t, buf, len); + return; + } + /* First chunk is an odd size */ + memcpy((md5byte *) ctx->in + 64 - t, buf, t); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + buf += t; + len -= t; + + /* Process data in 64-byte chunks */ + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void +MD5Final(md5byte digest[16], struct MD5Context *ctx) +{ + int count = ctx->bytes[0] & 0x3f; /* Number of bytes in ctx->in */ + md5byte *p = (md5byte *) ctx->in + count; + + /* Set the first char of padding to 0x80. There is always room. */ + *p++ = 0x80; + + /* Bytes of padding needed to make 56 bytes (-8..55) */ + count = 56 - 1 - count; + + if (count < 0) { /* Padding forces an extra block */ + memset(p, 0, count + 8); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + p = (md5byte *) ctx->in; + count = 56; + } + memset(p, 0, count); + byteSwap(ctx->in, 14); + + /* Append length in bits and transform */ + ctx->in[14] = ctx->bytes[0] << 3; + ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29; + MD5Transform(ctx->buf, ctx->in); + + byteSwap(ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof (*ctx)); /* In case it's sensitive */ +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f,w,x,y,z,in,s) \ + (w += f(x,y,z) + in, w = (w<>(32-s)) + x) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void +MD5Transform(uint32_t buf[4], uint32_t const in[16]) +{ + register uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#endif diff --git a/utils/md5.h b/utils/md5.h new file mode 100644 index 0000000..7a204e9 --- /dev/null +++ b/utils/md5.h @@ -0,0 +1,41 @@ +/* + * This is the header file for the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + * + * Changed so as no longer to depend on Colin Plumb's `usual.h' + * header definitions; now uses stuff from dpkg's config.h + * - Ian Jackson . + * Still in the public domain. + */ + +#ifndef MD5_H +#define MD5_H + +#include + +#define md5byte unsigned char + +struct MD5Context { + uint32_t buf[4]; + uint32_t bytes[2]; + uint32_t in[16]; +}; + +void MD5Init(struct MD5Context *context); +void MD5Update(struct MD5Context *context, md5byte const *buf, unsigned len); +void MD5Final(unsigned char digest[16], struct MD5Context *context); +void MD5Transform(uint32_t buf[4], uint32_t const in[16]); + +#endif /* !MD5_H */ diff --git a/utils/sysdeps/Makefile b/utils/sysdeps/Makefile new file mode 100644 index 0000000..4299164 --- /dev/null +++ b/utils/sysdeps/Makefile @@ -0,0 +1,16 @@ +# This Makefile will work only with GNU make. + +CFLAGS ?= -O2 -fno-inline -g +CFLAGS += $(WARNFLAGS) -Wall -Wstrict-prototypes + +SYSDEPS_OBJS=sysdeps.o + +all: $(SYSDEPS_OBJS) + +clean: + rm -f *.o .depend + +depend: + gcc $(CFLAGS) -M `ls *.c` > .depend + +-include .depend diff --git a/utils/sysdeps/sysdeps.c b/utils/sysdeps/sysdeps.c new file mode 100644 index 0000000..e39730d --- /dev/null +++ b/utils/sysdeps/sysdeps.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003 Greg Kroah-Hartman + * Copyright (C) 2005-2006 Kay Sievers + * + * 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 version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __GLIBC__ +size_t strlcpy(char *dst, const char *src, size_t size) +{ + size_t bytes = 0; + char *q = dst; + const char *p = src; + char ch; + + while ((ch = *p++)) { + if (bytes+1 < size) + *q++ = ch; + bytes++; + } + + /* If size == 0 there is no space for a final null... */ + if (size) + *q = '\0'; + return bytes; +} + +size_t strlcat(char *dst, const char *src, size_t size) +{ + size_t bytes = 0; + char *q = dst; + const char *p = src; + char ch; + + while (bytes < size && *q) { + q++; + bytes++; + } + if (bytes == size) + return (bytes + strlen(src)); + + while ((ch = *p++)) { + if (bytes+1 < size) + *q++ = ch; + bytes++; + } + + *q = '\0'; + return bytes; +} +#endif /* __GLIBC__ */