From f2a3180cc0123acabfed9e48200be0a063f695c0 Mon Sep 17 00:00:00 2001 From: Benjamin Cama Date: Thu, 7 Jul 2011 12:45:05 +0200 Subject: [PATCH] Imported Upstream version 2.1.21 --- COPYING | 340 +++ Changes | 479 ++++ Docs/l2tpns.8 | 70 + Docs/manual.html | 1074 +++++++ Docs/nsctl.8 | 69 + Docs/startup-config.5 | 363 +++ INSTALL | 74 + INTERNALS | 265 ++ Makefile | 133 + THANKS | 29 + arp.c | 64 + autosnoop.c | 75 + autothrottle.c | 158 ++ bgp.c | 1244 ++++++++ bgp.h | 205 ++ cli.c | 3066 ++++++++++++++++++++ cluster.c | 1870 ++++++++++++ cluster.h | 89 + constants.c | 227 ++ constants.h | 17 + control.c | 163 ++ control.h | 54 + etc/ip_pool.default | 2 + etc/l2tpns.logrotate | 9 + etc/startup-config.default | 117 + etc/users.default | 1 + fake_epoll.h | 179 ++ garden.c | 297 ++ icmp.c | 177 ++ l2tpns.c | 5489 ++++++++++++++++++++++++++++++++++++ l2tpns.h | 859 ++++++ l2tpns.spec | 47 + ll.c | 141 + ll.h | 28 + md5.c | 274 ++ md5.h | 28 + nsctl.c | 238 ++ plugin.h | 129 + ppp.c | 2000 +++++++++++++ radius.c | 1093 +++++++ scripts/l2tpns-capture | 68 + scripts/l2tpns-monitor | 28 + scripts/l2tpns.script | 93 + sessionctl.c | 74 + setrxspeed.c | 41 + snoopctl.c | 122 + stripdomain.c | 31 + tbf.c | 354 +++ tbf.h | 45 + test/README | 13 + test/bounce.c | 96 + test/generateload.c | 1288 +++++++++ test/ping-sweep | 121 + test/radius.c | 723 +++++ throttlectl.c | 135 + util.c | 175 ++ util.h | 13 + 57 files changed, 24656 insertions(+) create mode 100644 COPYING create mode 100644 Changes create mode 100644 Docs/l2tpns.8 create mode 100644 Docs/manual.html create mode 100644 Docs/nsctl.8 create mode 100644 Docs/startup-config.5 create mode 100644 INSTALL create mode 100644 INTERNALS create mode 100644 Makefile create mode 100644 THANKS create mode 100644 arp.c create mode 100644 autosnoop.c create mode 100644 autothrottle.c create mode 100644 bgp.c create mode 100644 bgp.h create mode 100644 cli.c create mode 100644 cluster.c create mode 100644 cluster.h create mode 100644 constants.c create mode 100644 constants.h create mode 100644 control.c create mode 100644 control.h create mode 100644 etc/ip_pool.default create mode 100644 etc/l2tpns.logrotate create mode 100644 etc/startup-config.default create mode 100644 etc/users.default create mode 100644 fake_epoll.h create mode 100644 garden.c create mode 100644 icmp.c create mode 100644 l2tpns.c create mode 100644 l2tpns.h create mode 100644 l2tpns.spec create mode 100644 ll.c create mode 100644 ll.h create mode 100644 md5.c create mode 100644 md5.h create mode 100644 nsctl.c create mode 100644 plugin.h create mode 100644 ppp.c create mode 100644 radius.c create mode 100644 scripts/l2tpns-capture create mode 100644 scripts/l2tpns-monitor create mode 100644 scripts/l2tpns.script create mode 100644 sessionctl.c create mode 100644 setrxspeed.c create mode 100644 snoopctl.c create mode 100644 stripdomain.c create mode 100644 tbf.c create mode 100644 tbf.h create mode 100644 test/README create mode 100644 test/bounce.c create mode 100644 test/generateload.c create mode 100644 test/ping-sweep create mode 100644 test/radius.c create mode 100644 throttlectl.c create mode 100644 util.c create mode 100644 util.h diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d60c31a --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/Changes b/Changes new file mode 100644 index 0000000..abe7854 --- /dev/null +++ b/Changes @@ -0,0 +1,479 @@ +* Fri Dec 1 2006 Brendan O'Dea 2.1.21 +- Security: Rhys Kidd identified a vulnerability in the handling of + heartbeat packets. Drop oversize heartbeat packets. + +* Thu Aug 3 2006 Brendan O'Dea 2.1.20 +- Fix sign problem with reporting of unknown VSAs. +- Allow DNS servers to be specified either using the old or new + vendor-specific Ascend formats. + +* Fri Jun 23 2006 Brendan O'Dea 2.1.19 +- Kludge around problem with Netgear DM602 authentication. +- Use result code AVP to set Acct-Terminate-Cause is disconnect cause + AVP is not present. + +* Tue Apr 18 2006 Brendan O'Dea 2.1.18 +- Don't shutdown on TerminateReq, wait for CDN. +- Interpret "local" direction correctly (as LAC) in disconnect AVPs. + +* Thu Apr 13 2006 Brendan O'Dea 2.1.17 +- Fix IPCP length test to allow Terminate-Request (4 bytes). +- Send nsctl responses back using the correct source address (thanks ltd). +- Similarly set the source for DAE responses; use bind_address when + handling forwarded packets on the master. +- Add Acct-Terminate-Cause to RADIUS stop records. + +* Thu Feb 23 2006 Brendan O'Dea 2.1.16 +- Send configured magic-no in LCP EchoReq when LCP is opened. +- Correct addition of single IP to pool (Jonathan Yarden). +- Ensure session changes from LCP ConfigReq/ConfigNak are sent to cluster. +- Verify that RADIUS packets come from a configured server (Jonathan Yarden). +- Avoid endless loop in processipcp, processipv6cp. +- Additional length checks in processlcp. +- Allow peer to request a new magic-number, or to disable magic-numbers. +- Decrease ip_conntrack_tcp_timeout_established to 5hrs (table filling). + +* Mon Dec 19 2005 Brendan O'Dea 2.1.15 +- Drop backtrace. +- Reduce logging of LCP EchoReply packets. +- Break LCP configure loop with shutdown. +- Limit value of MRU of 1492 (rfc2516). +- Tun MTU should be MRU (not MRU+4). +- Add Service-Type/Framed-Protocol to RADIUS records (Paul Martin). + +* Fri Dec 9 2005 Brendan O'Dea 2.1.14 +- Run PLUGIN_RADIUS_ACCOUNT for Start records. + +* Wed Dec 7 2005 Brendan O'Dea 2.1.13 +- Add test/ping-sweep. +- Apply spec changes from Charlie Brady: use License header, change + BuildRoot to include username. +- Fix IPCP negotiation of secondary DNS server, reported by Jon Morby. +- Clean up sessiont, removing some unused fields. +- Remove unused "MAC" config type. +- Reject unknown/unconfigured protocols on the master. +- Sanity check MRU before using in ppp_code_rej, protoreject. + +* Thu Nov 17 2005 Brendan O'Dea 2.1.12 +- Set MTU on tunnel interface so the kernel will re-fragment large + packets to within MRU. +- Fix TCP checksum recalc. +- NAK silly MRU values from peer. + +* Mon Nov 14 2005 Brendan O'Dea 2.1.11 +- Fix fragment handling in ip_filter. +- Exclude counter when comparing filter rules. + +* Sat Nov 5 2005 Brendan O'Dea 2.1.10 +- Add scripts/l2tpns-capture. +- Fix LCP Echo frequency. +- Add Framed-Route entries to RADIUS records. +- Reset restart counters correctly. +- Reset timers on sending ConfigReq. +- Only send one RADIUS Start record, even if IPCP is restarted. + +* Tue Oct 11 2005 Brendan O'Dea 2.1.9 +- Fix Calling-Station-Id in RADIUS accounting records (Slobodan Tomic). +- Fix RADIUS authentication on DAE responses. +- Don't send tunnel HELLO when there are pending control messages. +- Move plugin_radius_reset from *ctl to auto* plugins. +- Add Cisco-AVPairs to RADIUS accounting records via plugin_radius_account. + +* Mon Sep 19 2005 Brendan O'Dea 2.1.8 +- Move code from signal handlers into mainloop, avoiding a race + condition when forking CLI. + +* Fri Sep 16 2005 Brendan O'Dea 2.1.7 +- This time, for sure: really fix Protocol-Reject. + +* Fri Sep 16 2005 Brendan O'Dea 2.1.6 +- Any traffic on a tunnel resets lastrec, not just control messages. +- Use a unique identifier for LCP. +- Fix Code-Reject/Protocol-Reject. +- Add l2tp_mtu configuration option, used to define MRU, MSS. +- Adjust TCP MSS options in SYN and SYN,ACK packets to avoid + fragmentation of tcp packets. + +* Sat Sep 3 2005 Brendan O'Dea 2.1.5 +- Avoid Code-Reject loop. +- Increase size of PPP buffers to MAXETHER. +- Bug fixes for CLI ringbuffer and tunnel HELLO from Yuri. +- Restart rather than halt BGP on receipt of CEASE (Dominique Rousseau). +- Add cluster_mcast_ttl option to allow a cluster to span multiple + subnets (suggested by Tim Devries). + +* Mon Aug 29 2005 Brendan O'Dea 2.1.4 +- Drop level of "Unexpected CHAP message" log. +- Fix parsing of ProtocolRej (allow 1 or two byte protocols). +- Handle rejection of MRU negotiation by peer. +- Use local hostname for tunnel in SCCRP (Alex Kiernan). + +* Wed Aug 17 2005 Brendan O'Dea 2.1.3 +- Fail IPCP negotiation only on ConfigRej of IP-Address. + +* Wed Aug 10 2005 Brendan O'Dea 2.1.2 +- Clear cluster_master on election so that slaves will accept a new master. +- Provide more comments/defaults in etc/startup-config.default. +- Add DAE support (PoD/CoA) from Vladislav Bjelic. +- Clean up new warnings from gcc 4.0. +- Replace flags used for LCP/IPCP with state machine. +- Include Acct-Session-Time in interim records. +- Fix DAE vector, generateload (Alex Kiernan). +- Replace RSA MD5 with public domain version. + +* Tue Jun 14 2005 Brendan O'Dea 2.1.1 +- Add missing newline to backtrace macro. +- Don't send CDN for each session when shutting down tunnels (this is + implicit). +- Move tunnel shutdown from SIGQUIT signal handler to be run once from + still_busy(). Reject new tunnels/sessions while in the process of + shutting down. +- Clarify usage of shutdown signals in documentation. +- Always initialise PRNG. +- Sanity check length of random_vector. +- Fix segv in unhide_value. +- Ping new master when we get C_MASTER and delay next election to allow + the unicast limp-along code to kick in if required. + +* Sun Jun 5 2005 Brendan O'Dea 2.1.0 +- Add IPv6 support from Jonathan McDowell. +- Add CHAP support from Jordan Hrycaj. +- Add interim accounting support from Vladislav Bjelic. +- Negotiate MRU, default 1458 to avoid fragmentation. +- Sanity check that cluster_send_session is not called from a child + process. +- Use bounds-checking lookup functions for string constants. +- Add enum for RADIUS codes. +- Make "call_" prefix implict in CSTAT() macro. +- Fix some format string problems. +- Remove "save_state" option. Not maintained anymore; use clustering + to retain state across restarts. +- Simplify AVP unhiding code. +- Add optional "username" parameter to ungarden control, allowing the + username to be reset before going online. +- Add result/error codes and message to StopCCN when shutting down tunnels. +- Add result/error codes to CDN when shutting down sessions. Sends 2/7 + (general error, try another LNS) when out of IP addresses, and 3 + (adminstrative) for everything else (suggestion from Chris Gates). +- Use cli_error() for error messages and help. +- Don't use LOG() macro in initdata() until the config struct has been + allocated (uses config->debug). +- Initialise log_stream to stderr to catch errors before the config file + is read. +- Make "show running-config" a privileged command (contains clear text + shared secrets). +- Add sessionctl plugin to provide drop/kill via nsctl. +- New config option: allow_duplicate_users which determines whether + or not to kill older sessions with the same username. +- Fix byte counters in accounting records. +- Add Acct-Output-Gigawords, Acct-Input-Gigawords attributes to RADIUS + accounting packets. +- Fix icmp host unreachable to use router address. +- Include endpoint address in accounting dump files. +- Convert mainloop to use epoll rather than select. +- Add note about fragmentation in Docs/manual.html, and a sample + iptables rule for MSS clamping. +- Merge 2.0.22: + + Show session open time in "show session"/"show user" detailed output. + + Have slaves with BGP configured drop BGP on receipt of a shutdown + signal, but hang about for an additional 5s to process any remaining + traffic. + + Run regular_cleanups after processing the results of the select, + looking at a sufficient slice of each table to ensure that all + entries are examined at least once per second. +- Merge 2.0.21: + + Cluster changes from Michael, intended to prevent a stray master + from trashing a cluster: + = Ignore heartbeats from peers claiming to be the master before the + timeout on the old master has expired. + = A master receiving a stray heartbeat sends a unicast HB back, which + should cause the rogue to die due to the tie-breaker code. + = Keep probing the master for late heartbeats. + = Drop BGP as soon as we become master with the minumum required peers. + = Any PING seen from a master forces an election (rather than just + where basetime is zero). + = A slave which receives a LASTSEEN message (presumably a restarted + master) sends back new message type, C_MASTER which indicates the + address of the current master. + + New config option: cluster_master_min_adv which determines the minimum + number of up to date slaves required before the master will drop + routes. +- Merge 2.0.20: + + Add handling of "throttle=N" RADIUS attributes. + + Fix RADIUS indexing (should have 16K entries with 64 sockets). +- Merge 2.0.19: + + Fix leak in session freelist when initial RADIUS session allocation + fails. +- Merge 2.0.18: + + Add a Cisco-Avpair with intercept details to RADIUS Start/Stop + records. +- Merge 2.0.17: + + Only send RADIUS stop record in sessionshutdown when there's an ip address. + + Reset .die on master takeover (so that dying sessions don't have to + hang around until the new master has the same uptime as the old one). + + Update .last_packet in cluster_handle_bytes only when there have + been bytes received from the modem (dead sessions were having the + idle timeout reset by stray packets). +- Merge 2.0.16: + + Ensure that sessionkill is not called on an unopened session (borks + the freelist). + + Bump MAXSESSION to 60K. + + Fix off-by-one errors in session/tunnel initialisation and + sessiont <-> sessionidt functions. + + Use session[s].opened consistently when checking for in-use sessions + (rather than session[s].tunnel). + + Use <= cluster_highest_sessionid rather than < MAXSESSION in a + couple of loops. + + Don't kill a whole tunnel if we're out of sessions. + + Change session[s].ip to 0 if set from RADIUS to 255.255.255.254; + avoids the possibility that it will be interpreted as a valid IP + address. + + Avoid a possible buffer overflow in processpap. + + Kill session if authentication was rejected. +- Merge 2.0.15: + + More DoS prevention: add packet_limit option to apply a hard limit + to downstream packets per session. + + Fix "clear counters". + + Log "Accepted connection to CLI" at 4 when connection is from localhost + to reduce noise in logs. + + Show time since last counter reset in "show counters". +- Merge 2.0.14: + + Throttle outgoing LASTSEEN packets to at most one per second for a + given seq#. + +* Fri Dec 17 2004 Brendan O'Dea 2.0.13 +- Better cluster master collision resolution: keep a counter of state + changes, propagated in the heartbeats; the master with the highest # + of changes (that has kept in contact with the LAC through the + outage) prevails. +- Skip newlines in ringbuffer messages to CLI. +- Drop "Session N is closing" message level to 4; don't process PPPIP + packets in this state. +- Use gzip --best for man pages, include pid_file in sample + startup-config (from Jonathan's Debian package patches). +- Read multiple packets off cluster_sockfd as well as udpfd, tunfd in an + attempt to avoid losing the cluster in high load (DoS) conditions. +- Add counters for select_called, multi_read_used and multi_read_exceeded. +- Compress logs. +- Retain counters of shutdown sessions to dump once per minute. +- Use standard uintN_t types for portability. + +* Wed Dec 1 2004 Brendan O'Dea 2.0.12 +- The "This time, for sure!" release. +- Fix throttlectl plugin argument parsing. + +* Wed Dec 1 2004 Brendan O'Dea 2.0.11 +- Don't send a RADIUS start record when ungardening on shutdown. + +* Wed Dec 1 2004 Brendan O'Dea 2.0.10 +- Fix byte ordering of LCP header length (thanks Yuri). +- Increase ip_conntrack_max due to dropped packets. + +* Tue Nov 30 2004 Brendan O'Dea 2.0.9 +- Revise CCP, send ConfigReq once only. +- Don't copy the old buffer into Config{Nak,Rej} LCP responses (oops); + add length checks when appending. +- Do copy the identifier from the request and update length. +- Have makeppp print a backtrace on overflow. +- Check control serial before clearing window, prevents looping tunnel + setup in some instances. +- Implement named access-lists which may be applied to a session + either via Filter-Id RADIUS responses, or using the CLI. +- Drop ip address from LOG. +- autothrottle: revise parsing; ignore lcp:interface-config avpairs + which don't start with serv[ice-policy]. +- Add THANKS file. + +* Sat Nov 20 2004 Brendan O'Dea 2.0.8 +- Ignore gateway address in Framed-Route (from Jonathan McDowell). +- Don't route Framed-IP-Address if contained in a Framed-Route. +- Call sessionshutdown() when a tunnel is dropped rather than + sessionkill() to ensure that RADIUS stop records are sent. +- Cleanup: make a bunch of global functions/variables static. +- Remove reference to old -a command line argument. +- Add l2tpns(8) and nsctl(8) manpages from Jonathan McDowell. +- Add startup-config(5) manpage. +- Revise nsctl to allow arbitrary strings/args to be passed to plugins. +- Add snoopctl, throttlectl plugins. +- Fix deletion from linked list. +- Allow LCP re-negotiation after connection completes (thanks Yuri). + +* Mon Nov 15 2004 Brendan O'Dea 2.0.7 +- Fix socket creation in host_unreachable() (thanks to Bjørn Augestad) +- Don't assume BGP peer sends back negotiated hold time, pick smallest + +* Thu Nov 11 2004 Brendan O'Dea 2.0.6 +- Make BGP keepalive/hold time configurable +- Revise BGP config to use "router bgp AS" syntax (requires libcli >= 1.8.2) + +* Tue Nov 9 2004 Brendan O'Dea 2.0.5 +- Handle routing properly in lone-master case +- Fix intercepts: don't double-snoop throttled customers, ensure + byte/packet counts are only updated once +- Add a callback to allow plugins to fetch values from the running config + +* Mon Nov 8 2004 Brendan O'Dea 2.0.4 +- Added setrxspeed plugin +- Added peer_address config option +- Rename wrapper macros to LOG()/LOG_HEX(), use p->log() in plugins +- Replace some PPP{PAP,CHAP} magic numebrs with constants +- Nak asyncmap (unless == 0) +- Bundle ConfigRej options +- Clean up initlcp handling + +* Wed Nov 3 2004 Brendan O'Dea 2.0.3 +- Added support for hidden AVPs by Robert Clark +- l2tpns-chap-response.patch from Robert Clark +- Merge l2tpns-config-hostname.patch from Robert Clark +- l2tpns-dont-timeshift-unidirectional-traffic.patch from Robert Clark - Dump accounting data if cin OR cout is non-zero +- Don't write accounting files if no accounting dir is set - Yuri +- Fix checking for mmap success +- Renegotiate MRU - Yuri +- Take LCP ConfigReq length from the packet length field - Yuri +- Hostname set via command line not config +- Make number of throttle buckets configurable +- Shared_malloc returns NULL on failure +- Sync changes +- Unfsck 4->8 indenting change +- Use 2 seperate u16 values for throttle rate in/out +- Defer adding radius fds to the select loop until become_master + +* Thu Sep 02 2004 David Parrish 2.0.2 +- Combined LCP patches from Iain and Yuri. This should allow Windows 2k/XP + clients to connect, as well Linksys DSL modems. +- Apply patch to fix -v option from Juergen Kammer. +- Makefile fix from Juergen Kammer to not overwrite existing config files on + make install +- Configurable radius port patch from Juergen Kammer. +- Send my_address if no bind_address when doing IPCP +- Write pid file if filename is set +- Add startup script and monitor script from Yuri +- Some logging correctness fixes from Iain Wade +- Add support for LCP Ident and CallBack (rejection only) from Yuri +- Initiate LCP if not attempted by the client, or in renegotiation - Yuri +- Indentation and style cleanups +- Per-user upload and download throttle rates - Yuri +- Make autothrottle.so understand cisco lcp:interface-config - Yuri +- Show filter stats in show session - Yuri +- Cleanup from Michael to change sid to unique_id +- Add plugin to remove domain name from auth requests +- Add .spec file for RPM generation + +* Tue Jul 13 2004 Brendan O'Dea 2.0.1 +- Update INSTALL, Docs/manual.html documentation. +- Add INTERNALS documentation. +- Add lock_pages option. +- TerminateAck fix from Yuri +- Adject cli_loop args for libcli 1.8.0 +- Allow for backward compatabity in C_PING packets +- Don't send RADIUS stop messages from sessionshutdown when called from + sessionkill. +- s/tap/tun/ . +- Fix for LASTSEEN breakage: don't do anything in the CLI other than + flag changes to be made by the parent. +- Split out master parts from cluster_check_master() into cluster_check_slaves(). +- Set hostname in CLI prompt. +- Make cluster_hb_interval work; include interval/timeout in heartbeats + so that a change on the master is propagated immediately to the slaves. +- Use fast heartbeats when there are slaves not up to date. +- Ensure basetime of shut down master is set to zero (prevent delayed election). +- Fix radius session leak on IPCP timeout. +- Fix some off-by-one errors in tunnel/session loops. +- Add "limp along" fix for when a slave drops temporarily from the mcast group. +- Rename l2tpns.cfg as startup-config to match CONFIGFILE. +- Update cli callbacks to work with libcli 1.6. + This supports privileged and unprivileged commands, as well as a configuration + mode. +- Add help for all cli commands. +- Add "show version" command. +- Fix uptime counter display. +- Fix nasty bug where cluster basetime can be set to 0 when sending initial + heartbeat. +- Don't rmmod ip_conntrack, as this can take a lot of time. +- Re-order logging in routeset such that the action is given before any error. +- Use the correct gateway address when deleting routes. +- Remove any routes when address changes. +- Require authentication if telnet from remote ip. +- Require enable password always. +- Return error if show pool done on slave. +- We MUST immediately exit if we're the wrong master! + +* Wed Jun 23 2004 David Parrish 2.0.0 +- Major release +- Completely replace active/standby clustering with a new peer-to-peer + clustering method which allows much greater throughput and is a lot more fault + tolerant +- Add internal tbf implementation for throttling without relying on tc and + kernel HTB +- Add support for iBGP and eBGP to advertise routes +- Add cli commands "show cluster", "show bgp", "show ipcache", "show throttle", + "show tbf", "suspend bgp", "restart bgp", "show user" +- Interception destination must be set per-user +- If SMP machine, allow use of SCHED_FIFO, which should improve performance +- Added config option to send GARP at startup +- Added plugin_become_master and plugin_new_session_master plugin hooks +- Remove useless sessionsendarp(). This isn't needed now that we are using TUN + instead of TAP. +- ICMP rate limiting so not every unreachable packet is replied with an ICMP + unreachable message +- mangle table is not required on anything but the cluster master, so slaves + will drop the mangle table and attempt to unload the ip_conntrack module +- Statically assigned IP addresses (by Radius) work now +- Add -d command-line flag to detach and become a daemon +- Configuration file is now "/etc/l2tpns/startup-config" +- Reduced MIN_IP_SIZE to 0x19 to stop a pile of Short IP warnings +- Resend initial IPCP request until it's acknowleged by the client +- Better radius session cleanup logic +- Many miscellaenous bugfixes and performance enhancements +- Thanks to Michael O'Reilly and Brendan O'Dea for most of these new features + +* Mon May 24 2004 David Parrish 1.2.0 +- Fix SEGFAULT in garden module +- Use multiple radius sockets to allow more concurrent authentication requests +- Add username parameter to "show users" command +- Fix counting tunnel rx errors as tunnel tx errors +- Add "show throttle" command +- Add gcc __attribute__ to logging functions +- Fix warnings shown by __attribute__ +- Make sure regular cleanup happens regularly under high load +- Add variable cleanup_interval for changing cleanup interval +- Add support for reading more than one packet per fd in each processing loop +- This is configurable with the multi_read_count variable +- Remove segv handler so core dumps can happen +- Use nonblocking sockets +- Increase tun queue length +- Fix minimum length of IP packets +- Remove per-packet plugin hooks (they are slow) +- Don't drop session if no free RADIUS +- Don't expire more than 1000 sessions per cleanup interval +- Remove -a and -c command-line options. They don't work anyway +- Don't require file: in log_filename +- Bump version to 1.2.0 +- Check return code when throttling users + +* Mon Apr 5 2004 David Parrish 1.1.1 +- Don't mention configure anymore, it's not used +- Added the autosnoop and autothrottle modules +- Don't default to using a htb for the class root + +* Fri Mar 5 2004 David Parrish 1.1.0 +- Change all strcpy() calls to strncpy() to avoid buffer overflow potential +- Add ICMP host unreachable support +- Logging to syslog if log_file = "syslog:facility" +- Now requires libcli 1.5 +- All configuration moves to a config structure +- Ability to modify and write config on the fly through command-line interface +- Config file support is removed, and now handled by the cli +- Show hostname in cli prompt +- Keep current state type for tunnels +- Add uptime command do CLI, which also shows real-time bandwidth utilisation +- Add goodbye command to cluster master, which forces droppping a slave +- Cache IP address allocation, so that reconnecting users get the same address +- Fix tunnel resend timeouts, so that dead tunnels will be cleaned up +- Allocate tunnels and radius without using a linked list which had issues +- Fix some off-by-one errors in tunnel and session and radius arrays +- Save and reload ip address pool when dieing +- Check version and size of reloaded data when restarting +- Remove plugin_config support +- Remove old support for TBF which didn't work anyway. HTB is required to do throttling now. +- Add COPYING and Changes files diff --git a/Docs/l2tpns.8 b/Docs/l2tpns.8 new file mode 100644 index 0000000..a5c14b6 --- /dev/null +++ b/Docs/l2tpns.8 @@ -0,0 +1,70 @@ +.\" -*- nroff -*- +.de Id +.ds Dt \\$4 \\$5 +.. +.Id $Id: l2tpns.8,v 1.4 2005/06/12 06:09:35 bodea Exp $ +.TH L2TPNS 8 "\*(Dt" L2TPNS "System Management Commands" +.SH NAME +l2tpns \- Layer 2 tunneling protocol network server (LNS) +.SH SYNOPSIS +.B l2tpns +.RB [ \-d ] +.RB [ \-v ] +.RB [ \-c +.IR file ] +.RB [ \-h +.IR hostname ] +.SH DESCRIPTION +.B l2tpns +is a daemon for terminating layer 2 tunneling protocol (L2TP: RFC +2661) sessions. +.PP +Once running, +.B l2tpns +may be controlled by telnetting to port 23 on the machine running the +daemon and with the +.B nsctl +utility. +.SH OPTIONS +.TP +.B \-d +Detach from terminal and fork into the background. By default l2tpns +will stay in the foreground. +.TP +.B \-v +Increase verbosity for debugging. Can be used multiple times. +.TP +.BI "\-c " file +Specify configuration file. +.TP +.BI "\-h " hostname +Force hostname to +.IR hostname . +.SH FILES +.TP +.I /etc/l2tpns/startup-config +The default configuration file. +.TP +.I /etc/l2tpns/ip_pool +IP address pool configuration. +.TP +.I /etc/l2tpns/users +Username/password configuration for access to admin interface. +.SH SIGNALS +.TP +.B SIGHUP +Reload the config from disk and re-open log file. +.TP +.BR SIGTERM ", " SIGINT +Stop process. Tunnels and sessions are not terminated. This signal +should be used to stop l2tpns on a cluster node where there are other +machines to continue handling traffic. +.TP +.B SIGQUIT +Shut down tunnels and sessions, exit process when complete. +.SH SEE ALSO +.BR startup-config (5), +.BR nsctl (8) +.SH AUTHOR +This manual page was written by Jonathan McDowell , +for the Debian GNU/Linux system (but may be used by others). diff --git a/Docs/manual.html b/Docs/manual.html new file mode 100644 index 0000000..5c830f2 --- /dev/null +++ b/Docs/manual.html @@ -0,0 +1,1074 @@ + + + + +L2TPNS Manual + + + +

L2TPNS Manual

+
    +
  1. Overview
  2. +
  3. Installation +
      +
    1. Requirements
    2. +
    3. Compile
    4. +
    5. Install
    6. +
    7. Running
    8. +
    +
  4. +
  5. Configuration +
      +
    1. startup-config
    2. +
    3. users
    4. +
    5. ip_pool
    6. +
    7. build-garden
    8. +
    +
  6. +
  7. Controlling the Process +
      +
    1. Command-Line Interface
    2. +
    3. nsctl
    4. +
    5. Signals
    6. +
    +
  8. +
  9. Throttling
  10. +
  11. Interception
  12. +
  13. Authentication
  14. +
  15. Plugins
  16. +
  17. Walled Garden
  18. +
  19. Filtering
  20. +
  21. Clustering
  22. +
  23. Routing
  24. +
  25. Performance
  26. +
+ +

Overview

+l2tpns is half of a complete L2TP implementation. It supports only the +LNS side of the connection.

+ +L2TP (Layer 2 Tunneling Protocol) is designed to allow any layer 2 +protocol (e.g. Ethernet, PPP) to be tunneled over an IP connection. l2tpns +implements PPP over L2TP only.

+ +There are a couple of other L2TP implementations, of which l2tpd is probably the +most popular. l2tpd also will handle being either end of a tunnel, and +is a lot more configurable than l2tpns. However, due to the way it works, +it is nowhere near as scalable.

+ +l2tpns uses the TUN/TAP interface provided by the Linux kernel to receive +and send packets. Using some packet manipulation it doesn't require a +single interface per connection, as l2tpd does.

+ +This allows it to scale extremely well to very high loads and very high +numbers of connections.

+ +It also has a plugin architecture which allows custom code to be run +during processing. An example of this is in the walled garden module +included.

+ +
+Documentation is not my best skill. If you find any problems +with this document, or if you wish to contribute, please email the mailing list.

+ +

Installation

+

Requirements

+ +
    +
  1. Linux kernel version 2.4 or above, with the Tun/Tap interface either +compiled in, or as a module.
  2. + +
  3. libcli 1.8.0 or greater.
    You can get this from http://sourceforge.net/projects/libcli
  4. +
+ +

Compile

+ +You can generally get away with just running make from the source +directory. This will compile the daemon, associated tools and any modules +shipped with the distribution.

+ +

Install

+ +After you have successfully compiled everything, run make +install to install it. By default, the binaries are installed into +/usr/sbin, the configuration into /etc/l2tpns, and the +modules into /usr/lib/l2tpns.

+ +You will definately need to edit the configuration files before you +start. See the Configuration section for +more information.

+ +

Running

+ +You only need to run /usr/sbin/l2tpns as root to start it. It does +not detach to a daemon process, so you should perhaps run it from init.

+ +By default there is no log destination set, so all log messages will go to +stdout.

+ +

Configuration

+ +All configuration of the software is done from the files installed into +/etc/l2tpns. + +

startup-config

+ +This is the main configuration file for l2tpns. The format of the file is a +list of commands that can be run through the command-line interface. This +file can also be written directly by the l2tpns process if a user runs the +write memory command, so any comments will be lost. However if your +policy is not to write the config by the program, then feel free to comment +the file with a # or ! at the beginning of the line.

+ +A list of the possible configuration directives follows. Each of these +should be set by a line like:

+

+set configstring "value"
+set ipaddress 192.168.1.1
+set boolean true
+
+ +

+

    +
  • debug (int)
    +Sets the level of messages that will be written to the log file. The value +should be between 0 and 5, with 0 being no debugging, and 5 being the +highest. A rough description of the levels is: +
      +
    1. Critical Errors - Things are probably broken
    2. +
    3. Errors - Things might have gone wrong, but probably will recover
    4. +
    5. Warnings - Just in case you care what is not quite perfect
    6. +
    7. Information - Parameters of control packets
    8. +
    9. Calls - For tracing the execution of the code
    10. +
    11. Packets - Everything, including a hex dump of all packets processed... probably twice
    12. +

    +Note that the higher you set the debugging level, the slower the program +will run. Also, at level 5 a LOT of information will be logged. This should +only ever be used for working out why it doesn't work at all. +

  • + +
  • log_file (string)
    +This will be where all logging and debugging information is written +to. This may be either a filename, such as /var/log/l2tpns, or +the special magic string syslog:facility, where facility +is any one of the syslog logging facilities, such as local5. +
  • + +
  • pid_file (string)
    +If set, the process id will be written to the specified file. The +value must be an absolute path. +
  • + +
  • l2tp_secret (string)
    +The secret used by l2tpns for authenticating tunnel request. Must be +the same as the LAC, or authentication will fail. Only actually be +used if the LAC requests authentication. +
  • + +
  • l2tp_mtu (int)
    +MTU of interface for L2TP traffic (default: 1500). Used to set link +MRU and adjust TCP MSS. +
  • + +
  • ppp_restart_time (int)
    +ppp_max_configure (int)
    +ppp_max_failure (int)
    +PPP counter and timer values, as described in §4.1 of +RFC1661. +
  • + +
  • primary_dns (ip address) +
  • secondary_dns (ip address)
    +Whenever a PPP connection is established, DNS servers will be sent to the +user, both a primary and a secondary. If either is set to 0.0.0.0, then that +one will not be sent. +
  • + +
  • primary_radius (ip address) +
  • secondary_radius (ip address)
    +Sets the RADIUS servers used for both authentication and accounting. +If the primary server does not respond, then the secondary RADIUS +server will be tried.
    +Note: in addition to the source IP address and +identifier, the RADIUS server must include the source +port when detecting duplicates to supress (in order to cope with a +large number of sessions comming on-line simultaneously l2tpns uses a +set of udp sockets, each with a seperate identifier). +
  • + +
  • primary_radius_port (short) +
  • secondary_radius_port (short)
    +Sets the authentication ports for the primary and secondary RADIUS +servers. The accounting port is one more than the authentication +port. If no RADIUS ports are given, the authentication port defaults +to 1645, and the accounting port to 1646. +
  • + +
  • radius_accounting (boolean)
    +If set to true, then RADIUS accounting packets will be sent. This +means that a Start record will be sent when the session is +successfully authenticated, and a Stop record will be sent when the +session is closed. +
  • + +
  • radius_secret (string)
    +This secret will be used in all RADIUS queries. If this is not set then +RADIUS queries will fail. +
  • + +
  • radius_authtypes (string)
    +A comma separated list of supported RADIUS authentication methods +(pap or chap), in order of preference (default pap). +
  • + +
  • radius_dae_port (short)
    +Port for DAE RADIUS (Packet of Death/Disconnect, Change of Authorization) +requests (default: 3799). +
  • + +
  • allow_duplicate_users (boolean)
    +Allow multiple logins with the same username. If false (the default), +any prior session with the same username will be dropped when a new +session is established. +
  • + +
  • bind_address (ip address)
    +When the tun interface is created, it is assigned the address +specified here. If no address is given, 1.1.1.1 is used. Packets +containing user traffic should be routed via this address if given, +otherwise the primary address of the machine. +
  • + +
  • peer_address (ip address)
    +Address to send to clients as the default gateway. + + +
  • send_garp (boolean)
    +Determines whether or not to send a gratuitous ARP for the +bind_address when the server is ready to handle traffic (default: +true).
    +This value is ignored if BGP is configured. +
  • + +
  • throttle_speed (int)
    +Sets the default speed (in kbits/s) which sessions will be limited to. +If this is set to 0, then throttling will not be used at all. Note: +You can set this by the CLI, but changes will not affect currently +connected users. +
  • + +
  • throttle_buckets (int)
    +Number of token buckets to allocate for throttling. Each throttled +session requires two buckets (in and out). +
  • + +
  • accounting_dir (string)
    +If set to a directory, then every 5 minutes the current usage for +every connected use will be dumped to a file in this directory. Each +file dumped begins with a header, where each line is prefixed by #. +Following the header is a single line for every connected user, fields +separated by a space.
    The fields are username, ip, qos, +uptxoctets, downrxoctets. The qos field is 1 if a standard user, and +2 if the user is throttled. +
  • + +
  • setuid (int)
    +After starting up and binding the interface, change UID to this. This +doesn't work properly. +
  • + +
  • dump_speed (boolean)
    +If set to true, then the current bandwidth utilization will be logged every +second. Even if this is disabled, you can see this information by running +the uptime command on the CLI. +
  • + +
  • multi_read_count (int)
    +Number of packets to read off each of the UDP and TUN fds when +returned as readable by select (default: 10). Avoids incurring the +unnecessary system call overhead of select on busy servers. +
  • + +
  • scheduler_fifo (boolean)
    +Sets the scheduling policy for the l2tpns process to SCHED_FIFO. This +causes the kernel to immediately preempt any currently running SCHED_OTHER +(normal) process in favour of l2tpns when it becomes runnable. +Ignored on uniprocessor systems. +
  • + +
  • lock_pages (boolean)
    +Keep all pages mapped by the l2tpns process in memory. +
  • + +
  • icmp_rate (int)
    +Maximum number of host unreachable ICMP packets to send per second. +
  • + +
  • packet_limit (int>
    +Maximum number of packets of downstream traffic to be handled each +tenth of a second per session. If zero, no limit is applied (default: +0). Intended as a DoS prevention mechanism and not a general +throttling control (packets are dropped, not queued). +
  • + +
  • cluster_address (ip address)
    +Multicast cluster address (default: 239.192.13.13). See the section +on Clustering for more information. +
  • + +
  • cluster_interface (string)
    +Interface for cluster packets (default: eth0). +
  • + +
  • cluster_mcast_ttl (int)
    +TTL for multicast packets (default: 1). +
  • + +
  • cluster_hb_interval (int)
    +Interval in tenths of a second between cluster heartbeat/pings. +
  • + +
  • cluster_hb_timeout (int)
    +Cluster heartbeat timeout in tenths of a second. A new master will be +elected when this interval has been passed without seeing a heartbeat +from the master. +
  • + +
  • cluster_master_min_adv (int)
    +Determines the minumum number of up to date slaves required before the +master will drop routes (default: 1). +
  • +
+ +

BGP routing configuration is entered by the command: +The routing configuration section is entered by the command +

router bgp as
+where as specifies the local AS number. + +

Subsequent lines prefixed with +

neighbour peer
+define the attributes of BGP neighhbours. Valid commands are: +
+
neighbour peer remote-as as +
neighbout peer timers keepalive hold +
+ +Where peer specifies the BGP neighbour as either a hostname or +IP address, as is the remote AS number and keepalive, +hold are the timer values in seconds. + +

Named access-lists are configured using one of the commands: +

+
ip access-list standard name +
ip access-list extended name +
+ +

Subsequent lines prefixed with permit or deny +define the body of the access-list. Standard access-list syntax: +

+
{permit|deny} + {host|source source-wildcard|any} + [{host|destination destination-wildcard|any}] +
+ +Extended access-lists: + +
+

{permit|deny} ip + {host|source source-wildcard|any} + {host|destination destination-wildcard|any} [fragments] +

{permit|deny} udp + {host|source source-wildcard|any} + [{eq|neq|gt|lt} port|range from to] + {host|destination destination-wildcard|any} + [{eq|neq|gt|lt} port|range from to] + [fragments] +

{permit|deny} tcp + {host|source source-wildcard|any} + [{eq|neq|gt|lt} port|range from to] + {host|destination destination-wildcard|any} + [{eq|neq|gt|lt} port|range from to] + [{established|{match-any|match-all} + {+|-}{fin|syn|rst|psh|ack|urg} + ...|fragments] +

+ +

users

+ +Usernames and passwords for the command-line interface are stored in +this file. The format is username:password where +password may either by plain text, an MD5 digest (prefixed by +$1salt$) or a DES password, distinguished from +plain text by the prefix {crypt}.

+ +The username enable has a special meaning and is used to set +the enable password.

+ +Note: If this file doesn't exist, then anyone who can get to +port 23 will be allowed access without a username / password.

+ +

ip_pool

+ +This file is used to configure the IP address pool which user +addresses are assigned from. This file should contain either an IP +address or a CIDR network per line. e.g.:

+ +

+    192.168.1.1
+    192.168.1.2
+    192.168.1.3
+    192.168.4.0/24
+    172.16.0.0/16
+    10.0.0.0/8
+
+ +Keep in mind that l2tpns can only handle 65535 connections per +process, so don't put more than 65535 IP addresses in the +configuration file. They will be wasted. + +

build-garden

+ +The garden plugin on startup creates a NAT table called "garden" then +sources the build-garden script to populate that table. All +packets from gardened users will be sent through this table. Example: + +
+    iptables -t nat -A garden -p tcp -m tcp --dport 25 -j DNAT --to 192.168.1.1
+    iptables -t nat -A garden -p udp -m udp --dport 53 -j DNAT --to 192.168.1.1
+    iptables -t nat -A garden -p tcp -m tcp --dport 53 -j DNAT --to 192.168.1.1
+    iptables -t nat -A garden -p tcp -m tcp --dport 80 -j DNAT --to 192.168.1.1
+    iptables -t nat -A garden -p tcp -m tcp --dport 110 -j DNAT --to 192.168.1.1
+    iptables -t nat -A garden -p tcp -m tcp --dport 443 -j DNAT --to 192.168.1.1
+    iptables -t nat -A garden -p icmp -m icmp --icmp-type echo-request -j DNAT --to 192.168.1.1
+    iptables -t nat -A garden -p icmp -j ACCEPT
+    iptables -t nat -A garden -j DROP
+
+ +

Controlling the Process

+ +A running l2tpns process can be controlled in a number of ways. The primary +method of control is by the Command-Line Interface (CLI).

+ +You can also remotely send commands to modules via the nsctl client +provided.

+ +Also, there are a number of signals that l2tpns understands and takes action +when it receives them. + +

Command-Line Interface

+ +You can access the command line interface by telnet'ing to port 23. +There is no IP address restriction, so it's a good idea to firewall +this port off from anyone who doesn't need access to it. See +users for information on restricting access based +on a username and password.

+ +The CLI gives you real-time control over almost everything in +the process. The interface is designed to look like a Cisco +device, and supports things like command history, line editing and +context sensitive help. This is provided by linking with the +libcli +library. Some general documentation of the interface is + +here.

+ +After you have connected to the telnet port (and perhaps logged in), you +will be presented with a hostname> prompt.

+ +Enter help to get a list of possible commands. A brief +overview of the more important commands follows: + +

    +
  • show session
    +Without specifying a session ID, this will list all tunnels currently +connected. If you specify a session ID, you will be given all +information on a single tunnel. Note that the full session list can +be around 185 columns wide, so you should probably use a wide terminal +to see the list properly.

    +The columns listed in the overview are: + + + + + + + + + + + + + + +
    SIDSession ID
    TIDTunnel ID - Use with show tunnel tid
    UsernameThe username given in the PPP + authentication. If this is *, then LCP authentication has not + completed.
    IPThe IP address given to the session. If + this is 0.0.0.0, LCP negotiation has not completed.
    IIntercept - Y or N depending on whether the + session is being snooped. See snoop.
    TThrottled - Y or N if the session is + currently throttled. See throttle.
    GWalled Garden - Y or N if the user is + trapped in the walled garden. This field is present even if the + garden module is not loaded.
    openedThe number of seconds since the + session started
    downloadedNumber of bytes downloaded by the user
    uploadedNumber of bytes uploaded by the user
    idleThe number of seconds since traffic was + detected on the session
    LACThe IP address of the LAC the session is + connected to.
    CLIThe Calling-Line-Identification field + provided during the session setup. This field is generated by the + LAC.
    +

    +

  • + +
  • show users
    +With no arguments, display a list of currently connected users. If an +argument is given, the session details for the given username are +displayed. +
  • + +
  • show tunnel
    +This will show all the open tunnels in a summary, or detail on a single +tunnel if you give a tunnel id.

    +The columns listed in the overview are: + + + + + + +
    TIDTunnel ID
    HostnameThe hostname for the tunnel as + provided by the LAC. This has no relation to DNS, it is just + a text field.
    IPThe IP address of the LAC
    StateTunnel state - Free, Open, Dieing, + Opening
    SessionsThe number of open sessions on the + tunnel
    +

    +

  • + +
  • show pool
    +Displays the current IP address pool allocation. This will only display +addresses that are in use, or are reserved for re-allocation to a +disconnected user.

    +If an address is not currently in use, but has been used, then in the User +column the username will be shown in square brackets, followed by the time +since the address was used: +

    +IP Address      Used  Session User
    +192.168.100.6     N           [joe.user] 1548s
    +
    +

    +

  • + +
  • show radius
    +Show a summary of the in-use RADIUS sessions. This list should not be very +long, as RADIUS sessions should be cleaned up as soon as they are used. The +columns listed are: + + + + + + +
    RadiusThe ID of the RADIUS request. This is + sent in the packet to the RADIUS server for identification.
    StateThe state of the request - WAIT, CHAP, + AUTH, IPCP, START, STOP, NULL.
    SessionThe session ID that this RADIUS + request is associated with
    RetryIf a response does not appear to the + request, it will retry at this time. This is a unix timestamp.
    TryRetry count. The RADIUS request is + discarded after 3 retries.
    +

    +

  • + +
  • show running-config
    +This will list the current running configuration. This is in a format that +can either be pasted into the configuration file, or run directly at the +command line. +

    +

  • + +
  • show counters
    +Internally, counters are kept of key values, such as bytes and packets +transferred, as well as function call counters. This function displays all +these counters, and is probably only useful for debugging.

    +You can reset these counters by running clear counters. +

    +

  • + +
  • show cluster
    +Show cluster status. Shows the cluster state for this server +(Master/Slave), information about known peers and (for slaves) the +master IP address, last packet seen and up-to-date status.

    +See Clustering for more information. +

    +

  • + +
  • write memory
    +This will write the current running configuration to the config file +startup-config, which will be run on a restart. +

    +

  • + +
  • snoop
    +You must specify a username, IP address and port. All packets for the +current session for that username will be forwarded to the given +host/port. Specify no snoop username to disable interception +for the session.

    + +If you want interception to be permanent, you will have to modify the RADIUS +response for the user. See Interception. +

    +

  • + +
  • throttle
    +You must specify a username, which will be throttled for the current +session. Specify no throttle username to disable throttling +for the current session.

    + +If you want throttling to be permanent, you will have to modify the +RADIUS response for the user. See Throttling. +

    +

  • + +
  • drop session
    +This will cleanly disconnect a session. You must specify a session id, which +you can get from show session. This will send a disconnect message +to the remote end. +

    +

  • + +
  • drop tunnel
    +This will cleanly disconnect a tunnel, as well as all sessions on that +tunnel. It will send a disconnect message for each session individually, and +after 10 seconds it will send a tunnel disconnect message. +

    +

  • + +
  • uptime
    +This will show how long the l2tpns process has been running, and the current +bandwidth utilization: +
    +17:10:35 up 8 days, 2212 users, load average: 0.21, 0.17, 0.16
    +Bandwidth: UDP-ETH:6/6  ETH-UDP:13/13  TOTAL:37.6   IN:3033 OUT:2569
    +
    +The bandwidth line contains 4 sets of values.
    +UDP-ETH is the current bandwidth going from the LAC to the ethernet +(user uploads), in mbits/sec.
    +ETH-UDP is the current bandwidth going from ethernet to the LAC (user +downloads).
    +TOTAL is the total aggregate bandwidth in mbits/s.
    +IN and OUT are packets/per-second going between UDP-ETH and ETH-UDP. +

    +These counters are updated every second. +

    +

  • + +
  • configure terminal
    +Enter configuration mode. Use exit or ^Z to exit this mode. +The following commands are valid in this mode:

    +

  • + +
  • load plugin
    +Load a plugin. You must specify the plugin name, and it will search in +/usr/lib/l2tpns for plugin.so. You can unload a loaded plugin with +remove plugin. +

    +

  • + +
  • set
    +Set a configuration variable. You must specify the variable name, and +the value. If the value contains any spaces, you should quote the +value with double (") or single (') quotes.

    + +You can set any startup-config value in +this way, although some may require a restart to take effect.

    +

  • +
+ +

nsctl

+ +nsctl allows messages to be passed to plugins.

+ +Arguments are command and optional args. See +nsctl(8) for more details.

+ +Built-in command are load_plugin, unload_plugin and +help. Any other commands are passed to plugins for processing. + +

Signals

+ +While the process is running, you can send it a few different signals, using +the kill command. +
+killall -HUP l2tpns
+
+ +The signals understood are: +
+
SIGHUP
Reload the config from disk and re-open log file.
+
SIGTERM, SIGINT
Stop process. Tunnels and sessions are not +terminated. This signal should be used to stop l2tpns on a +cluster node where there are other machines to +continue handling traffic.
+
SIGQUIT
Shut down tunnels and sessions, exit process when +complete.
+
+ +

Throttling

+ +l2tpns contains support for slowing down user sessions to whatever speed you +desire. You must first enable the global setting throttle_speed +before this will be activated.

+ +If you wish a session to be throttled permanently, you should set the +Vendor-Specific RADIUS value Cisco-Avpair="throttle=yes", which +will be handled by the autothrottle module.

+ +Otherwise, you can enable and disable throttling an active session using +the throttle CLI command.

+ +

Interception

+ +You may have to deal with legal requirements to be able to intercept a +user's traffic at any time. l2tpns allows you to begin and end interception +on the fly, as well as at authentication time.

+ +When a user is being intercepted, a copy of every packet they send and +receive will be sent wrapped in a UDP packet to the IP address and port set +in the snoop_host and snoop_port configuration +variables.

+ +The UDP packet contains just the raw IP frame, with no extra headers.

+ +To enable interception on a connected user, use the snoop username +and no snoop username CLI commands. These will enable interception +immediately.

+ +If you wish the user to be intercepted whenever they reconnect, you will +need to modify the RADIUS response to include the Vendor-Specific value +Cisco-Avpair="intercept=yes". For this feature to be enabled, +you need to have the autosnoop module loaded.

+ +

Authentication

+ +Whenever a session connects, it is not fully set up until authentication is +completed. The remote end must send a PPP CHAP or PPP PAP authentication +request to l2tpns.

+ +This request is sent to the RADIUS server, which will hopefully respond with +Auth-Accept or Auth-Reject.

+ +If Auth-Accept is received, the session is set up and an IP address is +assigned. The RADIUS server can include a Framed-IP-Address field in the +reply, and that address will be assigned to the client. It can also include +specific DNS servers, and a Framed-Route if that is required.

+ +If Auth-Reject is received, then the client is sent a PPP AUTHNAK packet, +at which point they should disconnect. The exception to this is when the +walled garden module is loaded, in which case the user still receives the +PPP AUTHACK, but their session is flagged as being a garden'd user, and they +should not receive any service.

+ +The RADIUS reply can also contain a Vendor-Specific attribute called +Cisco-Avpair. This field is a freeform text field that most Cisco +devices understand to contain configuration instructions for the session. In +the case of l2tpns it is expected to be of the form +

+key=value,key2=value2,key3=value3,keyn=value
+
+ +Each key-value pair is separated and passed to any modules loaded. The +autosnoop and autothrottle understand the keys +intercept and throttle respectively. For example, to have +a user who is to be throttled and intercepted, the Cisco-Avpair value should +contain: +
+intercept=yes,throttle=yes
+
+ +

Plugins

+ +So as to make l2tpns as flexible as possible (I know the core code is pretty +difficult to understand), it includes a plugin API, which you can use to +hook into certain events.

+ +There are a few example modules included - autosnoop, autothrottle and +garden.

+ +When an event happens that has a hook, l2tpns looks for a predefined +function name in every loaded module, and runs them in the order the modules +were loaded.

+ +The function should return PLUGIN_RET_OK if it is all OK. If it returns +PLUGIN_RET_STOP, then it is assumed to have worked, but that no further +modules should be run for this event.

+A return of PLUGIN_RET_ERROR means that this module failed, and +no further processing should be done for this event. Use this with care. + +Every event function called takes a specific structure named +param_event, which varies in content with each event. The +function name for each event will be plugin_event, +so for the event timer, the function declaration should look like: +

+int plugin_timer(struct param_timer *data);
+
+ +A list of the available events follows, with a list of all the fields in the +supplied structure: +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EventDescriptionParameters
pre_authThis is called after a RADIUS response has been + received, but before it has been processed by the + code. This will allow you to modify the response in + some way. + +
+
t
Tunnel +
s
Session +
username +
password +
protocol
0xC023 for PAP, 0xC223 for CHAP +
continue_auth
Set to 0 to stop processing authentication modules +
+
post_authThis is called after a RADIUS response has been + received, and the basic checks have been performed. This + is what the garden module uses to force authentication + to be accepted. + +
+
t
Tunnel +
s
Session +
username +
auth_allowed
This is already set to true or + false depending on whether authentication has been + allowed so far. You can set this to 1 or 0 to force + allow or disallow authentication +
protocol
0xC023 for PAP, 0xC223 for CHAP +
+
packet_rxThis is called whenever a session receives a + packet. Use this sparingly, as this will + seriously slow down the system. + +
+
t
Tunnel +
s
Session +
buf
The raw packet data +
len
The length of buf +
+
packet_txThis is called whenever a session sends a + packet. Use this sparingly, as this will + seriously slow down the system. + +
+
t
Tunnel +
s
Session +
buf
The raw packet data +
len
The length of buf +
+
timerThis is run every second, no matter what is happening. + This is called from a signal handler, so make sure anything + you do is reentrant. + +
+
time_now
The current unix timestamp +
+
new_sessionThis is called after a session is fully set up. The + session is now ready to handle traffic. + +
+
t
Tunnel +
s
Session +
+
kill_sessionThis is called when a session is about to be shut down. + This may be called multiple times for the same session. + +
+
t
Tunnel +
s
Session +
+
radius_responseThis is called whenever a RADIUS response includes a + Cisco-Avpair value. The value is split up into + key=value pairs, and each is processed through all + modules. + +
+
t
Tunnel +
s
Session +
key +
value +
+
radius_resetThis is called whenever a RADIUS CoA request is + received to reset any options to default values before + the new values are applied. + +
+
t
Tunnel +
s
Session +
+
controlThis is called in whenever a nsctl packet is received. + This should handle the packet and form a response if + required. + +
+
iam_master
Cluster master status +
argc
The number of arguments +
argv
Arguments +
response
Return value: NSCTL_RES_OK or NSCTL_RES_ERR +
additional
Extended response text +
+
+
+ +

Walled Garden

+ +Walled Garden is implemented so that you can provide perhaps limited service +to sessions that incorrectly authenticate.

+ +Whenever a session provides incorrect authentication, and the +RADIUS server responds with Auth-Reject, the walled garden module +(if loaded) will force authentication to succeed, but set the flag +garden in the session structure, and adds an iptables rule to +the garden_users chain to force all packets for the session's IP +address to traverse the garden chain.

+ +This doesn't just work. To set this all up, you will to +setup the garden nat table with the +build-garden script with rules to limit +user's traffic. For example, to force all traffic except DNS to be +forwarded to 192.168.1.1, add these entries to your +build-garden: +

+iptables -t nat -A garden -p tcp --dport ! 53 -j DNAT --to 192.168.1.1
+iptables -t nat -A garden -p udp --dport ! 53 -j DNAT --to 192.168.1.1
+
+ +l2tpns will add entries to the garden_users chain as appropriate.

+ +You can check the amount of traffic being captured using the following +command: +

+iptables -t nat -L garden -nvx
+
+ +

Filtering

+ +Sessions may be filtered by specifying Filter-Id attributes in +the RADIUS reply. filter.in specifies that the named +access-list filter should be applied to traffic from the +customer, filter.out specifies a list for traffic to the +customer. + +

Clustering

+ +An l2tpns cluster consists of of one* or more servers configured with +the same configuration, notably the multicast cluster_address.

+ +*A stand-alone server is simply a degraded cluster.

+ +Initially servers come up as cluster slaves, and periodically (every +cluster_hb_interval/10 seconds) send out ping packets +containing the start time of the process to the multicast +cluster_address.

+ +A cluster master sends heartbeat rather than ping packets, which +contain those session and tunnel changes since the last heartbeat.

+ +When a slave has not seen a heartbeat within +cluster_hb_timeout/10 seconds it "elects" a new master by +examining the list of peers it has seen pings from and determines +which of these and itself is the "best" candidate to be master. +"Best" in this context means the server with the highest uptime (the +highest IP address is used as a tie-breaker in the case of equal +uptimes).

+ +After discovering a master, and determining that it is up-to-date (has +seen an update for all in-use sessions and tunnels from heartbeat +packets) will raise a route (see Routing) for +the bind_address and for all addresses/networks in +ip_pool. Any packets recieved by the slave which would alter +the session state, as well as packets for throttled or gardened +sessions are forwarded to the master for handling. In addition, byte +counters for session traffic are periodically forwarded.

+ +A master, when determining that it has at least one up-to-date slave +will drop all routes (raising them again if all slaves disappear) and +subsequently handle only packets forwarded to it by the slaves.

+ +

Routing

+If you are running a single instance, you may simply statically route +the IP pools to the bind_address (l2tpns will send a gratuitous +arp).

+ +For a cluster, configure the members as BGP neighbours on your router +and configure multi-path load-balancing. Cisco uses "maximum-paths +ibgp" for IBGP. If this is not supported by your IOS revision, you +can use "maximum-paths" (which works for EBGP) and set +as_number to a private value such as 64512.

+ +

Performance

+ +Performance is great.

+ +I'd like to include some pretty graphs here that show a linear performance +increase, with no impact by number of connected sessions.

+ +That's really what it looks like.

+ +
+David Parrish
+l2tpns-users@lists.sourceforge.net + + diff --git a/Docs/nsctl.8 b/Docs/nsctl.8 new file mode 100644 index 0000000..b7613f6 --- /dev/null +++ b/Docs/nsctl.8 @@ -0,0 +1,69 @@ +.\" -*- nroff -*- +.de Id +.ds Dt \\$4 \\$5 +.. +.Id $Id: nsctl.8,v 1.2 2004/11/17 15:08:19 bodea Exp $ +.TH NSCTL 8 "\*(Dt" L2TPNS "System Management Commands" +.SH NAME +nsctl \- manage running l2tpns instance +.SH SYNOPSIS +.B nsctl +.RB [ \-d ] +.RB [ \-h +.IR host [: port ]] +.RB [ \-t +.IR timeout ] +.I command +.RI [ arg " ...]" +.SH DESCRIPTION +.B nsctl +sends commands to a running +.B l2tpns +process. It provides both for the loading or unloading of plugins and +also the management of sessions via functions provided by those plugins. +.SH OPTIONS +.TP +.B \-d +Enable debugging output. +.TP +.B \-h \fIhost\fR[:\fIport\fR] +The host running +.B l2tpns +that should receive the message. By default the message is sent to +UDP port 1702 on +.BR localhost . +.TP +.B \-t \fItimeout\fR +Timeout in seconds to wait for a response from the server. +.SH COMMANDS +The first argument specifies the command to send to +.B l2tpns . +The following commands are as defined: +.TP +.BI "load_plugin " plugin +Load the named +.IR plugin . +.TP +.BI "unload_plugin " plugin +Unload the named +.IR plugin . +.TP +.B help +Each loaded plugin is queried for what commands it supports and the +synopsis for each is output. +.PP +Any other value of +.I command +(and +.I args +if any) +are sent to +.B l2tpns +as-is, to be passed to each plugin which registers a +.B plugin_control +function in turn (in which it may be acted upon). +.SH SEE ALSO +.BR l2tpns (8) +.SH AUTHOR +This manual page was written by Jonathan McDowell , +for the Debian GNU/Linux system (but may be used by others). diff --git a/Docs/startup-config.5 b/Docs/startup-config.5 new file mode 100644 index 0000000..a74c87f --- /dev/null +++ b/Docs/startup-config.5 @@ -0,0 +1,363 @@ +.\" -*- nroff -*- +.de Id +.ds Dt \\$4 \\$5 +.. +.Id $Id: startup-config.5,v 1.15 2005/09/16 05:04:31 bodea Exp $ +.TH STARTUP-CONFIG 5 "\*(Dt" L2TPNS "File Formats and Conventions" +.SH NAME +startup\-config \- configuration file for l2tpns +.SH SYNOPSIS +/etc/l2tpns/startup-config +.SH DESCRIPTION +.B startup-config +is the configuration file for +.BR l2tpns . +.PP +The format is plain text, in the same format as accepted by the +configuration mode of +.BR l2tpns 's +telnet administrative interface. Comments are indicated by either the +character +.B # +or +.BR ! . +.SS SETTINGS +Settings are specified with +.IP +.BI "set " "variable value" +.PP +The following +.IR variable s +may be set: +.RS +.TP +.B debug +Set the level of debugging messages written to the log file. The +value should be between 0 and 5, with 0 being no debugging, and 5 +being the highest. +.TP +.B log_file +This will be where all logging and debugging information is written +to. This may be either a filename, such as +.BR /var/log/l2tpns , +or the string +.BR syslog : \fIfacility\fR , +where +.I facility +is any one of the syslog logging facilities, such as +.BR local5 . +.TP +.B pid_file +If set, the process id will be written to the specified file. The +value must be an absolute path. +.TP +.B random_device +Path to random data source (default +.BR /dev/urandom ). +Use "" to use the rand() library function. +.TP +.B l2tp_secret +The secret used by +.B l2tpns +for authenticating tunnel request. Must be the same as the LAC, or +authentication will fail. Only actually be used if the LAC requests +authentication. +.TP +.B l2tp_mtu +MTU of interface for L2TP traffic (default: 1500). Used to set link +MRU and adjust TCP MSS. +.TP +.B ppp_restart_time +Restart timer for PPP protocol negotiation in seconds (default: 3). +.TP +.B ppp_max_configure +Number of configure requests to send before giving up (default: 10). +.TP +.B ppp_max_failure +Number of Configure-Nak requests to send before sending a +Configure-Reject (default: 5). +.TP +.BR primary_dns , " secondary_dns" +Whenever a PPP connection is established, DNS servers will be sent to the +user, both a primary and a secondary. If either is set to 0.0.0.0, then that +one will not be sent. +.TP +.BR primary_radius , " secondary_radius" +Sets the RADIUS servers used for both authentication and accounting. +If the primary server does not respond, then the secondary RADIUS +server will be tried. +.TP +.BR primary_radius_port , " secondary_radius_port" +Sets the authentication ports for the primary and secondary RADIUS +servers. The accounting port is one more than the authentication +port. If no ports are given, authentication defaults to 1645, and +accounting to 1646. +.TP +.B radius_accounting +If set to true, then RADIUS accounting packets will be sent. A +.B Start +record will be sent when the session is successfully authenticated, +and a +.B Stop +record when the session is closed. +.TP +.B radius_interim +If +.B radius_accounting +is on, defines the interval between sending of RADIUS interim +accounting records (in seconds). +.TP +.B radius_secret +Secret to be used in RADIUS packets. +.TP +.B radius_authtypes +A comma separated list of supported RADIUS authentication methods +("pap" or "chap"), in order of preference (default "pap"). +.TP +.B radius_dae_port +Port for DAE RADIUS (Packet of Death/Disconnect, Change of Authorization) +requests (default: 3799). +.TP +.B allow_duplicate_users +Allow multiple logins with the same username. If false (the default), +any prior session with the same username will be dropped when a new +session is established. +.TP +.B bind_address +When the tun interface is created, it is assigned the address +specified here. If no address is given, 1.1.1.1 is used. Packets +containing user traffic should be routed via this address if given, +otherwise the primary address of the machine. +.TP +.B peer_address +Address to send to clients as the default gateway. +.TP +.B send_garp +Determines whether or not to send a gratuitous ARP for the +.B bind_address +when the server is ready to handle traffic (default: true). This +setting is ignored if BGP is configured. +.TP +.B throttle_speed +Sets the default speed (in kbits/s) which sessions will be limited to. +.TP +.B throttle_buckets +Number of token buckets to allocate for throttling. Each throttled +session requires two buckets (in and out). +.TP +.B accounting_dir +If set to a directory, then every 5 minutes the current usage for +every connected use will be dumped to a file in this directory. +.TP +.B setuid +After starting up and binding the interface, change UID to this. This +doesn't work properly. +.TP +.B dump_speed +If set to true, then the current bandwidth utilization will be logged +every second. Even if this is disabled, you can see this information +by running the +.B uptime +command on the CLI. +.TP +.B multi_read_count +Number of packets to read off each of the UDP and TUN fds when +returned as readable by select (default: 10). Avoids incurring the +unnecessary system call overhead of select on busy servers. +.TP +.B scheduler_fifo +Sets the scheduling policy for the +.B l2tpns +process to +.BR SCHED_FIFO . +This causes the kernel to immediately preempt any currently running +.B SCHED_OTHER +(normal) process in favour of +.B l2tpns +when it becomes runnable. +.br +Ignored on uniprocessor systems. +.TP +.B lock_pages +Keep all pages mapped by the +.B l2tpns +process in memory. +.TP +.B icmp_rate +Maximum number of host unreachable ICMP packets to send per second. +.TP +.B packet_limit +Maximum number of packets of downstream traffic to be handled each +tenth of a second per session. If zero, no limit is applied (default: +0). Intended as a DoS prevention mechanism and not a general +throttling control (packets are dropped, not queued). +.TP +.B cluster_address +Multicast cluster address (default: 239.192.13.13). +.TP +.B cluster_interface +Interface for cluster packets (default: eth0). +.TP +.B cluster_mcast_ttl +TTL for multicast packets (default: 1). +.TP +.B cluster_hb_interval +Interval in tenths of a second between cluster heartbeat/pings. +.TP +.B cluster_hb_timeout +Cluster heartbeat timeout in tenths of a second. A new master will be +elected when this interval has been passed without seeing a heartbeat +from the master. +.TP +.B cluster_master_min_adv +Determines the minumum number of up to date slaves required before the +master will drop routes (default: 1). +.TP +.B ipv6_prefix +Enable negotiation of IPv6. This forms the the first 64 bits of the +client allocated address. The remaining 64 come from the allocated +IPv4 address and 4 bytes of 0s. +.RE +.SS BGP ROUTING +The routing configuration section is entered by the command +.IP +.BI "router bgp " as +.PP +where +.I as +specifies the local AS number. +.PP +Subsequent lines prefixed with +.BI "neighbour " peer +define the attributes of BGP neighhbours. Valid commands are: +.IP +.BI "neighbour " peer " remote-as " as +.br +.BI "neighbour " peer " timers " "keepalive hold" +.PP +Where +.I peer +specifies the BGP neighbour as either a hostname or IP address, +.I as +is the remote AS number and +.IR keepalive , +.I hold +are the timer values in seconds. +.SS NAMED ACCESS LISTS +Named access lists may be defined with either of +.IP +.BI "ip access\-list standard " name +.br +.BI "ip access\-list extended " name +.PP +Subsequent lines starting with +.B permit +or +.B deny +define the body of the access\-list. +.PP +.B Standard Access Lists +.RS 4n +Standard access lists are defined with: +.IP +.RB { permit | deny } +.IR source " [" dest ] +.PP +Where +.I source +and +.I dest +specify IP matches using one of: +.IP +.I address +.I wildard +.br +.B host +.I address +.br +.B any +.PP +.I address +and +.I wildard +are in dotted-quad notation, bits in the +.I wildard +indicate which address bits in +.I address +are relevant to the match (0 = exact match; 1 = don't care). +.PP +The shorthand +.RB ' host +.IR address ' +is equivalent to +.RI ' address +.BR 0.0.0.0 '; +.RB ' any ' +to +.RB ' 0.0.0.0 +.BR 255.255.255.255 '. +.RE +.PP +.B Extended Access Lists +.RS 4n +Extended access lists are defined with: +.IP +.RB { permit | deny } +.I proto +.IR source " [" ports "] " dest " [" ports "] [" flags ] +.PP +Where +.I proto +is one of +.BR ip , +.B tcp +or +.BR udp , +and +.I source +and +.I dest +are as described above for standard lists. +.PP +For TCP and UDP matches, source and destination may be optionally +followed by a +.I ports +specification: +.IP +.RB { eq | neq | gt | lt } +.I port +.br +.B +range +.I from to +.PP +.I flags +may be one of: +.RS +.HP +.RB { match\-any | match\-all } +.RB { + | - }{ fin | syn | rst | psh | ack | urg } +\&... +.br +Match packets with any or all of the tcp flags set +.RB ( + ) +or clear +.RB ( - ). +.HP +.B established +.br +Match "established" TCP connections: packets with +.B RST +or +.B ACK +set, and +.B SYN +clear. +.HP +.B fragments +.br +Match IP fragments. May not be specified on rules with layer 4 +matches. +.RE +.SH SEE ALSO +.BR l2tpns (8) diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..0b225c9 --- /dev/null +++ b/INSTALL @@ -0,0 +1,74 @@ +Brief Installation guide for L2TPNS + +1. Requirements + + * libcli 1.8.5 or greater + You can get it from http://sourceforge.net/projects/libcli. + + * A kernel with iptables support. + + +2. Compile + + * make + + +3. Install + + * make install. This process: + - Installs the binaries into /usr/sbin (l2tpns and nsctl). + - Creates the config dir /etc/l2tpns installs default config files. + - Ensures that /dev/net/tun exists. + + * Modify config file. You probably need to change most of the config + options. + + * Set up basic firewall rules. The l2tpns process listens on a bunch of + ports: + + 23/tcp command line interface + 1701/udp l2tp (on bind_address) + 1702/udp control port (nsctl) + 3799/udp RADIUS DAE port + 32792/udp clustering messages + + * If you are using the garden plugin, setup the walled garden firewall + rules. These should be in /etc/l2tpns/build-garden, which is run by the + plugin after creating/flushing the "garden" nat table. + + iptables -t nat -A garden -p tcp -m tcp --dport 25 -j DNAT --to 192.168.1.1 + iptables -t nat -A garden -p udp -m udp --dport 53 -j DNAT --to 192.168.1.1 + iptables -t nat -A garden -p tcp -m tcp --dport 53 -j DNAT --to 192.168.1.1 + iptables -t nat -A garden -p tcp -m tcp --dport 80 -j DNAT --to 192.168.1.1 + iptables -t nat -A garden -p tcp -m tcp --dport 110 -j DNAT --to 192.168.1.1 + iptables -t nat -A garden -p tcp -m tcp --dport 443 -j DNAT --to 192.168.1.1 + iptables -t nat -A garden -p icmp -m icmp --icmp-type echo-request -j DNAT --to 192.168.1.1 + iptables -t nat -A garden -p icmp -j ACCEPT + iptables -t nat -A garden -j DROP + + * Set up IP address pools in /etc/l2tpns/ip_pool + + * Set up routing. + - If you are running a single instance, you can simply statically route + the IP pools to the bind_address (l2tpns will send a gratuitous arp). + + - For a cluster, configure the members as BGP neighbours on your router + and configure multi-path load-balancing (on Cisco use "maximum-paths"). + + * Make l2tpns run on startup. In a clustered environment running from + inittab is recomended: + + l2tp:2345:respawn:/home/l2tpns/src/l2tpns >/dev/null 2>&1 + + * Test it out. + + + +This software is quite stable and is being used in a production environment at +a quite large ISP. However, you may have problems setting it up, and if so, I +would appreciate it if you would file useful bug reports on the Source Forge +page: + +http://sourceforge.net/projects/l2tpns/ + +-- David Parrish diff --git a/INTERNALS b/INTERNALS new file mode 100644 index 0000000..b7b0b8c --- /dev/null +++ b/INTERNALS @@ -0,0 +1,265 @@ +Documentation on various internal structures. + +Most important structure use an anonymous shared mmap() +so that child processes can watch them. (All the cli connections +are handled in child processes). + +TODO: Re-investigate threads to see if we can use a thread to handle +cli connections without killing forwarding performance. + +session[] + An array of session structures. This is one of the two + major data structures that are sync'ed across the cluster. + + This array is statically allocated at startup time to a + compile time size (currently 50k sessions). This sets a + hard limit on the number of sessions a cluster can handle. + + There is one element per l2tp session. (I.e. each active user). + + The zero'th session is always invalid. + +tunnel[] + An array of tunnel structures. This is the other major data structure + that's actively sync'ed across the cluster. + + As per sessions, this is statically allocated at startup time + to a compile time size limit. + + There is one element per l2tp tunnel. (normally one per BRAS + that this cluster talks to). + + The zero'th tunnel is always invalid. + +ip_pool[] + + A table holding all the IP address in the pool. As addresses + are used, they are tagged with the username of the session, + and the session index. + + When they are free'd the username tag ISN'T cleared. This is + to ensure that were possible we re-allocate the same IP + address back to the same user. + +radius[] + A table holding active radius session. Whenever a radius + conversation is needed (login, accounting et al), a radius + session is allocated. + +char **ip_hash + + A mapping of IP address to session structure. This is a + tenary tree (each byte of the IP address is used in turn + to index that level of the tree). + + If the value is postive, it's considered to be an index + into the session table. + + If it's negative, it's considered to be an index into + the ip_pool[] table. + + If it's zero, then there is no associated value. + +config->cluster_iam_master + + If true, indicates that this node is the master for + the cluster. This has many consequences... + +config->cluster_iam_uptodate + + On the slaves, this indicates if it's seen a full run + of sessions from the master, and thus it's safe to be + taking traffic. + + On the master, this indicates that all slaves are + up to date. If any of the slaves aren't up to date, + this variable is false, and indicates that we should + shift to more rapid heartbeats to bring the slave + back up to date. + + +============================================================ + +Clustering: How it works. + + At a high level, the various members of the cluster elect +a master. All other machines become slaves as soon as they hear +a heartbeat from the master. Slaves handle normal packet forwarding. +Whenever a slave get a 'state changing' packet (i.e. tunnel setup/teardown, +session setup etc) it _doesn't_ handle it, but instead forwards it +to the master. + + 'State changing' it defined to be "a packet that would cause +a change in either a session or tunnel structure that isn't just +updating the idle time or byte counters". In practise, this means +almost all LCP, IPCP, and L2TP control packets. + + The master then handles the packet normally, updating +the session/tunnel structures. The changed structures are then +flooded out to the slaves via a multicast packet. + + +Heartbeat'ing: + The master sends out a multicast 'heartbeat' packet +at least once every second. This packet contains a sequence number, +and any changes to the session/tunnel structures that have +been queued up. If there is room in the packet, it also sends +out a number of extra session/tunnel structures. + + The sending out of 'extra' structures means that the +master will slowly walk the entire session and tunnel tables. +This allows a new slave to catch-up on cluster state. + + + Each heartbeat has an in-order sequence number. If a +slave receives a heartbeat with a sequence number other than +the one it was expecting, it drops the unexpected packet and +unicasts C_LASTSEEN to tell the master the last heartbeast it +had seen. The master normally than unicasts the missing packets +to the slave. If the master doesn't have the old packet any more +(i.e. it's outside the transmission window) then the master +unicasts C_KILL to the slave asking it to die. (The slave should +then restart, and catchup on state via the normal process). + + If a slave goes for more than a few seconds without +hearing from the master, it sends out a preemptive C_LASTSEEN. +If the master still exists, this forces to the master to unicast +the missed heartbeats. This is a work around for a temporary +multicast problem. (i.e. if an IGMP probe is missed, the slave +will temporarily stop seeing the multicast heartbeats. This +work around prevents the slave from becoming master with +horrible consequences). + +Ping'ing: + All slaves send out a 'ping' once per second as a +multicast packet. This 'ping' contains the slave's ip address, +and most importantly, the number of seconds from epoch +that the slave started up. (I.e. the value of time(2) at +that the process started). (This is the 'basetime'). +Obviously, this is never zero. + + There is a special case. The master can send a single +ping on shutdown to indicate that it is dead and that an +immediate election should be held. This special ping is +send from the master with a 'basetime' of zero. + +Elections: + + All machines start up as slaves. + + Each slave listens for a heartbeat from the master. +If a slave fails to hear a heartbeat for N seconds then it +checks to see if it should become master. + + A slave will become master if: + * It hasn't heard from a master for N seconds. + * It is the oldest of all it's peers (the other slaves). + * In the event of a tie, the machine with the + lowest IP address will win. + + A 'peer' is any other slave machine that's send out a + ping in the last N seconds. (i.e. we must have seen + a recent ping from that slave for it to be considered). + + The upshot of this is that no special communication + takes place when a slave becomes a master. + + On initial cluster startup, the process would be (for example) + + * 3 machines startup simultaneously, all as slaves. + * each machine sends out a multicast 'ping' every second. + * 15 seconds later, the machine with the lowest IP + address becomes master, and starts sending + out heartbeats. + * The remaining two machine hear the heartbeat and + set that machine as their master. + +Becoming master: + + When a slave become master, the only structure maintained up + to date are the tunnel and session structures. This means + the master will rebuild a number of mappings. + + #0. All the session and table structures are marked as + defined. (Even if we weren't fully up to date, it's + too late now). + + #1. All the token bucket filters are re-build from scratch + with the associated session to tbf pointers being re-built. + +TODO: These changed tbf pointers aren't flooded to the slave right away! +Throttled session could take a couple of minutes to start working again +on master failover! + + #2. The ipcache to session hash is rebuilt. (This isn't + strictly needed, but it's a safety measure). + + #3. The mapping from the ippool into the session table + (and vice versa) is re-built. + + +Becoming slave: + + At startup the entire session and table structures are + marked undefined. + + As it seens updates from the master, the updated structures + are marked as defined. + + When there are no undefined tunnel or session structures, the + slave marks itself as 'up-to-date' and starts advertising routes + (if BGP is enabled). + +STONITH: + + Currently, there is very minimal protection from split brain. +In particular, there is no real STONITH protocol to stop two masters +appearing in the event of a network problem. + + + +TODO: + Should slaves that have undefined sessions, and receive +a packet from a non-existant session then forward it to the master?? +In normal practice, a slave with undefined session shouldn't be +handling packets, but ... + + There is far too much walking of large arrays (in the master +specifically). Although this is mitigated somewhat by the +cluster_high_{sess,tun}, this benefit is lost as that value gets +closer to MAX{SESSION,TUNNEL}. There are two issues here: + + * The tunnel, radius and tbf arrays should probably use a + mechanism like sessions, where grabbing a new one is a + single lookup rather than a walk. + + * A list structure (simillarly rooted at [0].interesting) is + required to avoid having to walk tables periodically. As a + back-stop the code in the master which *does* walk the + arrays can mark any entry it processes as "interesting" to + ensure it gets looked at even if a bug causes it to be + otherwiase overlooked. + + Support for more than 64k sessions per cluster. There is +currently a 64k session limit because each session gets an id that global +over the cluster (as opposed to local to the tunnel). Obviously, the tunnel +id needs to be used in conjunction with the session id to index into +the session table. But how? + + I think the best way is to use something like page tables. +for a given , the appropriate session index is +session[ tunnel[tid].page[sid>>10] + (sid & 1023) ] +Where tunnel[].page[] is a 64 element array. As a tunnel +fills up it's page block, it allocated a new 1024 session block +from the session table and fills in the appropriate .page[] +entry. + + This should be a reasonable compromise between wasting memory +(average 500 sessions per tunnel wasted) and speed. (Still a direct +index without searching, but extra lookups required). Obviously +the <6,10> split on the sid can be moved around to tune the size +of the page table v the session table block size. + + This unfortunately means that the tunnel structure HAS to +be filled on the slave before any of the sessions on it can be used. + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ea7e8df --- /dev/null +++ b/Makefile @@ -0,0 +1,133 @@ +DESTDIR = +bindir = /usr/sbin +etcdir = /etc/l2tpns +libdir = /usr/lib/l2tpns +man5dir = /usr/share/man/man5 +man8dir = /usr/share/man/man8 +statedir = /var/lib/l2tpns + +DEFINES = +DEFINES += -DLIBDIR='"$(libdir)"' +DEFINES += -DETCDIR='"$(etcdir)"' + +OPTIM = +OPTIM += -g +OPTIM += -O3 + +CC = gcc +LD = gcc +INCLUDES = -I. +CPPFLAGS = $(INCLUDES) $(DEFINES) +CFLAGS = -Wall -Wformat-security -Wno-format-zero-length $(OPTIM) +LDFLAGS = +LDLIBS = +INSTALL = install -c -D -o root -g root + +l2tpns.LIBS = -lm -lcli -ldl + +OBJS = arp.o cli.o cluster.o constants.o control.o icmp.o l2tpns.o \ + ll.o md5.o ppp.o radius.o tbf.o util.o + +PROGRAMS = l2tpns nsctl +PLUGINS = autosnoop.so autothrottle.so garden.so sessionctl.so \ + setrxspeed.so snoopctl.so stripdomain.so throttlectl.so + +DEFINES += -DSTATISTICS +DEFINES += -DSTAT_CALLS +DEFINES += -DRINGBUFFER + +ifneq (2.4, $(shell uname -r | perl -pe 's/^(\d+\.\d+).*/$$1/')) + DEFINES += -DHAVE_EPOLL +endif + +DEFINES += -DBGP +OBJS += bgp.o + +all: programs plugins +programs: $(PROGRAMS) +plugins: $(PLUGINS) + +clean: + rm -f *.o test/*.o $(PROGRAMS) $(PLUGINS) Makefile.tmp Makefile.bak + +depend: + (sed -n 'p; /^## Dependencies: (autogenerated) ##/q' Makefile && \ + gcc -MM $(CPPFLAGS) $(OBJS:.o=.c) && \ + gcc -MM $(CPPFLAGS) $(PLUGINS:.so=.c) | sed 's/\.o/.so/') >Makefile.tmp + mv Makefile Makefile.bak + mv Makefile.tmp Makefile + +l2tpns: $(OBJS) + $(LD) $(LDFLAGS) -o $@ $^ $(LDLIBS) $($@.LIBS) + +nsctl: nsctl.o control.o + $(LD) $(LDFLAGS) -o $@ $^ $(LDLIBS) $($@.LIBS) + +%.o: %.c + $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $< + +%.so: %.c + $(CC) -fPIC -shared $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $< + +install: all + $(INSTALL) -m 0755 l2tpns $(DESTDIR)$(bindir)/l2tpns + $(INSTALL) -m 0755 nsctl $(DESTDIR)$(bindir)/nsctl + + $(INSTALL) -m 0644 Docs/startup-config.5 $(DESTDIR)$(man5dir)/startup-config.5 + $(INSTALL) -m 0644 Docs/l2tpns.8 $(DESTDIR)$(man8dir)/l2tpns.8 + $(INSTALL) -m 0644 Docs/nsctl.8 $(DESTDIR)$(man8dir)/nsctl.8 + + gzip --best --force $(DESTDIR)$(man5dir)/*.5 $(DESTDIR)$(man8dir)/*.8 + + @for config in startup-config users ip_pool; \ + do \ + suffix=; \ + mode=0600; [ $$config = ip_pool ] && mode=0644; \ + if [ -f $(DESTDIR)$(etcdir)/$$config ]; \ + then \ + cmp -s etc/$$config.default $(DESTDIR)$(etcdir)/$$config && continue; \ + suffix=.default; \ + fi; \ + echo $(INSTALL) -m $$mode etc/$$config.default $(DESTDIR)$(etcdir)/$$config$$suffix; \ + $(INSTALL) -m $$mode etc/$$config.default $(DESTDIR)$(etcdir)/$$config$$suffix; \ + done + + @for plugin in $(PLUGINS); \ + do \ + echo $(INSTALL) -m 0755 $$plugin $(DESTDIR)$(libdir)/$$plugin; \ + $(INSTALL) -m 0755 $$plugin $(DESTDIR)$(libdir)/$$plugin; \ + done + + @if [ -z $(DESTDIR) ] && [ ! -e /dev/net/tun ]; \ + then \ + mkdir /dev/net; \ + echo mknod /dev/net/tun c 10 200; \ + mknod /dev/net/tun c 10 200; \ + fi + +.PHONY: all clean depend install + +## Dependencies: (autogenerated) ## +arp.o: arp.c l2tpns.h +cli.o: cli.c l2tpns.h constants.h util.h cluster.h tbf.h ll.h bgp.h +cluster.o: cluster.c l2tpns.h cluster.h util.h tbf.h bgp.h +constants.o: constants.c constants.h +control.o: control.c l2tpns.h control.h +icmp.o: icmp.c l2tpns.h +l2tpns.o: l2tpns.c md5.h l2tpns.h cluster.h plugin.h ll.h constants.h \ + control.h util.h tbf.h bgp.h +ll.o: ll.c ll.h +md5.o: md5.c md5.h +ppp.o: ppp.c l2tpns.h constants.h plugin.h util.h tbf.h cluster.h +radius.o: radius.c md5.h constants.h l2tpns.h plugin.h util.h cluster.h +tbf.o: tbf.c l2tpns.h util.h tbf.h +util.o: util.c l2tpns.h bgp.h +bgp.o: bgp.c l2tpns.h bgp.h util.h +autosnoop.so: autosnoop.c l2tpns.h plugin.h +autothrottle.so: autothrottle.c l2tpns.h plugin.h +garden.so: garden.c l2tpns.h plugin.h control.h +sessionctl.so: sessionctl.c l2tpns.h plugin.h control.h +setrxspeed.so: setrxspeed.c l2tpns.h plugin.h +snoopctl.so: snoopctl.c l2tpns.h plugin.h control.h +stripdomain.so: stripdomain.c l2tpns.h plugin.h +throttlectl.so: throttlectl.c l2tpns.h plugin.h control.h diff --git a/THANKS b/THANKS new file mode 100644 index 0000000..c39a07d --- /dev/null +++ b/THANKS @@ -0,0 +1,29 @@ +A list of people who have contributed to the development of L2TPNS +by reporting problems, suggesting various improvements or submitting +actual code follows. + +Adrian Kennard +David Parrish +Michael O'Reilly +Brendan O'Dea +Bradley Baetz +Iain Wade +Yuri +Juergen Kammer +Simon Talbot +Jonathan McDowell +Bjørn Augestad +Roberto Chostakovis +Jordan Hrycaj +Vladislav Bjelic +Alex Kiernan +Dominique Rousseau +Tim Devries +Slobodan Tomic +Michael Chapman +Charlie Brady +Jon Morby +Paul Martin +Jonathan Yarden +Patrick Cole +Rhys Kidd diff --git a/arp.c b/arp.c new file mode 100644 index 0000000..5ffd1a4 --- /dev/null +++ b/arp.c @@ -0,0 +1,64 @@ +// L2TPNS: arp + +char const *cvs_id_arp = "$Id: arp.c,v 1.7 2005/07/31 10:04:09 bodea Exp $"; + +#include +#include +#include +#include +#include + +#include "l2tpns.h" + +/* Most of this code is based on keepalived:vrrp_arp.c */ + +struct arp_buf { + struct ether_header eth; + struct arphdr arp; + + /* Data bit - variably sized, so not present in |struct arphdr| */ + unsigned char ar_sha[ETH_ALEN]; /* Sender hardware address */ + in_addr_t ar_sip; /* Sender IP address. */ + unsigned char ar_tha[ETH_ALEN]; /* Target hardware address */ + in_addr_t ar_tip; /* Target ip */ +} __attribute__((packed)); + +void sendarp(int ifr_idx, const unsigned char* mac, in_addr_t ip) +{ + int fd; + struct sockaddr_ll sll; + struct arp_buf buf; + + CSTAT(sendarp); + STAT(arp_sent); + + /* Ethernet */ + memset(buf.eth.ether_dhost, 0xFF, ETH_ALEN); + memcpy(buf.eth.ether_shost, mac, ETH_ALEN); + buf.eth.ether_type = htons(ETHERTYPE_ARP); + + /* ARP */ + buf.arp.ar_hrd = htons(ARPHRD_ETHER); + buf.arp.ar_pro = htons(ETHERTYPE_IP); + buf.arp.ar_hln = ETH_ALEN; + buf.arp.ar_pln = 4; //IPPROTO_ADDR_LEN; + buf.arp.ar_op = htons(ARPOP_REQUEST); + + /* Data */ + memcpy(buf.ar_sha, mac, ETH_ALEN); + memcpy(&buf.ar_sip, &ip, sizeof(ip)); + memcpy(buf.ar_tha, mac, ETH_ALEN); + memcpy(&buf.ar_tip, &ip, sizeof(ip)); + + /* Now actually send the thing */ + fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_RARP)); + + memset(&sll, 0, sizeof(sll)); + sll.sll_family = AF_PACKET; + memcpy(sll.sll_addr, mac, sizeof(sll.sll_addr) - 1); + sll.sll_halen = ETH_ALEN; + sll.sll_ifindex = ifr_idx; + + sendto(fd, &buf, sizeof(buf), 0, (struct sockaddr*)&sll, sizeof(sll)); + close(fd); +} diff --git a/autosnoop.c b/autosnoop.c new file mode 100644 index 0000000..fa3de8b --- /dev/null +++ b/autosnoop.c @@ -0,0 +1,75 @@ +#include +#include "l2tpns.h" +#include "plugin.h" + +/* set up intercept based on RADIUS reply */ + +char const *cvs_id = "$Id: autosnoop.c,v 1.12 2005/10/11 09:04:53 bodea Exp $"; + +int plugin_api_version = PLUGIN_API_VERSION; +static struct pluginfuncs *f = 0; + +int plugin_radius_response(struct param_radius_response *data) +{ + if (!strcmp(data->key, "intercept")) + { + char *p; + data->s->snoop_ip = 0; + data->s->snoop_port = 0; + if ((p = strchr(data->value, ':'))) + { + *p++ = 0; + if (*data->value) + data->s->snoop_ip = inet_addr(data->value); + + if (data->s->snoop_ip == INADDR_NONE) + data->s->snoop_ip = 0; + + if (*p) + data->s->snoop_port = atoi(p); + + f->log(3, f->get_id_by_session(data->s), data->s->tunnel, + " Intercepting user to %s:%d\n", + f->fmtaddr(data->s->snoop_ip, 0), data->s->snoop_port); + } + else + { + f->log(3, f->get_id_by_session(data->s), data->s->tunnel, + " Not Intercepting user (reply string should" + " be intercept=ip:port)\n"); + } + } + + return PLUGIN_RET_OK; +} + +int plugin_radius_reset(struct param_radius_reset *data) +{ + data->s->snoop_ip = 0; + data->s->snoop_port = 0; + return PLUGIN_RET_OK; +} + +int plugin_radius_account(struct param_radius_account *data) +{ + if (data->s->snoop_ip && data->s->snoop_port) + { + uint8_t *p = *data->packet; + + *p = 26; // vendor-specific + *(uint32_t *) (p + 2) = htonl(9); // Cisco + p[6] = 1; // Cisco-AVPair + p[7] = 2 + sprintf((char *) p + 8, "intercept=%s:%d", + f->fmtaddr(data->s->snoop_ip, 0), data->s->snoop_port); + + p[1] = p[7] + 6; + *data->packet += p[1]; + } + + return PLUGIN_RET_OK; +} + +int plugin_init(struct pluginfuncs *funcs) +{ + return ((f = funcs)) ? 1 : 0; +} diff --git a/autothrottle.c b/autothrottle.c new file mode 100644 index 0000000..cd42365 --- /dev/null +++ b/autothrottle.c @@ -0,0 +1,158 @@ +#include +#include "l2tpns.h" +#include "plugin.h" + +/* set up throttling based on RADIUS reply */ + +/* + * lcp:interface-config#1=service-policy input N + * lcp:interface-config#2=service-policy output N + * + * throttle=N + * throttle=yes (use throttle_rate from config) + * throttle=no + */ + +char const *cvs_id = "$Id: autothrottle.c,v 1.16 2005/10/11 09:04:53 bodea Exp $"; + +int plugin_api_version = PLUGIN_API_VERSION; +static struct pluginfuncs *f = 0; + +#define THROTTLE_KEY "lcp:interface-config" + +int plugin_radius_response(struct param_radius_response *data) +{ + if (!strncmp(data->key, THROTTLE_KEY, sizeof(THROTTLE_KEY) - 1)) + { + char *sp = strchr(data->value, ' '); + char type; + int rate; + + if (!sp || sp - data->value < 4 || + strncmp("service-policy", data->value, sp - data->value)) + return PLUGIN_RET_OK; + + while (*sp == ' ') sp++; + data->value = sp; + + if (!(sp = strchr(data->value, ' ')) || + (strncmp("input", data->value, sp - data->value) && + strncmp("output", data->value, sp - data->value))) + { + f->log(3, f->get_id_by_session(data->s), data->s->tunnel, + " Not throttling user (invalid type %.*s)\n", + sp - data->value, data->value); + + return PLUGIN_RET_OK; + } + + type = *data->value; + + while (*sp == ' ') sp++; + data->value = sp; + + if ((rate = strtol(data->value, &sp, 10)) < 0 || *sp) + { + f->log(3, f->get_id_by_session(data->s), data->s->tunnel, + " Not throttling user (invalid rate %s)\n", + data->value); + + return PLUGIN_RET_OK; + } + + if (type == 'i') + { + data->s->throttle_in = rate; + f->log(3, f->get_id_by_session(data->s), data->s->tunnel, + " Throttling user input to %dkb/s\n", rate); + } + else + { + data->s->throttle_out = rate; + f->log(3, f->get_id_by_session(data->s), data->s->tunnel, + " Throttling user output to %dkb/s\n", rate); + } + } + else if (!strcmp(data->key, "throttle")) + { + char *e; + int rate; + + if ((rate = strtol(data->value, &e, 10)) < 0 || *e) + { + rate = -1; + if (!strcmp(data->value, "yes")) + { + unsigned long *ts = f->getconfig("throttle_speed", UNSIGNED_LONG); + if (ts) + rate = *ts; + } + else if (!strcmp(data->value, "no")) + rate = 0; + } + + if (rate < 0) + return PLUGIN_RET_OK; + + if (rate) + f->log(3, f->get_id_by_session(data->s), data->s->tunnel, + " Throttling user to %dkb/s\n", rate); + else + f->log(3, f->get_id_by_session(data->s), data->s->tunnel, + " Not throttling user\n"); + + data->s->throttle_in = data->s->throttle_out = rate; + } + + return PLUGIN_RET_OK; +} + +int plugin_radius_reset(struct param_radius_reset *data) +{ + f->throttle(f->get_id_by_session(data->s), 0, 0); + return PLUGIN_RET_OK; +} + +int plugin_radius_account(struct param_radius_account *data) +{ + if (data->s->throttle_in || data->s->throttle_out) + { + uint8_t *p = *data->packet; + int i = 1; + + if (data->s->throttle_in) + { + *p = 26; // vendor-specific + *(uint32_t *) (p + 2) = htonl(9); // Cisco + p[6] = 1; // Cisco-AVPair + p[7] = 2 + sprintf((char *) p + 8, + "lcp:interface-config#%d=service-policy input %d", i++, + data->s->throttle_in); + + p[1] = p[7] + 6; + p += p[1]; + } + + if (data->s->throttle_out) + { + *p = 26; // vendor-specific + *(uint32_t *) (p + 2) = htonl(9); // Cisco + p[6] = 1; // Cisco-AVPair + p[7] = 2 + sprintf((char *) p + 8, + "lcp:interface-config#%d=service-policy output %d", i++, + data->s->throttle_out); + + p[1] = p[7] + 6; + p += p[1]; + } + + *data->packet = p; + } + + return PLUGIN_RET_OK; +} + +int plugin_init(struct pluginfuncs *funcs) +{ + return ((f = funcs)) ? 1 : 0; +} diff --git a/bgp.c b/bgp.c new file mode 100644 index 0000000..6a868fa --- /dev/null +++ b/bgp.c @@ -0,0 +1,1244 @@ +/* + * BGPv4 + * Used to advertise routes for upstream (l2tp port, rather than gratiutious + * arp) and downstream--allowing routers to load-balance both. + * + * Implementation limitations: + * - We never listen for incoming connections (session always initiated by us). + * - Any routes advertised by the peer are accepted, but ignored. + * - No password support; neither RFC1771 (which no-one seems to do anyway) + * nor RFC2385 (which requires a kernel patch on 2.4 kernels). + */ + +char const *cvs_id_bgp = "$Id: bgp.c,v 1.12 2005/09/02 23:39:36 bodea Exp $"; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "l2tpns.h" +#include "bgp.h" +#include "util.h" + +static void bgp_clear(struct bgp_peer *peer); +static void bgp_set_retry(struct bgp_peer *peer); +static void bgp_cidr(in_addr_t ip, in_addr_t mask, struct bgp_ip_prefix *pfx); +static struct bgp_route_list *bgp_insert_route(struct bgp_route_list *head, + struct bgp_route_list *new); + +static void bgp_free_routes(struct bgp_route_list *routes); +static char const *bgp_msg_type_str(uint8_t type); +static int bgp_connect(struct bgp_peer *peer); +static int bgp_handle_connect(struct bgp_peer *peer); +static int bgp_write(struct bgp_peer *peer); +static int bgp_read(struct bgp_peer *peer); +static int bgp_handle_input(struct bgp_peer *peer); +static int bgp_send_open(struct bgp_peer *peer); +static int bgp_send_keepalive(struct bgp_peer *peer); +static int bgp_send_update(struct bgp_peer *peer); +static int bgp_send_notification(struct bgp_peer *peer, uint8_t code, + uint8_t subcode); + +static uint16_t our_as; +static struct bgp_route_list *bgp_routes = 0; + +int bgp_configured = 0; +struct bgp_peer *bgp_peers = 0; + +/* prepare peer structure, globals */ +int bgp_setup(int as) +{ + int i; + struct bgp_peer *peer; + + for (i = 0; i < BGP_NUM_PEERS; i++) + { + peer = &bgp_peers[i]; + memset(peer, 0, sizeof(*peer)); + + peer->addr = INADDR_NONE; + peer->sock = -1; + peer->state = peer->next_state = Disabled; + + if (!((peer->outbuf = malloc(sizeof(*peer->outbuf))) + && (peer->inbuf = malloc(sizeof(*peer->inbuf))))) + { + LOG(0, 0, 0, "Can't allocate buffers for bgp peer (%s)\n", + strerror(errno)); + + return 0; + } + + peer->edata.type = FD_TYPE_BGP; + peer->edata.index = i; + peer->events = 0; + } + + if (as < 1) + as = 0; + + if ((our_as = as)) + return 0; + + bgp_routes = 0; + bgp_configured = 0; /* set by bgp_start */ + + return 1; +} + +/* start connection with a peer */ +int bgp_start(struct bgp_peer *peer, char *name, int as, int keepalive, + int hold, int enable) +{ + struct hostent *h; + int ibgp; + int i; + struct bgp_path_attr a; + char path_attrs[64]; + char *p = path_attrs; + in_addr_t ip; + uint32_t metric = htonl(BGP_METRIC); + uint32_t no_export = htonl(BGP_COMMUNITY_NO_EXPORT); + + if (!our_as) + return 0; + + if (peer->state != Disabled) + bgp_halt(peer); + + snprintf(peer->name, sizeof(peer->name), "%s", name); + + if (!(h = gethostbyname(name)) || h->h_addrtype != AF_INET) + { + LOG(0, 0, 0, "Can't get address for BGP peer %s (%s)\n", + name, h ? "no address" : hstrerror(h_errno)); + + return 0; + } + + memcpy(&peer->addr, h->h_addr, sizeof(peer->addr)); + peer->as = as > 0 ? as : our_as; + ibgp = peer->as == our_as; + + /* set initial timer values */ + peer->init_keepalive = keepalive == -1 ? BGP_KEEPALIVE_TIME : keepalive; + peer->init_hold = hold == -1 ? BGP_HOLD_TIME : hold; + + if (peer->init_hold < 3) + peer->init_hold = 3; + + if (peer->init_keepalive * 3 > peer->init_hold) + peer->init_keepalive = peer->init_hold / 3; + + /* clear buffers, go to Idle state */ + peer->next_state = Idle; + bgp_clear(peer); + + /* set initial routing state */ + peer->routing = enable; + + /* all our routes use the same attributes, so prepare it in advance */ + if (peer->path_attrs) + free(peer->path_attrs); + + peer->path_attr_len = 0; + + /* ORIGIN */ + a.flags = BGP_PATH_ATTR_FLAG_TRANS; + a.code = BGP_PATH_ATTR_CODE_ORIGIN; + a.data.s.len = 1; + a.data.s.value[0] = BGP_PATH_ATTR_CODE_ORIGIN_IGP; + +#define ADD_ATTRIBUTE() do { \ + i = BGP_PATH_ATTR_SIZE(a); \ + memcpy(p, &a, i); \ + p += i; \ + peer->path_attr_len += i; } while (0) + + ADD_ATTRIBUTE(); + + /* AS_PATH */ + a.flags = BGP_PATH_ATTR_FLAG_TRANS; + a.code = BGP_PATH_ATTR_CODE_AS_PATH; + if (ibgp) + { + /* empty path */ + a.data.s.len = 0; + } + else + { + /* just our AS */ + struct { + uint8_t type; + uint8_t len; + uint16_t value; + } as_path = { + BGP_PATH_ATTR_CODE_AS_PATH_AS_SEQUENCE, + 1, + htons(our_as), + }; + + a.data.s.len = sizeof(as_path); + memcpy(&a.data.s.value, &as_path, sizeof(as_path)); + } + + ADD_ATTRIBUTE(); + + /* NEXT_HOP */ + a.flags = BGP_PATH_ATTR_FLAG_TRANS; + a.code = BGP_PATH_ATTR_CODE_NEXT_HOP; + ip = my_address; /* we're it */ + a.data.s.len = sizeof(ip); + memcpy(a.data.s.value, &ip, sizeof(ip)); + + ADD_ATTRIBUTE(); + + /* MULTI_EXIT_DISC */ + a.flags = BGP_PATH_ATTR_FLAG_OPTIONAL; + a.code = BGP_PATH_ATTR_CODE_MULTI_EXIT_DISC; + a.data.s.len = sizeof(metric); + memcpy(a.data.s.value, &metric, sizeof(metric)); + + ADD_ATTRIBUTE(); + + if (ibgp) + { + uint32_t local_pref = htonl(BGP_LOCAL_PREF); + + /* LOCAL_PREF */ + a.flags = BGP_PATH_ATTR_FLAG_TRANS; + a.code = BGP_PATH_ATTR_CODE_LOCAL_PREF; + a.data.s.len = sizeof(local_pref); + memcpy(a.data.s.value, &local_pref, sizeof(local_pref)); + + ADD_ATTRIBUTE(); + } + + /* COMMUNITIES */ + a.flags = BGP_PATH_ATTR_FLAG_OPTIONAL | BGP_PATH_ATTR_FLAG_TRANS; + a.code = BGP_PATH_ATTR_CODE_COMMUNITIES; + a.data.s.len = sizeof(no_export); + memcpy(a.data.s.value, &no_export, sizeof(no_export)); + + ADD_ATTRIBUTE(); + + if (!(peer->path_attrs = malloc(peer->path_attr_len))) + { + LOG(0, 0, 0, "Can't allocate path_attrs for %s (%s)\n", + name, strerror(errno)); + + return 0; + } + + memcpy(peer->path_attrs, path_attrs, peer->path_attr_len); + + LOG(4, 0, 0, "Initiating BGP connection to %s (routing %s)\n", + name, enable ? "enabled" : "suspended"); + + /* we have at least one peer configured */ + bgp_configured = 1; + + /* connect */ + return bgp_connect(peer); +} + +/* clear counters, timers, routes and buffers; close socket; move to + next_state, which may be Disabled or Idle */ +static void bgp_clear(struct bgp_peer *peer) +{ + if (peer->sock != -1) + { + close(peer->sock); + peer->sock = -1; + } + + peer->keepalive_time = 0; + peer->expire_time = 0; + + peer->keepalive = peer->init_keepalive; + peer->hold = peer->init_hold; + + bgp_free_routes(peer->routes); + peer->routes = 0; + + peer->outbuf->packet.header.len = 0; + peer->outbuf->done = 0; + peer->inbuf->packet.header.len = 0; + peer->inbuf->done = 0; + + peer->cli_flag = 0; + peer->events = 0; + + if (peer->state != peer->next_state) + { + peer->state = peer->next_state; + peer->state_time = time_now; + + LOG(4, 0, 0, "BGP peer %s: state %s\n", peer->name, + bgp_state_str(peer->next_state)); + } +} + +/* initiate a clean shutdown */ +void bgp_stop(struct bgp_peer *peer) +{ + LOG(4, 0, 0, "Terminating BGP connection to %s\n", peer->name); + bgp_send_notification(peer, BGP_ERR_CEASE, 0); +} + +/* drop connection (if any) and set state to Disabled */ +void bgp_halt(struct bgp_peer *peer) +{ + LOG(4, 0, 0, "Aborting BGP connection to %s\n", peer->name); + peer->next_state = Disabled; + bgp_clear(peer); +} + +/* drop connection (if any) and set to Idle for connection retry */ +int bgp_restart(struct bgp_peer *peer) +{ + peer->next_state = Idle; + bgp_clear(peer); + + /* restart now */ + peer->retry_time = time_now; + peer->retry_count = 0; + + /* connect */ + return bgp_connect(peer); +} + +static void bgp_set_retry(struct bgp_peer *peer) +{ + if (peer->retry_count++ < BGP_MAX_RETRY) + { + peer->retry_time = time_now + (BGP_RETRY_BACKOFF * peer->retry_count); + peer->next_state = Idle; + bgp_clear(peer); + } + else + bgp_halt(peer); /* give up */ +} + +/* convert ip/mask to CIDR notation */ +static void bgp_cidr(in_addr_t ip, in_addr_t mask, struct bgp_ip_prefix *pfx) +{ + int i; + uint32_t b; + + /* convert to prefix notation */ + pfx->len = 32; + pfx->prefix = ip; + + if (!mask) /* bogus */ + mask = 0xffffffff; + + for (i = 0; i < 32 && ((b = ntohl(1 << i)), !(mask & b)); i++) + { + pfx->len--; + pfx->prefix &= ~b; + } +} + +/* insert route into list; sorted */ +static struct bgp_route_list *bgp_insert_route(struct bgp_route_list *head, + struct bgp_route_list *new) +{ + struct bgp_route_list *p = head; + struct bgp_route_list *e = 0; + + while (p && memcmp(&p->dest, &new->dest, sizeof(p->dest)) < 0) + { + e = p; + p = p->next; + } + + if (e) + { + new->next = e->next; + e->next = new; + } + else + { + new->next = head; + head = new; + } + + return head; +} + +/* add route to list for peers */ +/* + * Note: this doesn't do route aggregation, nor drop routes if a less + * specific match already exists (partly because I'm lazy, but also so + * that if that route is later deleted we don't have to be concerned + * about adding back the more specific one). + */ +int bgp_add_route(in_addr_t ip, in_addr_t mask) +{ + struct bgp_route_list *r = bgp_routes; + struct bgp_route_list add; + int i; + + bgp_cidr(ip, mask, &add.dest); + add.next = 0; + + /* check for duplicate */ + while (r) + { + i = memcmp(&r->dest, &add.dest, sizeof(r->dest)); + if (!i) + return 1; /* already covered */ + + if (i > 0) + break; + + r = r->next; + } + + /* insert into route list; sorted */ + if (!(r = malloc(sizeof(*r)))) + { + LOG(0, 0, 0, "Can't allocate route for %s/%d (%s)\n", + fmtaddr(add.dest.prefix, 0), add.dest.len, strerror(errno)); + + return 0; + } + + memcpy(r, &add, sizeof(*r)); + bgp_routes = bgp_insert_route(bgp_routes, r); + + /* flag established peers for update */ + for (i = 0; i < BGP_NUM_PEERS; i++) + if (bgp_peers[i].state == Established) + bgp_peers[i].update_routes = 1; + + LOG(4, 0, 0, "Registered BGP route %s/%d\n", + fmtaddr(add.dest.prefix, 0), add.dest.len); + + return 1; +} + +/* remove route from list for peers */ +int bgp_del_route(in_addr_t ip, in_addr_t mask) +{ + struct bgp_route_list *r = bgp_routes; + struct bgp_route_list *e = 0; + struct bgp_route_list del; + int i; + + bgp_cidr(ip, mask, &del.dest); + del.next = 0; + + /* find entry in routes list and remove */ + while (r) + { + i = memcmp(&r->dest, &del.dest, sizeof(r->dest)); + if (!i) + { + if (e) + e->next = r->next; + else + bgp_routes = r->next; + + free(r); + break; + } + + e = r; + + if (i > 0) + r = 0; /* stop */ + else + r = r->next; + } + + /* not found */ + if (!r) + return 1; + + /* flag established peers for update */ + for (i = 0; i < BGP_NUM_PEERS; i++) + if (bgp_peers[i].state == Established) + bgp_peers[i].update_routes = 1; + + LOG(4, 0, 0, "Removed BGP route %s/%d\n", + fmtaddr(del.dest.prefix, 0), del.dest.len); + + return 1; +} + +/* enable or disable routing */ +void bgp_enable_routing(int enable) +{ + int i; + + for (i = 0; i < BGP_NUM_PEERS; i++) + { + bgp_peers[i].routing = enable; + + /* flag established peers for update */ + if (bgp_peers[i].state == Established) + bgp_peers[i].update_routes = 1; + } + + LOG(4, 0, 0, "%s BGP routing\n", enable ? "Enabled" : "Suspended"); +} + +#ifdef HAVE_EPOLL +# include +#else +# include "fake_epoll.h" +#endif + +/* return a bitmask of the events required to poll this peer's fd */ +int bgp_set_poll() +{ + int i; + + if (!bgp_configured) + return 0; + + for (i = 0; i < BGP_NUM_PEERS; i++) + { + struct bgp_peer *peer = &bgp_peers[i]; + int events = 0; + + if (peer->state == Disabled || peer->state == Idle) + continue; + + if (peer->inbuf->done < BGP_MAX_PACKET_SIZE) + events |= EPOLLIN; + + if (peer->state == Connect || /* connection in progress */ + peer->update_routes || /* routing updates */ + peer->outbuf->packet.header.len) /* pending output */ + events |= EPOLLOUT; + + if (peer->events != events) + { + struct epoll_event ev; + + ev.events = peer->events = events; + ev.data.ptr = &peer->edata; + epoll_ctl(epollfd, EPOLL_CTL_MOD, peer->sock, &ev); + } + } + + return 1; +} + +/* process bgp events/timers */ +int bgp_process(uint32_t events[]) +{ + int i; + + if (!bgp_configured) + return 0; + + for (i = 0; i < BGP_NUM_PEERS; i++) + { + struct bgp_peer *peer = &bgp_peers[i]; + + if (*peer->name && peer->cli_flag == BGP_CLI_RESTART) + { + bgp_restart(peer); + continue; + } + + if (peer->state == Disabled) + continue; + + if (peer->cli_flag) + { + switch (peer->cli_flag) + { + case BGP_CLI_SUSPEND: + if (peer->routing) + { + peer->routing = 0; + if (peer->state == Established) + peer->update_routes = 1; + } + + break; + + case BGP_CLI_ENABLE: + if (!peer->routing) + { + peer->routing = 1; + if (peer->state == Established) + peer->update_routes = 1; + } + + break; + } + + peer->cli_flag = 0; + } + + /* handle empty/fill of buffers */ + if (events[i] & EPOLLOUT) + { + int r = 1; + if (peer->state == Connect) + r = bgp_handle_connect(peer); + else if (peer->outbuf->packet.header.len) + r = bgp_write(peer); + + if (!r) + continue; + } + + if (events[i] & (EPOLLIN|EPOLLHUP)) + { + if (!bgp_read(peer)) + continue; + } + + /* process input buffer contents */ + while (peer->inbuf->done >= sizeof(peer->inbuf->packet.header) + && !peer->outbuf->packet.header.len) /* may need to queue a response */ + { + if (bgp_handle_input(peer) < 0) + continue; + } + + /* process pending updates */ + if (peer->update_routes + && !peer->outbuf->packet.header.len) /* ditto */ + { + if (!bgp_send_update(peer)) + continue; + } + + /* process timers */ + if (peer->state == Established) + { + if (time_now > peer->expire_time) + { + LOG(1, 0, 0, "No message from BGP peer %s in %ds\n", + peer->name, peer->hold); + + bgp_send_notification(peer, BGP_ERR_HOLD_TIMER_EXP, 0); + continue; + } + + if (time_now > peer->keepalive_time && !peer->outbuf->packet.header.len) + bgp_send_keepalive(peer); + } + else if (peer->state == Idle) + { + if (time_now > peer->retry_time) + bgp_connect(peer); + } + else if (time_now > peer->state_time + BGP_STATE_TIME) + { + LOG(1, 0, 0, "%s timer expired for BGP peer %s\n", + bgp_state_str(peer->state), peer->name); + + bgp_restart(peer); + } + } + + return 1; +} + +static void bgp_free_routes(struct bgp_route_list *routes) +{ + struct bgp_route_list *tmp; + + while ((tmp = routes)) + { + routes = tmp->next; + free(tmp); + } +} + +char const *bgp_state_str(enum bgp_state state) +{ + switch (state) + { + case Disabled: return "Disabled"; + case Idle: return "Idle"; + case Connect: return "Connect"; + case Active: return "Active"; + case OpenSent: return "OpenSent"; + case OpenConfirm: return "OpenConfirm"; + case Established: return "Established"; + } + + return "?"; +} + +static char const *bgp_msg_type_str(uint8_t type) +{ + switch (type) + { + case BGP_MSG_OPEN: return "OPEN"; + case BGP_MSG_UPDATE: return "UPDATE"; + case BGP_MSG_NOTIFICATION: return "NOTIFICATION"; + case BGP_MSG_KEEPALIVE: return "KEEPALIVE"; + } + + return "?"; +} + +/* attempt to connect to peer */ +static int bgp_connect(struct bgp_peer *peer) +{ + static int bgp_port = 0; + struct sockaddr_in addr; + struct epoll_event ev; + + if (!bgp_port) + { + struct servent *serv; + if (!(serv = getservbyname("bgp", "tcp"))) + { + LOG(0, 0, 0, "Can't get bgp service (%s)\n", strerror(errno)); + return 0; + } + + bgp_port = serv->s_port; + } + + if ((peer->sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) + { + LOG(0, 0, 0, "Can't create a socket for BGP peer %s (%s)\n", + peer->name, strerror(errno)); + + peer->state = peer->next_state = Disabled; + return 0; + } + + /* add to poll set */ + ev.events = peer->events = EPOLLOUT; + ev.data.ptr = &peer->edata; + epoll_ctl(epollfd, EPOLL_CTL_ADD, peer->sock, &ev); + + /* set to non-blocking */ + fcntl(peer->sock, F_SETFL, fcntl(peer->sock, F_GETFL, 0) | O_NONBLOCK); + + /* try connect */ + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = bgp_port; + addr.sin_addr.s_addr = peer->addr; + + while (connect(peer->sock, (struct sockaddr *) &addr, sizeof(addr)) == -1) + { + if (errno == EINTR) /* SIGALARM handler */ + continue; + + if (errno != EINPROGRESS) + { + LOG(1, 0, 0, "Can't connect to BGP peer %s (%s)\n", + inet_ntoa(addr.sin_addr), strerror(errno)); + + bgp_set_retry(peer); + return 0; + } + + peer->state = Connect; + peer->state_time = time_now; + + LOG(4, 0, 0, "BGP peer %s: state Connect\n", peer->name); + return 1; + } + + peer->state = Active; + peer->state_time = time_now; + peer->retry_time = peer->retry_count = 0; + + LOG(4, 0, 0, "BGP peer %s: state Active\n", inet_ntoa(addr.sin_addr)); + + return bgp_send_open(peer); +} + +/* complete partial connection (state = Connect) */ +static int bgp_handle_connect(struct bgp_peer *peer) +{ + int err = 0; + socklen_t len = sizeof(int); + getsockopt(peer->sock, SOL_SOCKET, SO_ERROR, &err, &len); + if (err) + { + LOG(1, 0, 0, "Can't connect to BGP peer %s (%s)\n", peer->name, + strerror(err)); + + bgp_set_retry(peer); + return 0; + } + + peer->state = Active; + peer->state_time = time_now; + + LOG(4, 0, 0, "BGP peer %s: state Active\n", peer->name); + + return bgp_send_open(peer); +} + +/* initiate a write */ +static int bgp_write(struct bgp_peer *peer) +{ + int len = htons(peer->outbuf->packet.header.len); + int r; + + while ((r = write(peer->sock, &peer->outbuf->packet + peer->outbuf->done, + len - peer->outbuf->done)) == -1) + { + if (errno == EINTR) + continue; + + if (errno == EAGAIN) + return 1; + + if (errno == EPIPE) + LOG(1, 0, 0, "Connection to BGP peer %s closed\n", peer->name); + else + LOG(1, 0, 0, "Can't write to BGP peer %s (%s)\n", peer->name, + strerror(errno)); + + bgp_set_retry(peer); + return 0; + } + + if (r < len) + { + peer->outbuf->done += r; + return 1; + } + + LOG(4, 0, 0, "Sent %s to BGP peer %s\n", + bgp_msg_type_str(peer->outbuf->packet.header.type), peer->name); + + peer->outbuf->packet.header.len = 0; + peer->outbuf->done = 0; + + if (peer->state == Established) + peer->keepalive_time = time_now + peer->keepalive; + + if (peer->state != peer->next_state) + { + if (peer->next_state == Disabled || peer->next_state == Idle) + { + bgp_clear(peer); + return 0; + } + + peer->state = peer->next_state; + peer->state_time = time_now; + + LOG(4, 0, 0, "BGP peer %s: state %s\n", peer->name, + bgp_state_str(peer->state)); + } + + return 1; +} + +/* initiate a read */ +static int bgp_read(struct bgp_peer *peer) +{ + int r; + + while ((r = read(peer->sock, &peer->inbuf->packet + peer->inbuf->done, + BGP_MAX_PACKET_SIZE - peer->inbuf->done)) < 1) + { + if (!r) + { + LOG(1, 0, 0, "Connection to BGP peer %s closed\n", peer->name); + } + else + { + if (errno == EINTR) + continue; + + if (errno == EAGAIN) + return 1; + + LOG(1, 0, 0, "Can't read from BGP peer %s (%s)\n", peer->name, + strerror(errno)); + } + + bgp_set_retry(peer); + return 0; + } + + peer->inbuf->done += r; + return 1; +} + +/* process buffered packets */ +static int bgp_handle_input(struct bgp_peer *peer) +{ + struct bgp_packet *p = &peer->inbuf->packet; + int len = ntohs(p->header.len); + + if (len > BGP_MAX_PACKET_SIZE) + { + LOG(1, 0, 0, "Bad header length from BGP %s\n", peer->name); + bgp_send_notification(peer, BGP_ERR_HEADER, BGP_ERR_HDR_BAD_LEN); + return 0; + } + + if (peer->inbuf->done < len) + return 0; + + LOG(4, 0, 0, "Received %s from BGP peer %s\n", + bgp_msg_type_str(p->header.type), peer->name); + + switch (p->header.type) + { + case BGP_MSG_OPEN: + { + struct bgp_data_open data; + int hold; + int i; + + for (i = 0; i < sizeof(p->header.marker); i++) + { + if ((unsigned char) p->header.marker[i] != 0xff) + { + LOG(1, 0, 0, "Invalid marker from BGP peer %s\n", + peer->name); + + bgp_send_notification(peer, BGP_ERR_HEADER, + BGP_ERR_HDR_NOT_SYNC); + + return 0; + } + } + + if (peer->state != OpenSent) + { + LOG(1, 0, 0, "OPEN from BGP peer %s in %s state\n", + peer->name, bgp_state_str(peer->state)); + + bgp_send_notification(peer, BGP_ERR_FSM, 0); + return 0; + } + + memcpy(&data, p->data, len - sizeof(p->header)); + + if (data.version != BGP_VERSION) + { + LOG(1, 0, 0, "Bad version (%d) sent by BGP peer %s\n", + (int) data.version, peer->name); + + bgp_send_notification(peer, BGP_ERR_OPEN, BGP_ERR_OPN_VERSION); + return 0; + } + + if (ntohs(data.as) != peer->as) + { + LOG(1, 0, 0, "Bad AS sent by BGP peer %s (got %d, " + "expected %d)\n", peer->name, (int) htons(data.as), + (int) peer->as); + + bgp_send_notification(peer, BGP_ERR_OPEN, BGP_ERR_OPN_BAD_AS); + return 0; + } + + if ((hold = ntohs(data.hold_time)) < 3) + { + LOG(1, 0, 0, "Bad hold time (%d) from BGP peer %s\n", + hold, peer->name); + + bgp_send_notification(peer, BGP_ERR_OPEN, BGP_ERR_OPN_HOLD_TIME); + return 0; + } + + /* pick lowest hold time */ + if (hold < peer->hold) + peer->hold = hold; + + /* adjust our keepalive based on negotiated hold value */ + if (peer->keepalive * 3 > peer->hold) + peer->keepalive = peer->hold / 3; + + /* next transition requires an exchange of keepalives */ + bgp_send_keepalive(peer); + + /* FIXME: may need to check for optional params */ + } + + break; + + case BGP_MSG_KEEPALIVE: + if (peer->state == OpenConfirm) + { + peer->state = peer->next_state = Established; + peer->state_time = time_now; + peer->keepalive_time = time_now + peer->keepalive; + peer->update_routes = 1; + peer->retry_count = 0; + peer->retry_time = 0; + + LOG(4, 0, 0, "BGP peer %s: state Established\n", peer->name); + } + + break; + + case BGP_MSG_NOTIFICATION: + if (len > sizeof(p->header)) + { + struct bgp_data_notification *notification = + (struct bgp_data_notification *) p->data; + + if (notification->error_code == BGP_ERR_CEASE) + { + LOG(4, 0, 0, "BGP peer %s sent CEASE\n", peer->name); + bgp_restart(peer); + return 0; + } + + /* FIXME: should handle more notifications */ + LOG(4, 0, 0, "BGP peer %s sent unhandled NOTIFICATION %d\n", + peer->name, (int) notification->error_code); + } + + break; + } + + /* reset timer */ + peer->expire_time = time_now + peer->hold; + + /* see if there's another message in the same packet/buffer */ + if (peer->inbuf->done > len) + { + peer->inbuf->done -= len; + memmove(p, (char *) p + len, peer->inbuf->done); + } + else + { + peer->inbuf->packet.header.len = 0; + peer->inbuf->done = 0; + } + + return peer->inbuf->done; +} + +/* send/buffer OPEN message */ +static int bgp_send_open(struct bgp_peer *peer) +{ + struct bgp_data_open data; + uint16_t len = sizeof(peer->outbuf->packet.header); + + memset(peer->outbuf->packet.header.marker, 0xff, + sizeof(peer->outbuf->packet.header.marker)); + + peer->outbuf->packet.header.type = BGP_MSG_OPEN; + + data.version = BGP_VERSION; + data.as = htons(our_as); + data.hold_time = htons(peer->hold); + data.identifier = my_address; + data.opt_len = 0; + + memcpy(peer->outbuf->packet.data, &data, BGP_DATA_OPEN_SIZE); + len += BGP_DATA_OPEN_SIZE; + + peer->outbuf->packet.header.len = htons(len); + peer->outbuf->done = 0; + peer->next_state = OpenSent; + + return bgp_write(peer); +} + +/* send/buffer KEEPALIVE message */ +static int bgp_send_keepalive(struct bgp_peer *peer) +{ + memset(peer->outbuf->packet.header.marker, 0xff, + sizeof(peer->outbuf->packet.header.marker)); + + peer->outbuf->packet.header.type = BGP_MSG_KEEPALIVE; + peer->outbuf->packet.header.len = + htons(sizeof(peer->outbuf->packet.header)); + + peer->outbuf->done = 0; + peer->next_state = (peer->state == OpenSent) ? OpenConfirm : peer->state; + + return bgp_write(peer); +} + +/* send/buffer UPDATE message */ +static int bgp_send_update(struct bgp_peer *peer) +{ + uint16_t unf_len = 0; + uint16_t attr_len; + uint16_t len = sizeof(peer->outbuf->packet.header); + struct bgp_route_list *have = peer->routes; + struct bgp_route_list *want = peer->routing ? bgp_routes : 0; + struct bgp_route_list *e = 0; + struct bgp_route_list *add = 0; + int s; + + char *data = (char *) &peer->outbuf->packet.data; + + /* need leave room for attr_len, bgp_path_attrs and one prefix */ + char *max = (char *) &peer->outbuf->packet.data + + sizeof(peer->outbuf->packet.data) + - sizeof(attr_len) - peer->path_attr_len - sizeof(struct bgp_ip_prefix); + + /* skip over unf_len */ + data += sizeof(unf_len); + len += sizeof(unf_len); + + memset(peer->outbuf->packet.header.marker, 0xff, + sizeof(peer->outbuf->packet.header.marker)); + + peer->outbuf->packet.header.type = BGP_MSG_UPDATE; + + peer->update_routes = 0; /* tentatively clear */ + + /* find differences */ + while ((have || want) && data < (max - sizeof(struct bgp_ip_prefix))) + { + if (have) + s = want + ? memcmp(&have->dest, &want->dest, sizeof(have->dest)) + : -1; + else + s = 1; + + if (s < 0) /* found one to delete */ + { + struct bgp_route_list *tmp = have; + have = have->next; + + s = BGP_IP_PREFIX_SIZE(tmp->dest); + memcpy(data, &tmp->dest, s); + data += s; + unf_len += s; + len += s; + + LOG(5, 0, 0, "Withdrawing route %s/%d from BGP peer %s\n", + fmtaddr(tmp->dest.prefix, 0), tmp->dest.len, peer->name); + + free(tmp); + + if (e) + e->next = have; + else + peer->routes = have; + } + else + { + if (!s) /* same */ + { + e = have; /* stash the last found to relink above */ + have = have->next; + want = want->next; + } + else if (s > 0) /* addition reqd. */ + { + if (add) + { + peer->update_routes = 1; /* only one add per packet */ + if (!have) + break; + } + else + add = want; + + if (want) + want = want->next; + } + } + } + + if (have || want) + peer->update_routes = 1; /* more to do */ + + /* anything changed? */ + if (!(unf_len || add)) + return 1; + + /* go back and insert unf_len */ + unf_len = htons(unf_len); + memcpy(&peer->outbuf->packet.data, &unf_len, sizeof(unf_len)); + + if (add) + { + if (!(e = malloc(sizeof(*e)))) + { + LOG(0, 0, 0, "Can't allocate route for %s/%d (%s)\n", + fmtaddr(add->dest.prefix, 0), add->dest.len, strerror(errno)); + + return 0; + } + + memcpy(e, add, sizeof(*e)); + e->next = 0; + peer->routes = bgp_insert_route(peer->routes, e); + + attr_len = htons(peer->path_attr_len); + memcpy(data, &attr_len, sizeof(attr_len)); + data += sizeof(attr_len); + len += sizeof(attr_len); + + memcpy(data, peer->path_attrs, peer->path_attr_len); + data += peer->path_attr_len; + len += peer->path_attr_len; + + s = BGP_IP_PREFIX_SIZE(add->dest); + memcpy(data, &add->dest, s); + data += s; + len += s; + + LOG(5, 0, 0, "Advertising route %s/%d to BGP peer %s\n", + fmtaddr(add->dest.prefix, 0), add->dest.len, peer->name); + } + else + { + attr_len = 0; + memcpy(data, &attr_len, sizeof(attr_len)); + data += sizeof(attr_len); + len += sizeof(attr_len); + } + + peer->outbuf->packet.header.len = htons(len); + peer->outbuf->done = 0; + + return bgp_write(peer); +} + +/* send/buffer NOTIFICATION message */ +static int bgp_send_notification(struct bgp_peer *peer, uint8_t code, + uint8_t subcode) +{ + struct bgp_data_notification data; + uint16_t len = 0; + + data.error_code = code; + len += sizeof(data.error_code); + + data.error_subcode = subcode; + len += sizeof(data.error_code); + + memset(peer->outbuf->packet.header.marker, 0xff, + sizeof(peer->outbuf->packet.header.marker)); + + peer->outbuf->packet.header.type = BGP_MSG_NOTIFICATION; + peer->outbuf->packet.header.len = + htons(sizeof(peer->outbuf->packet.header) + len); + + memcpy(peer->outbuf->packet.data, &data, len); + + peer->outbuf->done = 0; + peer->next_state = code == BGP_ERR_CEASE ? Disabled : Idle; + + /* we're dying; ignore any pending input */ + peer->inbuf->packet.header.len = 0; + peer->inbuf->done = 0; + + return bgp_write(peer); +} diff --git a/bgp.h b/bgp.h new file mode 100644 index 0000000..0a2e59c --- /dev/null +++ b/bgp.h @@ -0,0 +1,205 @@ +/* BGPv4 (RFC1771) */ +/* $Id: bgp.h,v 1.5 2005/06/04 15:42:35 bodea Exp $ */ + +#ifndef __BGP_H__ +#define __BGP_H__ + +#define BGP_MAX_PACKET_SIZE 4096 +#define BGP_HOLD_TIME 180 /* seconds before peer times us out */ +#define BGP_KEEPALIVE_TIME 60 /* seconds between messages */ +#define BGP_STATE_TIME 60 /* state transition timeout in seconds */ +#define BGP_MAX_RETRY 42 /* maximum number of times to retry */ +#define BGP_RETRY_BACKOFF 60 /* number of seconds between retries, + cumulative */ + +#define BGP_METRIC 1 /* multi_exit_disc */ +#define BGP_LOCAL_PREF 100 /* local preference value */ + +struct bgp_header { + char marker[16]; + uint16_t len; + uint8_t type; +} __attribute__ ((packed)); + +/* bgp_header.type */ +#define BGP_MSG_OPEN 1 +#define BGP_MSG_UPDATE 2 +#define BGP_MSG_NOTIFICATION 3 +#define BGP_MSG_KEEPALIVE 4 + +struct bgp_packet { + struct bgp_header header; + char data[BGP_MAX_PACKET_SIZE - sizeof(struct bgp_header)]; /* variable */ +} __attribute__ ((packed)); + +struct bgp_data_open { + uint8_t version; +#define BGP_VERSION 4 + uint16_t as; + uint16_t hold_time; + uint32_t identifier; + uint8_t opt_len; +#define BGP_DATA_OPEN_SIZE 10 /* size of struct excluding opt_params */ + char opt_params[sizeof(((struct bgp_packet *)0)->data) - BGP_DATA_OPEN_SIZE]; /* variable */ +} __attribute__ ((packed)); + +struct bgp_ip_prefix { + uint8_t len; + uint32_t prefix; /* variable */ +} __attribute__ ((packed)); + +#define BGP_IP_PREFIX_SIZE(p) (1 + ((p).len / 8) + ((p).len % 8 != 0)) + +struct bgp_path_attr { + uint8_t flags; + uint8_t code; + union { + struct { + uint8_t len; + char value[29]; /* semi-random size, adequate for l2tpns */ + } __attribute__ ((packed)) s; /* short */ + struct { + uint16_t len; + char value[28]; + } __attribute__ ((packed)) e; /* extended */ + } data; /* variable */ +} __attribute__ ((packed)); + +/* bgp_path_attr.flags (bitfields) */ +#define BGP_PATH_ATTR_FLAG_OPTIONAL (1 << 7) +#define BGP_PATH_ATTR_FLAG_TRANS (1 << 6) +#define BGP_PATH_ATTR_FLAG_PARTIAL (1 << 5) +#define BGP_PATH_ATTR_FLAG_EXTLEN (1 << 4) + +/* bgp_path_attr.code, ...value */ +#define BGP_PATH_ATTR_CODE_ORIGIN 1 /* well-known, mandatory */ +# define BGP_PATH_ATTR_CODE_ORIGIN_IGP 0 +# define BGP_PATH_ATTR_CODE_ORIGIN_EGP 1 +# define BGP_PATH_ATTR_CODE_ORIGIN_INCOMPLETE 2 +#define BGP_PATH_ATTR_CODE_AS_PATH 2 /* well-known, mandatory */ +# define BGP_PATH_ATTR_CODE_AS_PATH_AS_SET 1 +# define BGP_PATH_ATTR_CODE_AS_PATH_AS_SEQUENCE 2 +#define BGP_PATH_ATTR_CODE_NEXT_HOP 3 /* well-known, mandatory */ +#define BGP_PATH_ATTR_CODE_MULTI_EXIT_DISC 4 /* optional, non-transitive */ +#define BGP_PATH_ATTR_CODE_LOCAL_PREF 5 /* well-known, discretionary */ +#define BGP_PATH_ATTR_CODE_ATOMIC_AGGREGATE 6 /* well-known, discretionary */ +#define BGP_PATH_ATTR_CODE_AGGREGATOR 7 /* optional, transitive */ +#define BGP_PATH_ATTR_CODE_COMMUNITIES 8 /* optional, transitive (RFC1997) */ + +#define BGP_PATH_ATTR_SIZE(p) ((((p).flags & BGP_PATH_ATTR_FLAG_EXTLEN) \ + ? ((p).data.e.len + 1) : (p).data.s.len) + 3) + +/* well known COMMUNITIES */ +#define BGP_COMMUNITY_NO_EXPORT 0xffffff01 /* don't advertise outside confederation */ +#define BGP_COMMUNITY_NO_ADVERTISE 0xffffff02 /* don't advertise to any peer */ +#define BGP_COMMUNITY_NO_EXPORT_SUBCONFED 0xffffff03 /* don't advertise to any other AS */ + +struct bgp_data_notification { + uint8_t error_code; + uint8_t error_subcode; + char data[sizeof(((struct bgp_packet *)0)->data) - 2]; /* variable */ +} __attribute__ ((packed)); + +/* bgp_data_notification.error_code, .error_subcode */ +#define BGP_ERR_HEADER 1 +# define BGP_ERR_HDR_NOT_SYNC 1 +# define BGP_ERR_HDR_BAD_LEN 2 +# define BGP_ERR_HDR_BAD_TYPE 3 +#define BGP_ERR_OPEN 2 +# define BGP_ERR_OPN_VERSION 1 +# define BGP_ERR_OPN_BAD_AS 2 +# define BGP_ERR_OPN_BAD_IDENT 3 +# define BGP_ERR_OPN_UNSUP_PARAM 4 +# define BGP_ERR_OPN_AUTH_FAILURE 5 +# define BGP_ERR_OPN_HOLD_TIME 6 +#define BGP_ERR_UPDATE 3 +# define BGP_ERR_UPD_BAD_ATTR_LIST 1 +# define BGP_ERR_UPD_UNKN_WK_ATTR 2 +# define BGP_ERR_UPD_MISS_WK_ATTR 3 +# define BGP_ERR_UPD_BAD_ATTR_FLAG 4 +# define BGP_ERR_UPD_BAD_ATTR_LEN 5 +# define BGP_ERR_UPD_BAD_ORIGIN 6 +# define BGP_ERR_UPD_ROUTING_LOOP 7 +# define BGP_ERR_UPD_BAD_NEXT_HOP 8 +# define BGP_ERR_UPD_BAD_OPT_ATTR 9 +# define BGP_ERR_UPD_BAD_NETWORK 10 +# define BGP_ERR_UPD_BAD_AS_PATH 11 +#define BGP_ERR_HOLD_TIMER_EXP 4 +#define BGP_ERR_FSM 5 +#define BGP_ERR_CEASE 6 + +enum bgp_state { + Disabled, /* initial, or failed */ + Idle, /* trying to connect */ + Connect, /* connect issued */ + Active, /* connected, waiting to send OPEN */ + OpenSent, /* OPEN sent, waiting for peer OPEN */ + OpenConfirm, /* KEEPALIVE sent, waiting for peer KEEPALIVE */ + Established, /* established */ +}; + +struct bgp_route_list { + struct bgp_ip_prefix dest; + struct bgp_route_list *next; +}; + +struct bgp_buf { + struct bgp_packet packet; /* BGP packet */ + size_t done; /* bytes sent/recvd */ +}; + +/* state */ +struct bgp_peer { + char name[32]; /* peer name */ + in_addr_t addr; /* peer address */ + int as; /* AS number */ + int sock; + enum bgp_state state; /* FSM state */ + enum bgp_state next_state; /* next state after outbuf cleared */ + time_t state_time; /* time of last state change */ + time_t keepalive_time; /* time to send next keepalive */ + time_t retry_time; /* time for connection retry */ + int retry_count; /* connection retry count */ + int init_keepalive; /* initial keepalive time */ + int init_hold; /* initial hold time */ + int keepalive; /* negotiated keepalive time */ + int hold; /* negotiated hold time */ + time_t expire_time; /* time next peer packet expected */ + int routing; /* propagate routes */ + int update_routes; /* UPDATE required */ + struct bgp_route_list *routes; /* routes known by this peer */ + struct bgp_buf *outbuf; /* pending output */ + struct bgp_buf *inbuf; /* pending input */ + int cli_flag; /* updates requested from CLI */ + char *path_attrs; /* path attrs to send in UPDATE message */ + int path_attr_len; /* length of path attrs */ + uint32_t events; /* events to poll */ + struct event_data edata; /* poll data */ +}; + +/* bgp_peer.cli_flag */ +#define BGP_CLI_SUSPEND 1 +#define BGP_CLI_ENABLE 2 +#define BGP_CLI_RESTART 3 + +extern struct bgp_peer *bgp_peers; +extern int bgp_configured; + +/* actions */ +int bgp_setup(int as); +int bgp_start(struct bgp_peer *peer, char *name, int as, int keepalive, + int hold, int enable); + +void bgp_stop(struct bgp_peer *peer); +void bgp_halt(struct bgp_peer *peer); +int bgp_restart(struct bgp_peer *peer); +int bgp_add_route(in_addr_t ip, in_addr_t mask); +int bgp_del_route(in_addr_t ip, in_addr_t mask); +void bgp_enable_routing(int enable); +int bgp_set_poll(void); +int bgp_process(uint32_t events[]); +char const *bgp_state_str(enum bgp_state state); + +extern char const *cvs_id_bgp; + +#endif /* __BGP_H__ */ diff --git a/cli.c b/cli.c new file mode 100644 index 0000000..bff3bf8 --- /dev/null +++ b/cli.c @@ -0,0 +1,3066 @@ +// L2TPNS Command Line Interface +// vim: sw=8 ts=8 + +char const *cvs_name = "$Name: release_2_1_21 $"; +char const *cvs_id_cli = "$Id: cli.c,v 1.71 2005/12/06 09:43:42 bodea Exp $"; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "l2tpns.h" +#include "constants.h" +#include "util.h" +#include "cluster.h" +#include "tbf.h" +#include "ll.h" +#ifdef BGP +#include "bgp.h" +#endif + +extern tunnelt *tunnel; +extern sessiont *session; +extern radiust *radius; +extern ippoolt *ip_address_pool; +extern struct Tstats *_statistics; +static struct cli_def *cli = NULL; +extern configt *config; +extern config_descriptt config_values[]; +#ifdef RINGBUFFER +extern struct Tringbuffer *ringbuffer; +#endif +extern struct cli_session_actions *cli_session_actions; +extern struct cli_tunnel_actions *cli_tunnel_actions; +extern tbft *filter_list; +extern ip_filtert *ip_filters; + +struct +{ + char critical; + char error; + char warning; + char info; + char calls; + char data; +} debug_flags; + +#ifdef RINGBUFFER + +static int debug_rb_tail; +static char *debug_levels[] = { + "CRIT", + "ERROR", + "WARN", + "INFO", + "CALL", + "DATA", +}; + +#endif + +static int cmd_show_session(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_show_tunnels(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_show_users(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_show_radius(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_show_version(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_show_pool(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_show_run(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_show_banana(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_show_plugins(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_show_throttle(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_write_memory(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_drop_user(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_drop_tunnel(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_drop_session(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_snoop(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_no_snoop(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_throttle(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_no_throttle(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_debug(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_no_debug(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_set(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_load_plugin(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_remove_plugin(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_uptime(struct cli_def *cli, char *command, char **argv, int argc); + +static int regular_stuff(struct cli_def *cli); + +#ifdef STATISTICS +static int cmd_show_counters(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_clear_counters(struct cli_def *cli, char *command, char **argv, int argc); +#endif /* STATISTICS */ + +#ifdef BGP +#define MODE_CONFIG_BGP 8 +static int cmd_router_bgp(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_router_bgp_neighbour(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_router_bgp_no_neighbour(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_show_bgp(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_suspend_bgp(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_no_suspend_bgp(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_restart_bgp(struct cli_def *cli, char *command, char **argv, int argc); +#endif /* BGP */ + +#define MODE_CONFIG_NACL 9 +static int cmd_ip_access_list(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_no_ip_access_list(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_ip_access_list_rule(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_filter(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_no_filter(struct cli_def *cli, char *command, char **argv, int argc); +static int cmd_show_access_list(struct cli_def *cli, char *command, char **argv, int argc); + +/* match if b is a substr of a */ +#define MATCH(a,b) (!strncmp((a), (b), strlen(b))) + +void init_cli(char *hostname) +{ + FILE *f; + char buf[4096]; + struct cli_command *c; + struct cli_command *c2; + int on = 1; + struct sockaddr_in addr; + + cli = cli_init(); + if (hostname && *hostname) + cli_set_hostname(cli, hostname); + else + cli_set_hostname(cli, "l2tpns"); + + c = cli_register_command(cli, NULL, "show", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); + cli_register_command(cli, c, "banana", cmd_show_banana, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show a banana"); +#ifdef BGP + cli_register_command(cli, c, "bgp", cmd_show_bgp, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show BGP status"); +#endif /* BGP */ + cli_register_command(cli, c, "cluster", cmd_show_cluster, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show cluster information"); + cli_register_command(cli, c, "ipcache", cmd_show_ipcache, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show contents of the IP cache"); + cli_register_command(cli, c, "plugins", cmd_show_plugins, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "List all installed plugins"); + cli_register_command(cli, c, "pool", cmd_show_pool, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show the IP address allocation pool"); + cli_register_command(cli, c, "radius", cmd_show_radius, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show active radius queries"); + cli_register_command(cli, c, "running-config", cmd_show_run, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Show the currently running configuration"); + cli_register_command(cli, c, "session", cmd_show_session, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show a list of sessions or details for a single session"); + cli_register_command(cli, c, "tbf", cmd_show_tbf, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "List all token bucket filters in use"); + cli_register_command(cli, c, "throttle", cmd_show_throttle, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "List all throttled sessions and associated TBFs"); + cli_register_command(cli, c, "tunnels", cmd_show_tunnels, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show a list of tunnels or details for a single tunnel"); + cli_register_command(cli, c, "users", cmd_show_users, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show a list of all connected users or details of selected user"); + cli_register_command(cli, c, "version", cmd_show_version, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show currently running software version"); + cli_register_command(cli, c, "access-list", cmd_show_access_list, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show named access-list"); + + c2 = cli_register_command(cli, c, "histogram", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); + cli_register_command(cli, c2, "idle", cmd_show_hist_idle, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show histogram of session idle times"); + cli_register_command(cli, c2, "open", cmd_show_hist_open, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show histogram of session durations"); + +#ifdef STATISTICS + cli_register_command(cli, c, "counters", cmd_show_counters, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Display all the internal counters and running totals"); + + c = cli_register_command(cli, NULL, "clear", NULL, PRIVILEGE_PRIVILEGED, MODE_EXEC, NULL); + cli_register_command(cli, c, "counters", cmd_clear_counters, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Clear internal counters"); +#endif + + cli_register_command(cli, NULL, "uptime", cmd_uptime, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show uptime and bandwidth utilisation"); + + c = cli_register_command(cli, NULL, "write", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); + cli_register_command(cli, c, "memory", cmd_write_memory, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Save the running config to flash"); + cli_register_command(cli, c, "terminal", cmd_show_run, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Show the running config"); + + cli_register_command(cli, NULL, "snoop", cmd_snoop, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Enable interception of a session"); + cli_register_command(cli, NULL, "throttle", cmd_throttle, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Enable throttling of a session"); + cli_register_command(cli, NULL, "filter", cmd_filter, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Add filtering to a session"); + cli_register_command(cli, NULL, "debug", cmd_debug, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Set the level of logging that is shown on the console"); + +#ifdef BGP + c = cli_register_command(cli, NULL, "suspend", NULL, PRIVILEGE_PRIVILEGED, MODE_EXEC, NULL); + cli_register_command(cli, c, "bgp", cmd_suspend_bgp, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Withdraw routes from BGP neighbour"); +#endif /* BGP */ + + c = cli_register_command(cli, NULL, "no", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL); + cli_register_command(cli, c, "snoop", cmd_no_snoop, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Disable interception of a session"); + cli_register_command(cli, c, "throttle", cmd_no_throttle, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Disable throttling of a session"); + cli_register_command(cli, c, "filter", cmd_no_filter, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Remove filtering from a session"); + cli_register_command(cli, c, "debug", cmd_no_debug, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, "Turn off logging of a certain level of debugging"); + +#ifdef BGP + c2 = cli_register_command(cli, c, "suspend", NULL, PRIVILEGE_PRIVILEGED, MODE_EXEC, NULL); + cli_register_command(cli, c2, "bgp", cmd_no_suspend_bgp, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Advertise routes to BGP neighbour"); + + c = cli_register_command(cli, NULL, "restart", NULL, PRIVILEGE_PRIVILEGED, MODE_EXEC, NULL); + cli_register_command(cli, c, "bgp", cmd_restart_bgp, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Restart BGP"); + + c = cli_register_command(cli, NULL, "router", NULL, PRIVILEGE_PRIVILEGED, MODE_CONFIG, NULL); + cli_register_command(cli, c, "bgp", cmd_router_bgp, PRIVILEGE_PRIVILEGED, MODE_CONFIG, "Configure BGP"); + + cli_register_command(cli, NULL, "neighbour", cmd_router_bgp_neighbour, PRIVILEGE_PRIVILEGED, MODE_CONFIG_BGP, "Configure BGP neighbour"); + + c = cli_register_command(cli, NULL, "no", NULL, PRIVILEGE_PRIVILEGED, MODE_CONFIG_BGP, NULL); + cli_register_command(cli, c, "neighbour", cmd_router_bgp_no_neighbour, PRIVILEGE_PRIVILEGED, MODE_CONFIG_BGP, "Remove BGP neighbour"); +#endif /* BGP */ + + c = cli_register_command(cli, NULL, "drop", NULL, PRIVILEGE_PRIVILEGED, MODE_EXEC, NULL); + cli_register_command(cli, c, "user", cmd_drop_user, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Disconnect a user"); + cli_register_command(cli, c, "tunnel", cmd_drop_tunnel, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Disconnect a tunnel and all sessions on that tunnel"); + cli_register_command(cli, c, "session", cmd_drop_session, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Disconnect a session"); + + c = cli_register_command(cli, NULL, "load", NULL, PRIVILEGE_PRIVILEGED, MODE_CONFIG, NULL); + cli_register_command(cli, c, "plugin", cmd_load_plugin, PRIVILEGE_PRIVILEGED, MODE_CONFIG, "Load a plugin"); + + c = cli_register_command(cli, NULL, "remove", NULL, PRIVILEGE_PRIVILEGED, MODE_CONFIG, NULL); + cli_register_command(cli, c, "plugin", cmd_remove_plugin, PRIVILEGE_PRIVILEGED, MODE_CONFIG, "Remove a plugin"); + + cli_register_command(cli, NULL, "set", cmd_set, PRIVILEGE_PRIVILEGED, MODE_CONFIG, "Set a configuration variable"); + + c = cli_register_command(cli, NULL, "ip", NULL, PRIVILEGE_PRIVILEGED, MODE_CONFIG, NULL); + cli_register_command(cli, c, "access-list", cmd_ip_access_list, PRIVILEGE_PRIVILEGED, MODE_CONFIG, "Add named access-list"); + + cli_register_command(cli, NULL, "permit", cmd_ip_access_list_rule, PRIVILEGE_PRIVILEGED, MODE_CONFIG_NACL, "Permit rule"); + cli_register_command(cli, NULL, "deny", cmd_ip_access_list_rule, PRIVILEGE_PRIVILEGED, MODE_CONFIG_NACL, "Deny rule"); + + c = cli_register_command(cli, NULL, "no", NULL, PRIVILEGE_UNPRIVILEGED, MODE_CONFIG, NULL); + c2 = cli_register_command(cli, c, "ip", NULL, PRIVILEGE_PRIVILEGED, MODE_CONFIG, NULL); + cli_register_command(cli, c2, "access-list", cmd_no_ip_access_list, PRIVILEGE_PRIVILEGED, MODE_CONFIG, "Remove named access-list"); + + // Enable regular processing + cli_regular(cli, regular_stuff); + + if (!(f = fopen(CLIUSERS, "r"))) + { + LOG(0, 0, 0, "WARNING! No users specified. Command-line access is open to all\n"); + } + else + { + while (fgets(buf, 4096, f)) + { + char *p; + if (*buf == '#') continue; + if ((p = strchr(buf, '\r'))) *p = 0; + if ((p = strchr(buf, '\n'))) *p = 0; + if (!*buf) continue; + if (!(p = strchr((char *)buf, ':'))) continue; + *p++ = 0; + if (!strcmp(buf, "enable")) + { + cli_allow_enable(cli, p); + LOG(3, 0, 0, "Setting enable password\n"); + } + else + { + cli_allow_user(cli, buf, p); + LOG(3, 0, 0, "Allowing user %s to connect to the CLI\n", buf); + } + } + fclose(f); + } + + memset(&addr, 0, sizeof(addr)); + clifd = socket(PF_INET, SOCK_STREAM, 6); + setsockopt(clifd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + { + int flags; + // Set cli fd as non-blocking + flags = fcntl(clifd, F_GETFL, 0); + fcntl(clifd, F_SETFL, flags | O_NONBLOCK); + } + addr.sin_family = AF_INET; + addr.sin_port = htons(23); + if (bind(clifd, (void *) &addr, sizeof(addr)) < 0) + { + LOG(0, 0, 0, "Error listening on cli port 23: %s\n", strerror(errno)); + return; + } + listen(clifd, 10); +} + +void cli_do(int sockfd) +{ + int require_auth = 1; + struct sockaddr_in addr; + socklen_t l = sizeof(addr); + + if (fork_and_close()) return; + if (getpeername(sockfd, (struct sockaddr *) &addr, &l) == 0) + { + require_auth = addr.sin_addr.s_addr != inet_addr("127.0.0.1"); + LOG(require_auth ? 3 : 4, 0, 0, "Accepted connection to CLI from %s\n", + fmtaddr(addr.sin_addr.s_addr, 0)); + } + else + LOG(0, 0, 0, "getpeername() failed on cli socket. Requiring authentication: %s\n", strerror(errno)); + + if (require_auth) + { + LOG(3, 0, 0, "CLI is remote, requiring authentication\n"); + if (!cli->users) /* paranoia */ + { + LOG(0, 0, 0, "No users for remote authentication! Exiting CLI\n"); + exit(0); + } + } + else + { + /* no username/pass required */ + cli->users = 0; + } + +#ifdef RINGBUFFER + debug_rb_tail = ringbuffer->tail; +#endif + memset(&debug_flags, 0, sizeof(debug_flags)); + debug_flags.critical = 1; + + cli_loop(cli, sockfd); + + close(sockfd); + LOG(require_auth ? 3 : 4, 0, 0, "Closed CLI connection from %s\n", + fmtaddr(addr.sin_addr.s_addr, 0)); + + exit(0); +} + +static void cli_print_log(struct cli_def *cli, char *string) +{ + LOG(3, 0, 0, "%s\n", string); +} + +void cli_do_file(FILE *fh) +{ + LOG(3, 0, 0, "Reading configuration file\n"); + cli_print_callback(cli, cli_print_log); + cli_file(cli, fh, PRIVILEGE_PRIVILEGED, MODE_CONFIG); + cli_print_callback(cli, NULL); +} + +int cli_arg_help(struct cli_def *cli, int cr_ok, char *entry, ...) +{ + va_list ap; + char *desc; + char buf[16]; + char *p; + + va_start(ap, entry); + while (entry) + { + /* allow one %d */ + if ((p = strchr(entry, '%')) && !strchr(p+1, '%') && p[1] == 'd') + { + int v = va_arg(ap, int); + snprintf(buf, sizeof(buf), entry, v); + p = buf; + } + else + p = entry; + + desc = va_arg(ap, char *); + if (desc && *desc) + cli_error(cli, " %-20s %s", p, desc); + else + cli_error(cli, " %s", p); + + entry = desc ? va_arg(ap, char *) : 0; + } + + va_end(ap); + if (cr_ok) + cli_error(cli, " "); + + return CLI_OK; +} + +static int cmd_show_session(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, 1, + "<1-%d>", MAXSESSION-1, "Show specific session by id", + NULL); + + time(&time_now); + if (argc > 0) + { + // Show individual session + for (i = 0; i < argc; i++) + { + unsigned int s, b_in, b_out; + s = atoi(argv[i]); + if (s <= 0 || s >= MAXSESSION) + { + cli_print(cli, "Invalid session id \"%s\"", argv[i]); + continue; + } + cli_print(cli, "\r\nSession %d:", s); + cli_print(cli, "\tUser:\t\t%s", session[s].user[0] ? session[s].user : "none"); + cli_print(cli, "\tCalling Num:\t%s", session[s].calling); + cli_print(cli, "\tCalled Num:\t%s", session[s].called); + cli_print(cli, "\tTunnel ID:\t%d", session[s].tunnel); + cli_print(cli, "\tPPP Phase:\t%s", ppp_phase(session[s].ppp.phase)); + switch (session[s].ppp.phase) + { + case Establish: + cli_print(cli, "\t LCP state:\t%s", ppp_state(session[s].ppp.lcp)); + break; + + case Authenticate: + case Network: + cli_print(cli, "\t IPCP state:\t%s", ppp_state(session[s].ppp.ipcp)); + cli_print(cli, "\t IPV6CP state:\t%s", ppp_state(session[s].ppp.ipv6cp)); + cli_print(cli, "\t CCP state:\t%s", ppp_state(session[s].ppp.ccp)); + } + cli_print(cli, "\tIP address:\t%s", fmtaddr(htonl(session[s].ip), 0)); + cli_print(cli, "\tUnique SID:\t%u", session[s].unique_id); + cli_print(cli, "\tOpened:\t\t%u seconds", session[s].opened ? abs(time_now - session[s].opened) : 0); + cli_print(cli, "\tIdle time:\t%u seconds", session[s].last_packet ? abs(time_now - session[s].last_packet) : 0); + cli_print(cli, "\tBytes In/Out:\t%u/%u", session[s].cout, session[s].cin); + cli_print(cli, "\tPkts In/Out:\t%u/%u", session[s].pout, session[s].pin); + cli_print(cli, "\tMRU:\t\t%d", session[s].mru); + cli_print(cli, "\tRx Speed:\t%u", session[s].rx_connect_speed); + cli_print(cli, "\tTx Speed:\t%u", session[s].tx_connect_speed); + if (session[s].filter_in && session[s].filter_in <= MAXFILTER) + cli_print(cli, "\tFilter in:\t%u (%s)", session[s].filter_in, ip_filters[session[s].filter_in - 1].name); + if (session[s].filter_out && session[s].filter_out <= MAXFILTER) + cli_print(cli, "\tFilter out:\t%u (%s)", session[s].filter_out, ip_filters[session[s].filter_out - 1].name); + if (session[s].snoop_ip && session[s].snoop_port) + cli_print(cli, "\tIntercepted:\t%s:%d", fmtaddr(session[s].snoop_ip, 0), session[s] .snoop_port); + else + cli_print(cli, "\tIntercepted:\tno"); + + cli_print(cli, "\tWalled Garden:\t%s", session[s].walled_garden ? "YES" : "no"); + { + int t = (session[s].throttle_in || session[s].throttle_out); + cli_print(cli, "\tThrottled:\t%s%s%.0d%s%s%.0d%s%s", + t ? "YES" : "no", t ? " (" : "", + session[s].throttle_in, session[s].throttle_in ? "kbps" : t ? "-" : "", + t ? "/" : "", + session[s].throttle_out, session[s].throttle_out ? "kbps" : t ? "-" : "", + t ? ")" : ""); + } + + b_in = session[s].tbf_in; + b_out = session[s].tbf_out; + if (b_in || b_out) + cli_print(cli, "\t\t\t%5s %6s %6s | %7s %7s %8s %8s %8s %8s", + "Rate", "Credit", "Queued", "ByteIn", "PackIn", + "ByteSent", "PackSent", "PackDrop", "PackDelay"); + + if (b_in) + cli_print(cli, "\tTBFI#%d%1s%s\t%5d %6d %6d | %7d %7d %8d %8d %8d %8d", + b_in, + (filter_list[b_in].next ? "*" : " "), + (b_in < 100 ? "\t" : ""), + filter_list[b_in].rate * 8, + filter_list[b_in].credit, + filter_list[b_in].queued, + filter_list[b_in].b_queued, + filter_list[b_in].p_queued, + filter_list[b_in].b_sent, + filter_list[b_in].p_sent, + filter_list[b_in].p_dropped, + filter_list[b_in].p_delayed); + + if (b_out) + cli_print(cli, "\tTBFO#%d%1s%s\t%5d %6d %6d | %7d %7d %8d %8d %8d %8d", + b_out, + (filter_list[b_out].next ? "*" : " "), + (b_out < 100 ? "\t" : ""), + filter_list[b_out].rate * 8, + filter_list[b_out].credit, + filter_list[b_out].queued, + filter_list[b_out].b_queued, + filter_list[b_out].p_queued, + filter_list[b_out].b_sent, + filter_list[b_out].p_sent, + filter_list[b_out].p_dropped, + filter_list[b_out].p_delayed); + + } + return CLI_OK; + } + + // Show Summary + cli_print(cli, "%5s %4s %-32s %-15s %s %s %s %s %10s %10s %10s %4s %-15s %s", + "SID", + "TID", + "Username", + "IP", + "I", + "T", + "G", + "6", + "opened", + "downloaded", + "uploaded", + "idle", + "LAC", + "CLI"); + + for (i = 1; i < MAXSESSION; i++) + { + if (!session[i].opened) continue; + cli_print(cli, "%5d %4d %-32s %-15s %s %s %s %s %10u %10lu %10lu %4u %-15s %s", + i, + session[i].tunnel, + session[i].user[0] ? session[i].user : "*", + fmtaddr(htonl(session[i].ip), 0), + (session[i].snoop_ip && session[i].snoop_port) ? "Y" : "N", + (session[i].throttle_in || session[i].throttle_out) ? "Y" : "N", + (session[i].walled_garden) ? "Y" : "N", + (session[i].ppp.ipv6cp == Opened) ? "Y" : "N", + abs(time_now - (unsigned long)session[i].opened), + (unsigned long)session[i].cout, + (unsigned long)session[i].cin, + abs(time_now - (session[i].last_packet ? session[i].last_packet : time_now)), + fmtaddr(htonl(tunnel[ session[i].tunnel ].ip), 1), + session[i].calling[0] ? session[i].calling : "*"); + } + return CLI_OK; +} + +static int cmd_show_tunnels(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i, x, show_all = 0; + char *states[] = { + "Free", + "Open", + "Closing", + "Opening", + }; + + if (CLI_HELP_REQUESTED) + { + if (argc > 1) + return cli_arg_help(cli, 1, + "<1-%d>", MAXTUNNEL-1, "Show specific tunnel by id", + NULL); + + return cli_arg_help(cli, 1, + "all", "Show all tunnels, including unused", + "<1-%d>", MAXTUNNEL-1, "Show specific tunnel by id", + NULL); + } + + time(&time_now); + if (argc > 0) + { + if (strcmp(argv[0], "all") == 0) + { + show_all = 1; + } + else + { + // Show individual tunnel + for (i = 0; i < argc; i++) + { + char s[65535] = {0}; + unsigned int t; + t = atoi(argv[i]); + if (t <= 0 || t >= MAXTUNNEL) + { + cli_print(cli, "Invalid tunnel id \"%s\"", argv[i]); + continue; + } + cli_print(cli, "\r\nTunnel %d:", t); + cli_print(cli, "\tState:\t\t%s", states[tunnel[t].state]); + cli_print(cli, "\tHostname:\t%s", tunnel[t].hostname[0] ? tunnel[t].hostname : "(none)"); + cli_print(cli, "\tRemote IP:\t%s", fmtaddr(htonl(tunnel[t].ip), 0)); + cli_print(cli, "\tRemote Port:\t%d", tunnel[t].port); + cli_print(cli, "\tRx Window:\t%u", tunnel[t].window); + cli_print(cli, "\tNext Recv:\t%u", tunnel[t].nr); + cli_print(cli, "\tNext Send:\t%u", tunnel[t].ns); + cli_print(cli, "\tQueue Len:\t%u", tunnel[t].controlc); + cli_print(cli, "\tLast Packet Age:%u", (unsigned)(time_now - tunnel[t].last)); + + for (x = 0; x < MAXSESSION; x++) + if (session[x].tunnel == t && session[x].opened && !session[x].die) + sprintf(s, "%s%u ", s, x); + + cli_print(cli, "\tSessions:\t%s", s); + } + return CLI_OK; + } + } + + // Show tunnel summary + cli_print(cli, "%4s %20s %20s %6s %s", + "TID", + "Hostname", + "IP", + "State", + "Sessions"); + + for (i = 1; i < MAXTUNNEL; i++) + { + int sessions = 0; + if (!show_all && (!tunnel[i].ip || tunnel[i].die)) continue; + + for (x = 0; x < MAXSESSION; x++) if (session[x].tunnel == i && session[x].opened && !session[x].die) sessions++; + cli_print(cli, "%4d %20s %20s %6s %6d", + i, + *tunnel[i].hostname ? tunnel[i].hostname : "(null)", + fmtaddr(htonl(tunnel[i].ip), 0), + states[tunnel[i].state], + sessions); + } + + return CLI_OK; +} + +static int cmd_show_users(struct cli_def *cli, char *command, char **argv, int argc) +{ + char sid[32][8]; + char *sargv[32]; + int sargc = 0; + int i; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, 1, + "USER", "Show details for specific username", + NULL); + + for (i = 0; i < MAXSESSION; i++) + { + if (!session[i].opened) continue; + if (!session[i].user[0]) continue; + if (argc > 0) + { + int j; + for (j = 0; j < argc && sargc < 32; j++) + { + if (strcmp(argv[j], session[i].user) == 0) + { + snprintf(sid[sargc], sizeof(sid[0]), "%d", i); + sargv[sargc] = sid[sargc]; + sargc++; + } + } + + continue; + } + + cli_print(cli, "%s", session[i].user); + } + + if (sargc > 0) + return cmd_show_session(cli, "users", sargv, sargc); + + return CLI_OK; +} + +#ifdef STATISTICS +static int cmd_show_counters(struct cli_def *cli, char *command, char **argv, int argc) +{ + if (CLI_HELP_REQUESTED) + return CLI_HELP_NO_ARGS; + + cli_print(cli, "%-10s %10s %10s %10s %10s", "Ethernet", "Bytes", "Packets", "Errors", "Dropped"); + cli_print(cli, "%-10s %10u %10u %10u %10u", "RX", + GET_STAT(tun_rx_bytes), + GET_STAT(tun_rx_packets), + GET_STAT(tun_rx_errors), + GET_STAT(tun_rx_dropped)); + cli_print(cli, "%-10s %10u %10u %10u", "TX", + GET_STAT(tun_tx_bytes), + GET_STAT(tun_tx_packets), + GET_STAT(tun_tx_errors)); + cli_print(cli, ""); + + cli_print(cli, "%-10s %10s %10s %10s %10s", "Tunnel", "Bytes", "Packets", "Errors", "Retries"); + cli_print(cli, "%-10s %10u %10u %10u", "RX", + GET_STAT(tunnel_rx_bytes), + GET_STAT(tunnel_rx_packets), + GET_STAT(tunnel_rx_errors)); + cli_print(cli, "%-10s %10u %10u %10u %10u", "TX", + GET_STAT(tunnel_tx_bytes), + GET_STAT(tunnel_tx_packets), + GET_STAT(tunnel_tx_errors), + GET_STAT(tunnel_retries)); + cli_print(cli, ""); + + cli_print(cli, "%-30s%-10s", "Counter", "Value"); + cli_print(cli, "-----------------------------------------"); + cli_print(cli, "%-30s%u", "radius_retries", GET_STAT(radius_retries)); + cli_print(cli, "%-30s%u", "arp_sent", GET_STAT(arp_sent)); + cli_print(cli, "%-30s%u", "packets_snooped", GET_STAT(packets_snooped)); + cli_print(cli, "%-30s%u", "tunnel_created", GET_STAT(tunnel_created)); + cli_print(cli, "%-30s%u", "session_created", GET_STAT(session_created)); + cli_print(cli, "%-30s%u", "tunnel_timeout", GET_STAT(tunnel_timeout)); + cli_print(cli, "%-30s%u", "session_timeout", GET_STAT(session_timeout)); + cli_print(cli, "%-30s%u", "radius_timeout", GET_STAT(radius_timeout)); + cli_print(cli, "%-30s%u", "radius_overflow", GET_STAT(radius_overflow)); + cli_print(cli, "%-30s%u", "tunnel_overflow", GET_STAT(tunnel_overflow)); + cli_print(cli, "%-30s%u", "session_overflow", GET_STAT(session_overflow)); + cli_print(cli, "%-30s%u", "ip_allocated", GET_STAT(ip_allocated)); + cli_print(cli, "%-30s%u", "ip_freed", GET_STAT(ip_freed)); + cli_print(cli, "%-30s%u", "cluster_forwarded", GET_STAT(c_forwarded)); + cli_print(cli, "%-30s%u", "recv_forward", GET_STAT(recv_forward)); + cli_print(cli, "%-30s%u", "select_called", GET_STAT(select_called)); + cli_print(cli, "%-30s%u", "multi_read_used", GET_STAT(multi_read_used)); + cli_print(cli, "%-30s%u", "multi_read_exceeded", GET_STAT(multi_read_exceeded)); + + +#ifdef STAT_CALLS + cli_print(cli, "\n%-30s%-10s", "Counter", "Value"); + cli_print(cli, "-----------------------------------------"); + cli_print(cli, "%-30s%u", "call_processtun", GET_STAT(call_processtun)); + cli_print(cli, "%-30s%u", "call_processipout", GET_STAT(call_processipout)); + cli_print(cli, "%-30s%u", "call_processipv6out", GET_STAT(call_processipv6out)); + cli_print(cli, "%-30s%u", "call_processudp", GET_STAT(call_processudp)); + cli_print(cli, "%-30s%u", "call_processpap", GET_STAT(call_processpap)); + cli_print(cli, "%-30s%u", "call_processchap", GET_STAT(call_processchap)); + cli_print(cli, "%-30s%u", "call_processlcp", GET_STAT(call_processlcp)); + cli_print(cli, "%-30s%u", "call_processipcp", GET_STAT(call_processipcp)); + cli_print(cli, "%-30s%u", "call_processipv6cp", GET_STAT(call_processipv6cp)); + cli_print(cli, "%-30s%u", "call_processipin", GET_STAT(call_processipin)); + cli_print(cli, "%-30s%u", "call_processipv6in", GET_STAT(call_processipv6in)); + cli_print(cli, "%-30s%u", "call_processccp", GET_STAT(call_processccp)); + cli_print(cli, "%-30s%u", "call_processrad", GET_STAT(call_processrad)); + cli_print(cli, "%-30s%u", "call_sendarp", GET_STAT(call_sendarp)); + cli_print(cli, "%-30s%u", "call_sendipcp", GET_STAT(call_sendipcp)); + cli_print(cli, "%-30s%u", "call_sendchap", GET_STAT(call_sendchap)); + cli_print(cli, "%-30s%u", "call_sessionbyip", GET_STAT(call_sessionbyip)); + cli_print(cli, "%-30s%u", "call_sessionbyipv6", GET_STAT(call_sessionbyipv6)); + cli_print(cli, "%-30s%u", "call_sessionbyuser", GET_STAT(call_sessionbyuser)); + cli_print(cli, "%-30s%u", "call_tunnelsend", GET_STAT(call_tunnelsend)); + cli_print(cli, "%-30s%u", "call_tunnelkill", GET_STAT(call_tunnelkill)); + cli_print(cli, "%-30s%u", "call_tunnelshutdown", GET_STAT(call_tunnelshutdown)); + cli_print(cli, "%-30s%u", "call_sessionkill", GET_STAT(call_sessionkill)); + cli_print(cli, "%-30s%u", "call_sessionshutdown", GET_STAT(call_sessionshutdown)); + cli_print(cli, "%-30s%u", "call_sessionsetup", GET_STAT(call_sessionsetup)); + cli_print(cli, "%-30s%u", "call_assign_ip_address", GET_STAT(call_assign_ip_address)); + cli_print(cli, "%-30s%u", "call_free_ip_address", GET_STAT(call_free_ip_address)); + cli_print(cli, "%-30s%u", "call_dump_acct_info", GET_STAT(call_dump_acct_info)); + cli_print(cli, "%-30s%u", "call_radiussend", GET_STAT(call_radiussend)); + cli_print(cli, "%-30s%u", "call_radiusretry", GET_STAT(call_radiusretry)); + cli_print(cli, "%-30s%u", "call_random_data", GET_STAT(call_random_data)); +#endif /* STAT_CALLS */ + + { + time_t l = GET_STAT(last_reset); + char *t = ctime(&l); + char *p = strchr(t, '\n'); + if (p) *p = 0; + + cli_print(cli, ""); + cli_print(cli, "Last counter reset %s", t); + } + + return CLI_OK; +} + +static int cmd_clear_counters(struct cli_def *cli, char *command, char **argv, int argc) +{ + if (CLI_HELP_REQUESTED) + return CLI_HELP_NO_ARGS; + + memset(_statistics, 0, sizeof(struct Tstats)); + SET_STAT(last_reset, time(NULL)); + + cli_print(cli, "Counters cleared"); + return CLI_OK; +} +#endif /* STATISTICS */ + +static int cmd_show_version(struct cli_def *cli, char *command, char **argv, int argc) +{ + int tag = 0; + int file = 0; + int i = 0; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, 1, + "tag", "Include CVS release tag", + "file", "Include file versions", + NULL); + + for (i = 0; i < argc; i++) + if (!strcmp(argv[i], "tag")) + tag++; + else if (!strcmp(argv[i], "file")) + file++; + + cli_print(cli, "L2TPNS %s", VERSION); + if (tag) + { + char const *p = strchr(cvs_name, ':'); + char const *e; + if (p) + { + p++; + while (isspace(*p)) + p++; + } + + if (!p || *p == '$') + p = "HEAD"; + + e = strpbrk(p, " \t$"); + cli_print(cli, "Tag: %.*s", (int) (e ? e - p + 1 : strlen(p)), p); + } + + if (file) + { + extern linked_list *loaded_plugins; + void *p; + + cli_print(cli, "Files:"); + cli_print(cli, " %s", cvs_id_arp); +#ifdef BGP + cli_print(cli, " %s", cvs_id_bgp); +#endif /* BGP */ + cli_print(cli, " %s", cvs_id_cli); + cli_print(cli, " %s", cvs_id_cluster); + cli_print(cli, " %s", cvs_id_constants); + cli_print(cli, " %s", cvs_id_control); + cli_print(cli, " %s", cvs_id_icmp); + cli_print(cli, " %s", cvs_id_l2tpns); + cli_print(cli, " %s", cvs_id_ll); + cli_print(cli, " %s", cvs_id_ppp); + cli_print(cli, " %s", cvs_id_radius); + cli_print(cli, " %s", cvs_id_tbf); + cli_print(cli, " %s", cvs_id_util); + + ll_reset(loaded_plugins); + while ((p = ll_next(loaded_plugins))) + { + char const **id = dlsym(p, "cvs_id"); + if (id) + cli_print(cli, " %s", *id); + } + } + + return CLI_OK; +} + +static int cmd_show_pool(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + int used = 0, free = 0, show_all = 0; + + if (!config->cluster_iam_master) + { + cli_print(cli, "Can't do this on a slave. Do it on %s", + fmtaddr(config->cluster_master_address, 0)); + + return CLI_OK; + } + + if (CLI_HELP_REQUESTED) + { + if (argc > 1) + return cli_arg_help(cli, 1, NULL); + + return cli_arg_help(cli, 1, + "all", "Show all pool addresses, including unused", + NULL); + } + + if (argc > 0 && strcmp(argv[0], "all") == 0) + show_all = 1; + + time(&time_now); + cli_print(cli, "%-15s %4s %8s %s", "IP Address", "Used", "Session", "User"); + for (i = 0; i < MAXIPPOOL; i++) + { + if (!ip_address_pool[i].address) continue; + if (ip_address_pool[i].assigned) + { + cli_print(cli, "%-15s\tY %8d %s", + fmtaddr(htonl(ip_address_pool[i].address), 0), + ip_address_pool[i].session, + session[ip_address_pool[i].session].user); + + used++; + } + else + { + if (ip_address_pool[i].last) + cli_print(cli, "%-15s\tN %8s [%s] %ds", + fmtaddr(htonl(ip_address_pool[i].address), 0), "", + ip_address_pool[i].user, (int) time_now - ip_address_pool[i].last); + + else if (show_all) + cli_print(cli, "%-15s\tN", fmtaddr(htonl(ip_address_pool[i].address), 0)); + + free++; + } + } + + if (!show_all) + cli_print(cli, "(Not displaying unused addresses)"); + + cli_print(cli, "\r\nFree: %d\r\nUsed: %d", free, used); + return CLI_OK; +} + +static FILE *save_config_fh = 0; +static void print_save_config(struct cli_def *cli, char *string) +{ + if (save_config_fh) + fprintf(save_config_fh, "%s\n", string); +} + +static int cmd_write_memory(struct cli_def *cli, char *command, char **argv, int argc) +{ + if (CLI_HELP_REQUESTED) + return CLI_HELP_NO_ARGS; + + if ((save_config_fh = fopen(config->config_file, "w"))) + { + cli_print(cli, "Writing configuration"); + cli_print_callback(cli, print_save_config); + cmd_show_run(cli, command, argv, argc); + cli_print_callback(cli, NULL); + fclose(save_config_fh); + save_config_fh = 0; + } + else + { + cli_error(cli, "Error writing configuration: %s", strerror(errno)); + } + return CLI_OK; +} + +static char const *show_access_list_rule(int extended, ip_filter_rulet *rule); + +static int cmd_show_run(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + char ipv6addr[INET6_ADDRSTRLEN]; + + if (CLI_HELP_REQUESTED) + return CLI_HELP_NO_ARGS; + + cli_print(cli, "# Current configuration:"); + + for (i = 0; config_values[i].key; i++) + { + void *value = ((void *)config) + config_values[i].offset; + if (config_values[i].type == STRING) + cli_print(cli, "set %s \"%.*s\"", config_values[i].key, config_values[i].size, (char *) value); + else if (config_values[i].type == IPv4) + cli_print(cli, "set %s %s", config_values[i].key, fmtaddr(*(in_addr_t *) value, 0)); + else if (config_values[i].type == IPv6) + cli_print(cli, "set %s %s", config_values[i].key, inet_ntop(AF_INET6, value, ipv6addr, INET6_ADDRSTRLEN)); + else if (config_values[i].type == SHORT) + cli_print(cli, "set %s %hu", config_values[i].key, *(short *) value); + else if (config_values[i].type == BOOL) + cli_print(cli, "set %s %s", config_values[i].key, (*(int *) value) ? "yes" : "no"); + else if (config_values[i].type == INT) + cli_print(cli, "set %s %d", config_values[i].key, *(int *) value); + else if (config_values[i].type == UNSIGNED_LONG) + cli_print(cli, "set %s %lu", config_values[i].key, *(unsigned long *) value); + } + + cli_print(cli, "# Plugins"); + for (i = 0; i < MAXPLUGINS; i++) + { + if (*config->plugins[i]) + { + cli_print(cli, "load plugin \"%s\"", config->plugins[i]); + } + } + +#ifdef BGP + if (config->as_number) + { + int k; + int h; + + cli_print(cli, "# BGP"); + cli_print(cli, "router bgp %u", config->as_number); + for (i = 0; i < BGP_NUM_PEERS; i++) + { + if (!config->neighbour[i].name[0]) + continue; + + cli_print(cli, " neighbour %s remote-as %u", config->neighbour[i].name, config->neighbour[i].as); + + k = config->neighbour[i].keepalive; + h = config->neighbour[i].hold; + + if (k == -1) + { + if (h == -1) + continue; + + k = BGP_KEEPALIVE_TIME; + } + + if (h == -1) + h = BGP_HOLD_TIME; + + cli_print(cli, " neighbour %s timers %d %d", config->neighbour[i].name, k, h); + } + } +#endif + + cli_print(cli, "# Filters"); + for (i = 0; i < MAXFILTER; i++) + { + ip_filter_rulet *rules; + if (!*ip_filters[i].name) + continue; + + cli_print(cli, "ip access-list %s %s", + ip_filters[i].extended ? "extended" : "standard", + ip_filters[i].name); + + rules = ip_filters[i].rules; + while (rules->action) + cli_print(cli, "%s", show_access_list_rule(ip_filters[i].extended, rules++)); + } + + cli_print(cli, "# end"); + return CLI_OK; +} + +static int cmd_show_radius(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i, free = 0, used = 0, show_all = 0; + char *states[] = { + "NULL", + "CHAP", + "AUTH", + "IPCP", + "START", + "STOP", + "INTRM", + "WAIT", + }; + + if (CLI_HELP_REQUESTED) + { + if (argc > 1) + return cli_arg_help(cli, 1, NULL); + + return cli_arg_help(cli, 1, + "all", "Show all RADIUS sessions, including unused", + NULL); + } + + cli_print(cli, "%6s%7s%5s%6s%9s%9s%4s", "ID", "Radius", "Sock", "State", "Session", "Retry", "Try"); + + time(&time_now); + + if (argc > 0 && strcmp(argv[0], "all") == 0) + show_all = 1; + + for (i = 1; i < MAXRADIUS; i++) + { + if (radius[i].state == RADIUSNULL) + free++; + else + used++; + + if (!show_all && radius[i].state == RADIUSNULL) continue; + + cli_print(cli, "%6d%7d%5d%6s%9d%9u%4d", + i, + i >> RADIUS_SHIFT, + i & RADIUS_MASK, + states[radius[i].state], + radius[i].session, + radius[i].retry, + radius[i].try); + } + + cli_print(cli, "\r\nFree: %d\r\nUsed: %d", free, used); + + return CLI_OK; +} + +static int cmd_show_plugins(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + + if (CLI_HELP_REQUESTED) + return CLI_HELP_NO_ARGS; + + cli_print(cli, "Plugins currently loaded:"); + for (i = 0; i < MAXPLUGINS; i++) + if (*config->plugins[i]) + cli_print(cli, " %s", config->plugins[i]); + + return CLI_OK; +} + +static int cmd_show_throttle(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + + if (CLI_HELP_REQUESTED) + return CLI_HELP_NO_ARGS; + + cli_print(cli, "%5s %4s %-32s %7s %6s %6s %6s", + "SID", + "TID", + "Username", + "Rate In", + "Out", + "TBFI", + "TBFO"); + + for (i = 0; i < MAXSESSION; i++) + { + if (session[i].throttle_in || session[i].throttle_out) + cli_print(cli, "%5d %4d %-32s %6d %6d %6d %6d", + i, + session[i].tunnel, + session[i].user, + session[i].throttle_in, + session[i].throttle_out, + session[i].tbf_in, + session[i].tbf_out); + } + + return CLI_OK; +} + +static int cmd_show_banana(struct cli_def *cli, char *command, char **argv, int argc) +{ + if (CLI_HELP_REQUESTED) + return CLI_HELP_NO_ARGS; + + cli_print(cli, " _\n" + "//\\\n" + "V \\\n" + " \\ \\_\n" + " \\,'.`-.\n" + " |\\ `. `.\n" + " ( \\ `. `-. _,.-:\\\n" + " \\ \\ `. `-._ __..--' ,-';/\n" + " \\ `. `-. `-..___..---' _.--' ,'/\n" + " `. `. `-._ __..--' ,' /\n" + " `. `-_ ``--..'' _.-' ,'\n" + " `-_ `-.___ __,--' ,'\n" + " `-.__ `----\"\"\" __.-'\n" + "hh `--..____..--'"); + + return CLI_OK; +} + +static int cmd_drop_user(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + sessionidt s; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, argc > 1, + "USER", "Username of session to drop", NULL); + + if (!config->cluster_iam_master) + { + cli_error(cli, "Can't do this on a slave. Do it on %s", + fmtaddr(config->cluster_master_address, 0)); + + return CLI_OK; + } + + if (!argc) + { + cli_error(cli, "Specify a user to drop"); + return CLI_OK; + } + + for (i = 0; i < argc; i++) + { + if (!(s = sessionbyuser(argv[i]))) + { + cli_error(cli, "User %s is not connected", argv[i]); + continue; + } + + if (session[s].ip && session[s].opened && !session[s].die) + { + cli_print(cli, "Dropping user %s", session[s].user); + cli_session_actions[s].action |= CLI_SESS_KILL; + } + } + + return CLI_OK; +} + +static int cmd_drop_tunnel(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + tunnelidt t; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, argc > 1, + "<1-%d>", MAXTUNNEL-1, "Tunnel id to drop", NULL); + + if (!config->cluster_iam_master) + { + cli_error(cli, "Can't do this on a slave. Do it on %s", + fmtaddr(config->cluster_master_address, 0)); + + return CLI_OK; + } + + if (!argc) + { + cli_error(cli, "Specify a tunnel to drop"); + return CLI_OK; + } + + for (i = 0; i < argc; i++) + { + if ((t = atol(argv[i])) <= 0 || (t >= MAXTUNNEL)) + { + cli_error(cli, "Invalid tunnel ID (1-%d)", MAXTUNNEL-1); + continue; + } + + if (!tunnel[t].ip) + { + cli_error(cli, "Tunnel %d is not connected", t); + continue; + } + + if (tunnel[t].die) + { + cli_error(cli, "Tunnel %d is already being shut down", t); + continue; + } + + cli_print(cli, "Tunnel %d shut down (%s)", t, tunnel[t].hostname); + cli_tunnel_actions[t].action |= CLI_TUN_KILL; + } + + return CLI_OK; +} + +static int cmd_drop_session(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + sessionidt s; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, argc > 1, + "<1-%d>", MAXSESSION-1, "Session id to drop", NULL); + + if (!config->cluster_iam_master) + { + cli_error(cli, "Can't do this on a slave. Do it on %s", + fmtaddr(config->cluster_master_address, 0)); + + return CLI_OK; + } + + if (!argc) + { + cli_error(cli, "Specify a session id to drop"); + return CLI_OK; + } + + for (i = 0; i < argc; i++) + { + if ((s = atol(argv[i])) <= 0 || (s > MAXSESSION)) + { + cli_error(cli, "Invalid session ID (1-%d)", MAXSESSION-1); + continue; + } + + if (session[s].ip && session[s].opened && !session[s].die) + { + cli_print(cli, "Dropping session %d", s); + cli_session_actions[s].action |= CLI_SESS_KILL; + } + else + { + cli_error(cli, "Session %d is not active.", s); + } + } + + return CLI_OK; +} + +static int cmd_snoop(struct cli_def *cli, char *command, char **argv, int argc) +{ + in_addr_t ip; + uint16_t port; + sessionidt s; + + if (CLI_HELP_REQUESTED) + { + switch (argc) + { + case 1: + return cli_arg_help(cli, 0, + "USER", "Username of session to snoop", NULL); + + case 2: + return cli_arg_help(cli, 0, + "A.B.C.D", "IP address of snoop destination", NULL); + + case 3: + return cli_arg_help(cli, 0, + "N", "Port of snoop destination", NULL); + + case 4: + if (!argv[3][1]) + return cli_arg_help(cli, 1, NULL); + + default: + return CLI_OK; + } + } + + if (!config->cluster_iam_master) + { + cli_error(cli, "Can't do this on a slave. Do it on %s", + fmtaddr(config->cluster_master_address, 0)); + + return CLI_OK; + } + + if (argc < 3) + { + cli_error(cli, "Specify username, ip and port"); + return CLI_OK; + } + + if (!(s = sessionbyuser(argv[0]))) + { + cli_error(cli, "User %s is not connected", argv[0]); + return CLI_OK; + } + + ip = inet_addr(argv[1]); + if (!ip || ip == INADDR_NONE) + { + cli_error(cli, "Cannot parse IP \"%s\"", argv[1]); + return CLI_OK; + } + + port = atoi(argv[2]); + if (!port) + { + cli_error(cli, "Invalid port %s", argv[2]); + return CLI_OK; + } + + cli_print(cli, "Snooping user %s to %s:%d", argv[0], fmtaddr(ip, 0), port); + cli_session_actions[s].snoop_ip = ip; + cli_session_actions[s].snoop_port = port; + cli_session_actions[s].action |= CLI_SESS_SNOOP; + + return CLI_OK; +} + +static int cmd_no_snoop(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + sessionidt s; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, argc > 1, + "USER", "Username of session to unsnoop", NULL); + + if (!config->cluster_iam_master) + { + cli_error(cli, "Can't do this on a slave. Do it on %s", + fmtaddr(config->cluster_master_address, 0)); + + return CLI_OK; + } + + if (!argc) + { + cli_error(cli, "Specify a user to unsnoop"); + return CLI_OK; + } + + for (i = 0; i < argc; i++) + { + if (!(s = sessionbyuser(argv[i]))) + { + cli_error(cli, "User %s is not connected", argv[i]); + continue; + } + + cli_print(cli, "Not snooping user %s", argv[i]); + cli_session_actions[s].action |= CLI_SESS_NOSNOOP; + } + + return CLI_OK; +} + +static int cmd_throttle(struct cli_def *cli, char *command, char **argv, int argc) +{ + int rate_in = 0; + int rate_out = 0; + sessionidt s; + + /* + throttle USER - throttle in/out to default rate + throttle USER RATE - throttle in/out to default rate + throttle USER in RATE - throttle input only + throttle USER out RATE - throttle output only + throttle USER in RATE out RATE - throttle both + */ + + if (CLI_HELP_REQUESTED) + { + switch (argc) + { + case 1: + return cli_arg_help(cli, 0, + "USER", "Username of session to throttle", NULL); + + case 2: + return cli_arg_help(cli, 1, + "RATE", "Rate in kbps (in and out)", + "in", "Select incoming rate", + "out", "Select outgoing rate", NULL); + + case 4: + return cli_arg_help(cli, 1, + "in", "Select incoming rate", + "out", "Select outgoing rate", NULL); + + case 3: + if (isdigit(argv[1][0])) + return cli_arg_help(cli, 1, NULL); + + case 5: + return cli_arg_help(cli, 0, "RATE", "Rate in kbps", NULL); + + default: + return cli_arg_help(cli, argc > 1, NULL); + } + } + + if (!config->cluster_iam_master) + { + cli_error(cli, "Can't do this on a slave. Do it on %s", + fmtaddr(config->cluster_master_address, 0)); + + return CLI_OK; + } + + if (argc == 0) + { + cli_error(cli, "Specify a user to throttle"); + return CLI_OK; + } + + if (!(s = sessionbyuser(argv[0]))) + { + cli_error(cli, "User %s is not connected", argv[0]); + return CLI_OK; + } + + if (argc == 1) + { + rate_in = rate_out = config->rl_rate; + } + else if (argc == 2) + { + rate_in = rate_out = atoi(argv[1]); + if (rate_in < 1) + { + cli_error(cli, "Invalid rate \"%s\"", argv[1]); + return CLI_OK; + } + } + else if (argc == 3 || argc == 5) + { + int i; + for (i = 1; i < argc - 1; i += 2) + { + int r = 0; + if (MATCH("in", argv[i])) + r = rate_in = atoi(argv[i+1]); + else if (MATCH("out", argv[i])) + r = rate_out = atoi(argv[i+1]); + + if (r < 1) + { + cli_error(cli, "Invalid rate specification \"%s %s\"", argv[i], argv[i+1]); + return CLI_OK; + } + } + } + else + { + cli_error(cli, "Invalid arguments"); + return CLI_OK; + } + + if ((rate_in && session[s].throttle_in) || (rate_out && session[s].throttle_out)) + { + cli_error(cli, "User %s already throttled, unthrottle first", argv[0]); + return CLI_OK; + } + + cli_session_actions[s].throttle_in = cli_session_actions[s].throttle_out = -1; + if (rate_in && session[s].throttle_in != rate_in) + cli_session_actions[s].throttle_in = rate_in; + + if (rate_out && session[s].throttle_out != rate_out) + cli_session_actions[s].throttle_out = rate_out; + + if (cli_session_actions[s].throttle_in == -1 && + cli_session_actions[s].throttle_out == -1) + { + cli_error(cli, "User %s already throttled at this rate", argv[0]); + return CLI_OK; + } + + cli_print(cli, "Throttling user %s", argv[0]); + cli_session_actions[s].action |= CLI_SESS_THROTTLE; + + return CLI_OK; +} + +static int cmd_no_throttle(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + sessionidt s; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, argc > 1, + "USER", "Username of session to unthrottle", NULL); + + if (!config->cluster_iam_master) + { + cli_error(cli, "Can't do this on a slave. Do it on %s", + fmtaddr(config->cluster_master_address, 0)); + + return CLI_OK; + } + + if (!argc) + { + cli_error(cli, "Specify a user to unthrottle"); + return CLI_OK; + } + + for (i = 0; i < argc; i++) + { + if (!(s = sessionbyuser(argv[i]))) + { + cli_error(cli, "User %s is not connected", argv[i]); + continue; + } + + if (session[s].throttle_in || session[s].throttle_out) + { + cli_print(cli, "Unthrottling user %s", argv[i]); + cli_session_actions[s].action |= CLI_SESS_NOTHROTTLE; + } + else + { + cli_error(cli, "User %s not throttled", argv[i]); + } + } + + return CLI_OK; +} + +static int cmd_debug(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, 1, + "all", "Enable debugging for all except \"data\"", + "critical", "", // FIXME: add descriptions + "error", "", + "warning", "", + "info", "", + "calls", "", + "data", "", + NULL); + + if (!argc) + { + char *p = (char *) &debug_flags; + for (i = 0; i < sizeof(debug_flags); i++) + { + if (p[i]) + { + cli_print(cli, "Currently debugging:%s%s%s%s%s%s", + (debug_flags.critical) ? " critical" : "", + (debug_flags.error) ? " error" : "", + (debug_flags.warning) ? " warning" : "", + (debug_flags.info) ? " info" : "", + (debug_flags.calls) ? " calls" : "", + (debug_flags.data) ? " data" : ""); + + return CLI_OK; + } + } + + cli_print(cli, "Debugging off"); + return CLI_OK; + } + + for (i = 0; i < argc; i++) + { + int len = strlen(argv[i]); + + if (argv[i][0] == 'c' && len < 2) + len = 2; /* distinguish [cr]itical from [ca]lls */ + + if (!strncmp("critical", argv[i], len)) { debug_flags.critical = 1; continue; } + if (!strncmp("error", argv[i], len)) { debug_flags.error = 1; continue; } + if (!strncmp("warning", argv[i], len)) { debug_flags.warning = 1; continue; } + if (!strncmp("info", argv[i], len)) { debug_flags.info = 1; continue; } + if (!strncmp("calls", argv[i], len)) { debug_flags.calls = 1; continue; } + if (!strncmp("data", argv[i], len)) { debug_flags.data = 1; continue; } + if (!strncmp("all", argv[i], len)) + { + memset(&debug_flags, 1, sizeof(debug_flags)); + debug_flags.data = 0; + continue; + } + + cli_error(cli, "Invalid debugging flag \"%s\"", argv[i]); + } + + return CLI_OK; +} + +static int cmd_no_debug(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, 1, + "all", "Disable all debugging", + "critical", "", // FIXME: add descriptions + "error", "", + "warning", "", + "info", "", + "calls", "", + "data", "", + NULL); + + if (!argc) + { + memset(&debug_flags, 0, sizeof(debug_flags)); + return CLI_OK; + } + + for (i = 0; i < argc; i++) + { + int len = strlen(argv[i]); + + if (argv[i][0] == 'c' && len < 2) + len = 2; /* distinguish [cr]itical from [ca]lls */ + + if (!strncmp("critical", argv[i], len)) { debug_flags.critical = 0; continue; } + if (!strncmp("error", argv[i], len)) { debug_flags.error = 0; continue; } + if (!strncmp("warning", argv[i], len)) { debug_flags.warning = 0; continue; } + if (!strncmp("info", argv[i], len)) { debug_flags.info = 0; continue; } + if (!strncmp("calls", argv[i], len)) { debug_flags.calls = 0; continue; } + if (!strncmp("data", argv[i], len)) { debug_flags.data = 0; continue; } + if (!strncmp("all", argv[i], len)) + { + memset(&debug_flags, 0, sizeof(debug_flags)); + continue; + } + + cli_error(cli, "Invalid debugging flag \"%s\"", argv[i]); + } + + return CLI_OK; +} + +static int cmd_load_plugin(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i, firstfree = 0; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, argc > 1, + "PLUGIN", "Name of plugin to load", NULL); + + if (argc != 1) + { + cli_error(cli, "Specify a plugin to load"); + return CLI_OK; + } + + for (i = 0; i < MAXPLUGINS; i++) + { + if (!*config->plugins[i] && !firstfree) + firstfree = i; + if (strcmp(config->plugins[i], argv[0]) == 0) + { + cli_error(cli, "Plugin is already loaded"); + return CLI_OK; + } + } + + if (firstfree) + { + strncpy(config->plugins[firstfree], argv[0], sizeof(config->plugins[firstfree]) - 1); + config->reload_config = 1; + cli_print(cli, "Loading plugin %s", argv[0]); + } + + return CLI_OK; +} + +static int cmd_remove_plugin(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, argc > 1, + "PLUGIN", "Name of plugin to unload", NULL); + + if (argc != 1) + { + cli_error(cli, "Specify a plugin to remove"); + return CLI_OK; + } + + for (i = 0; i < MAXPLUGINS; i++) + { + if (strcmp(config->plugins[i], argv[0]) == 0) + { + config->reload_config = 1; + memset(config->plugins[i], 0, sizeof(config->plugins[i])); + return CLI_OK; + } + } + + cli_error(cli, "Plugin is not loaded"); + return CLI_OK; +} + +static char *duration(time_t secs) +{ + static char *buf = NULL; + int p = 0; + + if (!buf) buf = calloc(64, 1); + + if (secs >= 86400) + { + int days = secs / 86400; + p = sprintf(buf, "%d day%s, ", days, days > 1 ? "s" : ""); + secs %= 86400; + } + + if (secs >= 3600) + { + int mins = secs / 60; + int hrs = mins / 60; + + mins %= 60; + sprintf(buf + p, "%d:%02d", hrs, mins); + } + else if (secs >= 60) + { + int mins = secs / 60; + sprintf(buf + p, "%d min%s", mins, mins > 1 ? "s" : ""); + } + else + sprintf(buf, "%ld sec%s", secs, secs > 1 ? "s" : ""); + + return buf; +} + +static int cmd_uptime(struct cli_def *cli, char *command, char **argv, int argc) +{ + FILE *fh; + char buf[100], *p = buf, *loads[3]; + int i, num_sessions = 0; + + if (CLI_HELP_REQUESTED) + return CLI_HELP_NO_ARGS; + + fh = fopen("/proc/loadavg", "r"); + fgets(buf, 100, fh); + fclose(fh); + + for (i = 0; i < 3; i++) + loads[i] = strdup(strsep(&p, " ")); + + time(&time_now); + strftime(buf, 99, "%H:%M:%S", localtime(&time_now)); + + for (i = 1; i < MAXSESSION; i++) + if (session[i].opened) num_sessions++; + + cli_print(cli, "%s up %s, %d users, load average: %s, %s, %s", + buf, + duration(time_now - config->start_time), + num_sessions, + loads[0], loads[1], loads[2] + ); + for (i = 0; i < 3; i++) + if (loads[i]) free(loads[i]); + + cli_print(cli, "Bandwidth: %s", config->bandwidth); + + return CLI_OK; +} + +static int cmd_set(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + + if (CLI_HELP_REQUESTED) + { + switch (argc) + { + case 1: + { + int len = strlen(argv[0])-1; + for (i = 0; config_values[i].key; i++) + if (!len || !strncmp(config_values[i].key, argv[0], len)) + cli_error(cli, " %s", config_values[i].key); + } + + return CLI_OK; + + case 2: + return cli_arg_help(cli, 0, + "VALUE", "Value for variable", NULL); + + case 3: + if (!argv[2][1]) + return cli_arg_help(cli, 1, NULL); + + default: + return CLI_OK; + } + } + + if (argc != 2) + { + cli_error(cli, "Specify variable and value"); + return CLI_OK; + } + + for (i = 0; config_values[i].key; i++) + { + void *value = ((void *) config) + config_values[i].offset; + if (strcmp(config_values[i].key, argv[0]) == 0) + { + // Found a value to set + cli_print(cli, "Setting \"%s\" to \"%s\"", argv[0], argv[1]); + switch (config_values[i].type) + { + case STRING: + snprintf((char *) value, config_values[i].size, "%s", argv[1]); + break; + case INT: + *(int *) value = atoi(argv[1]); + break; + case UNSIGNED_LONG: + *(unsigned long *) value = atol(argv[1]); + break; + case SHORT: + *(short *) value = atoi(argv[1]); + break; + case IPv4: + *(in_addr_t *) value = inet_addr(argv[1]); + break; + case IPv6: + inet_pton(AF_INET6, argv[1], value); + break; + case BOOL: + if (strcasecmp(argv[1], "yes") == 0 || strcasecmp(argv[1], "true") == 0 || strcasecmp(argv[1], "1") == 0) + *(int *) value = 1; + else + *(int *) value = 0; + break; + default: + cli_error(cli, "Unknown variable type"); + break; + } + config->reload_config = 1; + return CLI_OK; + } + } + + cli_error(cli, "Unknown variable \"%s\"", argv[0]); + return CLI_OK; +} + +int regular_stuff(struct cli_def *cli) +{ +#ifdef RINGBUFFER + int out = 0; + int i; + + for (i = debug_rb_tail; i != ringbuffer->tail; i = (i + 1) % RINGBUFFER_SIZE) + { + char *m = ringbuffer->buffer[i].message; + char *p; + int show = 0; + + if (!*m) continue; + + switch (ringbuffer->buffer[i].level) + { + case 0: show = debug_flags.critical; break; + case 1: show = debug_flags.error; break; + case 2: show = debug_flags.warning; break; + case 3: show = debug_flags.info; break; + case 4: show = debug_flags.calls; break; + case 5: show = debug_flags.data; break; + } + + if (!show) continue; + + if (!(p = strchr(m, '\n'))) + p = m + strlen(m); + + cli_error(cli, "\r%s-%u-%u %.*s", + debug_levels[(int)ringbuffer->buffer[i].level], + ringbuffer->buffer[i].tunnel, + ringbuffer->buffer[i].session, + (int) (p - m), m); + + out++; + } + + debug_rb_tail = ringbuffer->tail; + if (out) + cli_reprompt(cli); +#endif + return CLI_OK; +} + +#ifdef BGP +static int cmd_router_bgp(struct cli_def *cli, char *command, char **argv, int argc) +{ + int as; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, argc > 1, + "<1-65535>", "Autonomous system number", NULL); + + if (argc != 1 || (as = atoi(argv[0])) < 1 || as > 65535) + { + cli_error(cli, "Invalid autonomous system number"); + return CLI_OK; + } + + if (bgp_configured && as != config->as_number) + { + cli_error(cli, "Can't change local AS on a running system"); + return CLI_OK; + } + + config->as_number = as; + cli_set_configmode(cli, MODE_CONFIG_BGP, "router"); + + return CLI_OK; +} + +static int find_bgp_neighbour(char const *name) +{ + int i; + int new = -1; + struct hostent *h; + in_addr_t addrs[4] = { 0 }; + char **a; + + if (!(h = gethostbyname(name)) || h->h_addrtype != AF_INET) + return -2; + + for (i = 0; i < sizeof(addrs) / sizeof(*addrs) && h->h_addr_list[i]; i++) + memcpy(&addrs[i], h->h_addr_list[i], sizeof(*addrs)); + + for (i = 0; i < BGP_NUM_PEERS; i++) + { + if (!config->neighbour[i].name[0]) + { + if (new == -1) new = i; + continue; + } + + if (!strcmp(name, config->neighbour[i].name)) + return i; + + if (!(h = gethostbyname(config->neighbour[i].name)) || h->h_addrtype != AF_INET) + continue; + + for (a = h->h_addr_list; *a; a++) + { + int j; + for (j = 0; j < sizeof(addrs) / sizeof(*addrs) && addrs[j]; j++) + if (!memcmp(&addrs[j], *a, sizeof(*addrs))) + return i; + } + } + + return new; +} + +static int cmd_router_bgp_neighbour(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + int keepalive; + int hold; + + if (CLI_HELP_REQUESTED) + { + switch (argc) + { + case 1: + return cli_arg_help(cli, 0, + "A.B.C.D", "BGP neighbour address", + "NAME", "BGP neighbour name", + NULL); + + case 2: + return cli_arg_help(cli, 0, + "remote-as", "Set remote autonomous system number", + "timers", "Set timers", + NULL); + + default: + if (MATCH("remote-as", argv[1])) + return cli_arg_help(cli, argv[2][1], "<1-65535>", "Autonomous system number", NULL); + + if (MATCH("timers", argv[1])) + { + if (argc == 3) + return cli_arg_help(cli, 0, "<1-65535>", "Keepalive time", NULL); + + if (argc == 4) + return cli_arg_help(cli, argv[3][1], "<3-65535>", "Hold time", NULL); + + if (argc == 5 && !argv[4][1]) + return cli_arg_help(cli, 1, NULL); + } + + return CLI_OK; + } + } + + if (argc < 3) + { + cli_error(cli, "Invalid arguments"); + return CLI_OK; + } + + if ((i = find_bgp_neighbour(argv[0])) == -2) + { + cli_error(cli, "Invalid neighbour"); + return CLI_OK; + } + + if (i == -1) + { + cli_error(cli, "Too many neighbours (max %d)", BGP_NUM_PEERS); + return CLI_OK; + } + + if (MATCH("remote-as", argv[1])) + { + int as = atoi(argv[2]); + if (as < 0 || as > 65535) + { + cli_error(cli, "Invalid autonomous system number"); + return CLI_OK; + } + + if (!config->neighbour[i].name[0]) + { + snprintf(config->neighbour[i].name, sizeof(config->neighbour[i].name), "%s", argv[0]); + config->neighbour[i].keepalive = -1; + config->neighbour[i].hold = -1; + } + + config->neighbour[i].as = as; + return CLI_OK; + } + + if (argc != 4 || !MATCH("timers", argv[1])) + { + cli_error(cli, "Invalid arguments"); + return CLI_OK; + } + + if (!config->neighbour[i].name[0]) + { + cli_error(cli, "Specify remote-as first"); + return CLI_OK; + } + + keepalive = atoi(argv[2]); + hold = atoi(argv[3]); + + if (keepalive < 1 || keepalive > 65535) + { + cli_error(cli, "Invalid keepalive time"); + return CLI_OK; + } + + if (hold < 3 || hold > 65535) + { + cli_error(cli, "Invalid hold time"); + return CLI_OK; + } + + if (keepalive == BGP_KEEPALIVE_TIME) + keepalive = -1; // using default value + + if (hold == BGP_HOLD_TIME) + hold = -1; + + config->neighbour[i].keepalive = keepalive; + config->neighbour[i].hold = hold; + + return CLI_OK; +} + +static int cmd_router_bgp_no_neighbour(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, argc > 0, + "A.B.C.D", "BGP neighbour address", + "NAME", "BGP neighbour name", + NULL); + + if (argc != 1) + { + cli_error(cli, "Specify a BGP neighbour"); + return CLI_OK; + } + + if ((i = find_bgp_neighbour(argv[0])) == -2) + { + cli_error(cli, "Invalid neighbour"); + return CLI_OK; + } + + if (i < 0 || !config->neighbour[i].name[0]) + { + cli_error(cli, "Neighbour %s not configured", argv[0]); + return CLI_OK; + } + + memset(&config->neighbour[i], 0, sizeof(config->neighbour[i])); + return CLI_OK; +} + +static int cmd_show_bgp(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + int hdr = 0; + char *addr; + + if (!bgp_configured) + return CLI_OK; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, 1, + "A.B.C.D", "BGP neighbour address", + "NAME", "BGP neighbour name", + NULL); + + cli_print(cli, "BGPv%d router identifier %s, local AS number %d", + BGP_VERSION, fmtaddr(my_address, 0), (int) config->as_number); + + time(&time_now); + + for (i = 0; i < BGP_NUM_PEERS; i++) + { + if (!*bgp_peers[i].name) + continue; + + addr = fmtaddr(bgp_peers[i].addr, 0); + if (argc && strcmp(addr, argv[0]) && + strncmp(bgp_peers[i].name, argv[0], strlen(argv[0]))) + continue; + + if (!hdr++) + { + cli_print(cli, ""); + cli_print(cli, "Peer AS Address " + "State Retries Retry in Route Pend Timers"); + cli_print(cli, "------------------ ----- --------------- " + "----------- ------- -------- ----- ---- ---------"); + } + + cli_print(cli, "%-18.18s %5d %15s %-11s %7d %7lds %5s %4s %4d %4d", + bgp_peers[i].name, + bgp_peers[i].as, + addr, + bgp_state_str(bgp_peers[i].state), + bgp_peers[i].retry_count, + bgp_peers[i].retry_time ? bgp_peers[i].retry_time - time_now : 0, + bgp_peers[i].routing ? "yes" : "no", + bgp_peers[i].update_routes ? "yes" : "no", + bgp_peers[i].keepalive, + bgp_peers[i].hold); + } + + return CLI_OK; +} + +static int cmd_suspend_bgp(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + char *addr; + + if (!bgp_configured) + return CLI_OK; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, 1, + "A.B.C.D", "BGP neighbour address", + "NAME", "BGP neighbour name", + NULL); + + for (i = 0; i < BGP_NUM_PEERS; i++) + { + if (bgp_peers[i].state != Established) + continue; + + if (!bgp_peers[i].routing) + continue; + + addr = fmtaddr(bgp_peers[i].addr, 0); + if (argc && strcmp(addr, argv[0]) && strcmp(bgp_peers[i].name, argv[0])) + continue; + + bgp_peers[i].cli_flag = BGP_CLI_SUSPEND; + cli_print(cli, "Suspending peer %s", bgp_peers[i].name); + } + + return CLI_OK; +} + +static int cmd_no_suspend_bgp(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + char *addr; + + if (!bgp_configured) + return CLI_OK; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, 1, + "A.B.C.D", "BGP neighbour address", + "NAME", "BGP neighbour name", + NULL); + + for (i = 0; i < BGP_NUM_PEERS; i++) + { + if (bgp_peers[i].state != Established) + continue; + + if (bgp_peers[i].routing) + continue; + + addr = fmtaddr(bgp_peers[i].addr, 0); + if (argc && strcmp(addr, argv[0]) && + strncmp(bgp_peers[i].name, argv[0], strlen(argv[0]))) + continue; + + bgp_peers[i].cli_flag = BGP_CLI_ENABLE; + cli_print(cli, "Un-suspending peer %s", bgp_peers[i].name); + } + + return CLI_OK; +} + +static int cmd_restart_bgp(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + char *addr; + + if (!bgp_configured) + return CLI_OK; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, 1, + "A.B.C.D", "BGP neighbour address", + "NAME", "BGP neighbour name", + NULL); + + for (i = 0; i < BGP_NUM_PEERS; i++) + { + if (!*bgp_peers[i].name) + continue; + + addr = fmtaddr(bgp_peers[i].addr, 0); + if (argc && strcmp(addr, argv[0]) && + strncmp(bgp_peers[i].name, argv[0], strlen(argv[0]))) + continue; + + bgp_peers[i].cli_flag = BGP_CLI_RESTART; + cli_print(cli, "Restarting peer %s", bgp_peers[i].name); + } + + return CLI_OK; +} +#endif /* BGP*/ + +static int filt; +static int access_list(struct cli_def *cli, char **argv, int argc, int add) +{ + int extended; + + if (CLI_HELP_REQUESTED) + { + switch (argc) + { + case 1: + return cli_arg_help(cli, 0, + "standard", "Standard syntax", + "extended", "Extended syntax", + NULL); + + case 2: + return cli_arg_help(cli, argv[1][1], + "NAME", "Access-list name", + NULL); + + default: + if (argc == 3 && !argv[2][1]) + return cli_arg_help(cli, 1, NULL); + + return CLI_OK; + } + } + + if (argc != 2) + { + cli_error(cli, "Specify access-list type and name"); + return CLI_OK; + } + + if (MATCH("standard", argv[0])) + extended = 0; + else if (MATCH("extended", argv[0])) + extended = 1; + else + { + cli_error(cli, "Invalid access-list type"); + return CLI_OK; + } + + if (strlen(argv[1]) > sizeof(ip_filters[0].name) - 1 || + strspn(argv[1], "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-") != strlen(argv[1])) + { + cli_error(cli, "Invalid access-list name"); + return CLI_OK; + } + + filt = find_filter(argv[1], strlen(argv[1])); + if (add) + { + if (filt < 0) + { + cli_error(cli, "Too many access-lists"); + return CLI_OK; + } + + // racy + if (!*ip_filters[filt].name) + { + memset(&ip_filters[filt], 0, sizeof(ip_filters[filt])); + strcpy(ip_filters[filt].name, argv[1]); + ip_filters[filt].extended = extended; + } + else if (ip_filters[filt].extended != extended) + { + cli_error(cli, "Access-list is %s", + ip_filters[filt].extended ? "extended" : "standard"); + + return CLI_OK; + } + + cli_set_configmode(cli, MODE_CONFIG_NACL, extended ? "ext-nacl" : "std-nacl"); + return CLI_OK; + } + + if (filt < 0 || !*ip_filters[filt].name) + { + cli_error(cli, "Access-list not defined"); + return CLI_OK; + } + + // racy + if (ip_filters[filt].used) + { + cli_error(cli, "Access-list in use"); + return CLI_OK; + } + + memset(&ip_filters[filt], 0, sizeof(ip_filters[filt])); + return CLI_OK; +} + +static int cmd_ip_access_list(struct cli_def *cli, char *command, char **argv, int argc) +{ + return access_list(cli, argv, argc, 1); +} + +static int cmd_no_ip_access_list(struct cli_def *cli, char *command, char **argv, int argc) +{ + return access_list(cli, argv, argc, 0); +} + +static int show_ip_wild(char *buf, in_addr_t ip, in_addr_t wild) +{ + if (ip == INADDR_ANY && wild == INADDR_BROADCAST) + return sprintf(buf, " any"); + + if (wild == INADDR_ANY) + return sprintf(buf, " host %s", fmtaddr(ip, 0)); + + return sprintf(buf, " %s %s", fmtaddr(ip, 0), fmtaddr(wild, 1)); +} + +static int show_ports(char *buf, ip_filter_portt *ports) +{ + switch (ports->op) + { + case FILTER_PORT_OP_EQ: return sprintf(buf, " eq %u", ports->port); + case FILTER_PORT_OP_NEQ: return sprintf(buf, " neq %u", ports->port); + case FILTER_PORT_OP_GT: return sprintf(buf, " gt %u", ports->port); + case FILTER_PORT_OP_LT: return sprintf(buf, " lt %u", ports->port); + case FILTER_PORT_OP_RANGE: return sprintf(buf, " range %u %u", ports->port, ports->port2); + } + + return 0; +} + +static char const *show_access_list_rule(int extended, ip_filter_rulet *rule) +{ + static char buf[256]; + char *p = buf; + + p += sprintf(p, " %s", rule->action == FILTER_ACTION_PERMIT ? "permit" : "deny"); + if (extended) + { + struct protoent *proto = getprotobynumber(rule->proto); + p += sprintf(p, " %s", proto ? proto->p_name : "ERR"); + } + + p += show_ip_wild(p, rule->src_ip, rule->src_wild); + if (!extended) + return buf; + + if (rule->proto == IPPROTO_TCP || rule->proto == IPPROTO_UDP) + p += show_ports(p, &rule->src_ports); + + p += show_ip_wild(p, rule->dst_ip, rule->dst_wild); + if (rule->proto == IPPROTO_TCP || rule->proto == IPPROTO_UDP) + p += show_ports(p, &rule->dst_ports); + + if (rule->proto == IPPROTO_TCP && rule->tcp_flag_op) + { + switch (rule->tcp_flag_op) + { + case FILTER_FLAG_OP_EST: + p += sprintf(p, " established"); + break; + + case FILTER_FLAG_OP_ANY: + case FILTER_FLAG_OP_ALL: + p += sprintf(p, " match-%s", rule->tcp_flag_op == FILTER_FLAG_OP_ALL ? "all" : "any"); + if (rule->tcp_sflags & TCP_FLAG_FIN) p += sprintf(p, " +fin"); + if (rule->tcp_cflags & TCP_FLAG_FIN) p += sprintf(p, " -fin"); + if (rule->tcp_sflags & TCP_FLAG_SYN) p += sprintf(p, " +syn"); + if (rule->tcp_cflags & TCP_FLAG_SYN) p += sprintf(p, " -syn"); + if (rule->tcp_sflags & TCP_FLAG_RST) p += sprintf(p, " +rst"); + if (rule->tcp_cflags & TCP_FLAG_RST) p += sprintf(p, " -rst"); + if (rule->tcp_sflags & TCP_FLAG_PSH) p += sprintf(p, " +psh"); + if (rule->tcp_cflags & TCP_FLAG_PSH) p += sprintf(p, " -psh"); + if (rule->tcp_sflags & TCP_FLAG_ACK) p += sprintf(p, " +ack"); + if (rule->tcp_cflags & TCP_FLAG_ACK) p += sprintf(p, " -ack"); + if (rule->tcp_sflags & TCP_FLAG_URG) p += sprintf(p, " +urg"); + if (rule->tcp_cflags & TCP_FLAG_URG) p += sprintf(p, " -urg"); + break; + } + } + + if (rule->frag) + p += sprintf(p, " fragments"); + + return buf; +} + +static ip_filter_rulet *access_list_rule_ext(struct cli_def *cli, char *command, char **argv, int argc) +{ + static ip_filter_rulet rule; + struct in_addr addr; + int i; + int a; + + if (CLI_HELP_REQUESTED) + { + if (argc == 1) + { + cli_arg_help(cli, 0, + "ip", "Match IP packets", + "tcp", "Match TCP packets", + "udp", "Match UDP packets", + NULL); + + return NULL; + } + + // *sigh*, too darned complex + cli_arg_help(cli, 0, "RULE", "SOURCE [PORTS] DEST [PORTS] FLAGS", NULL); + return NULL; + } + + if (argc < 3) + { + cli_error(cli, "Specify rule details"); + return NULL; + } + + memset(&rule, 0, sizeof(rule)); + rule.action = (command[0] == 'p') + ? FILTER_ACTION_PERMIT + : FILTER_ACTION_DENY; + + if (MATCH("ip", argv[0])) + rule.proto = IPPROTO_IP; + else if (MATCH("udp", argv[0])) + rule.proto = IPPROTO_UDP; + else if (MATCH("tcp", argv[0])) + rule.proto = IPPROTO_TCP; + else + { + cli_error(cli, "Invalid protocol \"%s\"", argv[0]); + return NULL; + } + + for (a = 1, i = 0; i < 2; i++) + { + in_addr_t *ip; + in_addr_t *wild; + ip_filter_portt *port; + + if (i == 0) + { + ip = &rule.src_ip; + wild = &rule.src_wild; + port = &rule.src_ports; + } + else + { + ip = &rule.dst_ip; + wild = &rule.dst_wild; + port = &rule.dst_ports; + if (a >= argc) + { + cli_error(cli, "Specify destination"); + return NULL; + } + } + + if (MATCH("any", argv[a])) + { + *ip = INADDR_ANY; + *wild = INADDR_BROADCAST; + a++; + } + else if (MATCH("host", argv[a])) + { + if (++a >= argc) + { + cli_error(cli, "Specify host ip address"); + return NULL; + } + + if (!inet_aton(argv[a], &addr)) + { + cli_error(cli, "Cannot parse IP \"%s\"", argv[a]); + return NULL; + } + + *ip = addr.s_addr; + *wild = INADDR_ANY; + a++; + } + else + { + if (a >= argc - 1) + { + cli_error(cli, "Specify %s ip address and wildcard", i ? "destination" : "source"); + return NULL; + } + + if (!inet_aton(argv[a], &addr)) + { + cli_error(cli, "Cannot parse IP \"%s\"", argv[a]); + return NULL; + } + + *ip = addr.s_addr; + + if (!inet_aton(argv[++a], &addr)) + { + cli_error(cli, "Cannot parse IP \"%s\"", argv[a]); + return NULL; + } + + *wild = addr.s_addr; + a++; + } + + if (rule.proto == IPPROTO_IP || a >= argc) + continue; + + port->op = 0; + if (MATCH("eq", argv[a])) + port->op = FILTER_PORT_OP_EQ; + else if (MATCH("neq", argv[a])) + port->op = FILTER_PORT_OP_NEQ; + else if (MATCH("gt", argv[a])) + port->op = FILTER_PORT_OP_GT; + else if (MATCH("lt", argv[a])) + port->op = FILTER_PORT_OP_LT; + else if (MATCH("range", argv[a])) + port->op = FILTER_PORT_OP_RANGE; + + if (!port->op) + continue; + + if (++a >= argc) + { + cli_error(cli, "Specify port"); + return NULL; + } + + if (!(port->port = atoi(argv[a]))) + { + cli_error(cli, "Invalid port \"%s\"", argv[a]); + return NULL; + } + + a++; + if (port->op != FILTER_PORT_OP_RANGE) + continue; + + if (a >= argc) + { + cli_error(cli, "Specify port"); + return NULL; + } + + if (!(port->port2 = atoi(argv[a])) || port->port2 < port->port) + { + cli_error(cli, "Invalid port \"%s\"", argv[a]); + return NULL; + } + + a++; + } + + if (rule.proto == IPPROTO_TCP && a < argc) + { + if (MATCH("established", argv[a])) + { + rule.tcp_flag_op = FILTER_FLAG_OP_EST; + a++; + } + else if (!strcmp(argv[a], "match-any") || !strcmp(argv[a], "match-an") || + !strcmp(argv[a], "match-all") || !strcmp(argv[a], "match-al")) + { + rule.tcp_flag_op = argv[a][7] == 'n' + ? FILTER_FLAG_OP_ANY + : FILTER_FLAG_OP_ALL; + + if (++a >= argc) + { + cli_error(cli, "Specify tcp flags"); + return NULL; + } + + while (a < argc && (argv[a][0] == '+' || argv[a][0] == '-')) + { + uint8_t *f; + + f = (argv[a][0] == '+') ? &rule.tcp_sflags : &rule.tcp_cflags; + + if (MATCH("fin", &argv[a][1])) *f |= TCP_FLAG_FIN; + else if (MATCH("syn", &argv[a][1])) *f |= TCP_FLAG_SYN; + else if (MATCH("rst", &argv[a][1])) *f |= TCP_FLAG_RST; + else if (MATCH("psh", &argv[a][1])) *f |= TCP_FLAG_PSH; + else if (MATCH("ack", &argv[a][1])) *f |= TCP_FLAG_ACK; + else if (MATCH("urg", &argv[a][1])) *f |= TCP_FLAG_URG; + else + { + cli_error(cli, "Invalid tcp flag \"%s\"", argv[a]); + return NULL; + } + + a++; + } + } + } + + if (a < argc && MATCH("fragments", argv[a])) + { + if (rule.src_ports.op || rule.dst_ports.op || rule.tcp_flag_op) + { + cli_error(cli, "Can't specify \"fragments\" on rules with layer 4 matches"); + return NULL; + } + + rule.frag = 1; + a++; + } + + if (a < argc) + { + cli_error(cli, "Invalid flag \"%s\"", argv[a]); + return NULL; + } + + return &rule; +} + +static ip_filter_rulet *access_list_rule_std(struct cli_def *cli, char *command, char **argv, int argc) +{ + static ip_filter_rulet rule; + struct in_addr addr; + + if (CLI_HELP_REQUESTED) + { + if (argc == 1) + { + cli_arg_help(cli, argv[0][1], + "A.B.C.D", "Source address", + "any", "Any source address", + "host", "Source host", + NULL); + + return NULL; + } + + if (MATCH("any", argv[0])) + { + if (argc == 2 && !argv[1][1]) + cli_arg_help(cli, 1, NULL); + } + else if (MATCH("host", argv[0])) + { + if (argc == 2) + { + cli_arg_help(cli, argv[1][1], + "A.B.C.D", "Host address", + NULL); + } + else if (argc == 3 && !argv[2][1]) + cli_arg_help(cli, 1, NULL); + } + else + { + if (argc == 2) + { + cli_arg_help(cli, 1, + "A.B.C.D", "Wildcard bits", + NULL); + } + else if (argc == 3 && !argv[2][1]) + cli_arg_help(cli, 1, NULL); + } + + return NULL; + } + + if (argc < 1) + { + cli_error(cli, "Specify rule details"); + return NULL; + } + + memset(&rule, 0, sizeof(rule)); + rule.action = (command[0] == 'p') + ? FILTER_ACTION_PERMIT + : FILTER_ACTION_DENY; + + rule.proto = IPPROTO_IP; + if (MATCH("any", argv[0])) + { + rule.src_ip = INADDR_ANY; + rule.src_wild = INADDR_BROADCAST; + } + else if (MATCH("host", argv[0])) + { + if (argc != 2) + { + cli_error(cli, "Specify host ip address"); + return NULL; + } + + if (!inet_aton(argv[1], &addr)) + { + cli_error(cli, "Cannot parse IP \"%s\"", argv[1]); + return NULL; + } + + rule.src_ip = addr.s_addr; + rule.src_wild = INADDR_ANY; + } + else + { + if (argc > 2) + { + cli_error(cli, "Specify source ip address and wildcard"); + return NULL; + } + + if (!inet_aton(argv[0], &addr)) + { + cli_error(cli, "Cannot parse IP \"%s\"", argv[0]); + return NULL; + } + + rule.src_ip = addr.s_addr; + + if (argc > 1) + { + if (!inet_aton(argv[1], &addr)) + { + cli_error(cli, "Cannot parse IP \"%s\"", argv[1]); + return NULL; + } + + rule.src_wild = addr.s_addr; + } + else + rule.src_wild = INADDR_ANY; + } + + return &rule; +} + +static int cmd_ip_access_list_rule(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + ip_filter_rulet *rule = ip_filters[filt].extended + ? access_list_rule_ext(cli, command, argv, argc) + : access_list_rule_std(cli, command, argv, argc); + + if (!rule) + return CLI_OK; + + for (i = 0; i < MAXFILTER_RULES - 1; i++) // -1: list always terminated by empty rule + { + if (!ip_filters[filt].rules[i].action) + { + memcpy(&ip_filters[filt].rules[i], rule, sizeof(*rule)); + return CLI_OK; + } + + if (!memcmp(&ip_filters[filt].rules[i], rule, offsetof(ip_filter_rulet, counter))) + return CLI_OK; + } + + cli_error(cli, "Too many rules"); + return CLI_OK; +} + +static int cmd_filter(struct cli_def *cli, char *command, char **argv, int argc) +{ + sessionidt s; + int i; + + /* filter USER {in|out} FILTER ... */ + if (CLI_HELP_REQUESTED) + { + switch (argc) + { + case 1: + return cli_arg_help(cli, 0, + "USER", "Username of session to filter", NULL); + + case 2: + case 4: + return cli_arg_help(cli, 0, + "in", "Set incoming filter", + "out", "Set outgoing filter", NULL); + + case 3: + case 5: + return cli_arg_help(cli, argc == 5 && argv[4][1], + "NAME", "Filter name", NULL); + + default: + return cli_arg_help(cli, argc > 1, NULL); + } + } + + if (!config->cluster_iam_master) + { + cli_error(cli, "Can't do this on a slave. Do it on %s", + fmtaddr(config->cluster_master_address, 0)); + + return CLI_OK; + } + + if (argc != 3 && argc != 5) + { + cli_error(cli, "Specify a user and filters"); + return CLI_OK; + } + + if (!(s = sessionbyuser(argv[0]))) + { + cli_error(cli, "User %s is not connected", argv[0]); + return CLI_OK; + } + + cli_session_actions[s].filter_in = cli_session_actions[s].filter_out = -1; + for (i = 1; i < argc; i += 2) + { + int *f = 0; + int v; + + if (MATCH("in", argv[i])) + { + if (session[s].filter_in) + { + cli_error(cli, "Input already filtered"); + return CLI_OK; + } + f = &cli_session_actions[s].filter_in; + } + else if (MATCH("out", argv[i])) + { + if (session[s].filter_out) + { + cli_error(cli, "Output already filtered"); + return CLI_OK; + } + f = &cli_session_actions[s].filter_out; + } + else + { + cli_error(cli, "Invalid filter specification"); + return CLI_OK; + } + + v = find_filter(argv[i+1], strlen(argv[i+1])); + if (v < 0 || !*ip_filters[v].name) + { + cli_error(cli, "Access-list %s not defined", argv[i+1]); + return CLI_OK; + } + + *f = v + 1; + } + + cli_print(cli, "Filtering user %s", argv[0]); + cli_session_actions[s].action |= CLI_SESS_FILTER; + + return CLI_OK; +} + +static int cmd_no_filter(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + sessionidt s; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, argc > 1, + "USER", "Username of session to remove filters from", NULL); + + if (!config->cluster_iam_master) + { + cli_error(cli, "Can't do this on a slave. Do it on %s", + fmtaddr(config->cluster_master_address, 0)); + + return CLI_OK; + } + + if (!argc) + { + cli_error(cli, "Specify a user to remove filters from"); + return CLI_OK; + } + + for (i = 0; i < argc; i++) + { + if (!(s = sessionbyuser(argv[i]))) + { + cli_error(cli, "User %s is not connected", argv[i]); + continue; + } + + if (session[s].filter_in || session[s].filter_out) + { + cli_print(cli, "Removing filters from user %s", argv[i]); + cli_session_actions[s].action |= CLI_SESS_NOFILTER; + } + else + { + cli_error(cli, "User %s not filtered", argv[i]); + } + } + + return CLI_OK; +} + +static int cmd_show_access_list(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + + if (CLI_HELP_REQUESTED) + return cli_arg_help(cli, argc > 1, "NAME", "Filter name", NULL); + + if (argc < 1) + { + cli_error(cli, "Specify a filter name"); + return CLI_OK; + } + + for (i = 0; i < argc; i++) + { + int f = find_filter(argv[i], strlen(argv[i])); + ip_filter_rulet *rules; + + if (f < 0 || !*ip_filters[f].name) + { + cli_error(cli, "Access-list %s not defined", argv[i]); + return CLI_OK; + } + + if (i) + cli_print(cli, ""); + + cli_print(cli, "%s IP access list %s", + ip_filters[f].extended ? "Extended" : "Standard", + ip_filters[f].name); + + for (rules = ip_filters[f].rules; rules->action; rules++) + { + char const *r = show_access_list_rule(ip_filters[f].extended, rules); + if (rules->counter) + cli_print(cli, "%s (%u match%s)", r, + rules->counter, rules->counter > 1 ? "es" : ""); + else + cli_print(cli, "%s", r); + } + } + + return CLI_OK; +} diff --git a/cluster.c b/cluster.c new file mode 100644 index 0000000..538da20 --- /dev/null +++ b/cluster.c @@ -0,0 +1,1870 @@ +// L2TPNS Clustering Stuff + +char const *cvs_id_cluster = "$Id: cluster.c,v 1.50.2.1 2006/12/02 14:09:14 bodea Exp $"; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "l2tpns.h" +#include "cluster.h" +#include "util.h" +#include "tbf.h" + +#ifdef BGP +#include "bgp.h" +#endif +/* + * All cluster packets have the same format. + * + * One or more instances of + * a 32 bit 'type' id. + * a 32 bit 'extra' data dependant on the 'type'. + * zero or more bytes of structure data, dependant on the type. + * + */ + +// Module variables. +extern int cluster_sockfd; // The filedescriptor for the cluster communications port. + +in_addr_t my_address = 0; // The network address of my ethernet port. +static int walk_session_number = 0; // The next session to send when doing the slow table walk. +static int walk_tunnel_number = 0; // The next tunnel to send when doing the slow table walk. +int forked = 0; // Sanity check: CLI must not diddle with heartbeat table + +#define MAX_HEART_SIZE (8192) // Maximum size of heartbeat packet. Must be less than max IP packet size :) +#define MAX_CHANGES (MAX_HEART_SIZE/(sizeof(sessiont) + sizeof(int) ) - 2) // Assumes a session is the biggest type! + +static struct { + int type; + int id; +} cluster_changes[MAX_CHANGES]; // Queue of changed structures that need to go out when next heartbeat. + +static struct { + int seq; + int size; + uint8_t data[MAX_HEART_SIZE]; +} past_hearts[HB_HISTORY_SIZE]; // Ring buffer of heartbeats that we've recently sent out. Needed so + // we can re-transmit if needed. + +static struct { + in_addr_t peer; + uint32_t basetime; + clockt timestamp; + int uptodate; +} peers[CLUSTER_MAX_SIZE]; // List of all the peers we've heard from. +static int num_peers; // Number of peers in list. + +static int rle_decompress(uint8_t **src_p, int ssize, uint8_t *dst, int dsize); +static int rle_compress(uint8_t **src_p, int ssize, uint8_t *dst, int dsize); + +// +// Create a listening socket +// +// This joins the cluster multi-cast group. +// +int cluster_init() +{ + struct sockaddr_in addr; + struct sockaddr_in interface_addr; + struct ip_mreq mreq; + struct ifreq ifr; + int opt; + + config->cluster_undefined_sessions = MAXSESSION-1; + config->cluster_undefined_tunnels = MAXTUNNEL-1; + + if (!config->cluster_address) + return 0; + if (!*config->cluster_interface) + return 0; + + cluster_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(CLUSTERPORT); + addr.sin_addr.s_addr = INADDR_ANY; + setsockopt(cluster_sockfd, SOL_SOCKET, SO_REUSEADDR, &addr, sizeof(addr)); + + opt = fcntl(cluster_sockfd, F_GETFL, 0); + fcntl(cluster_sockfd, F_SETFL, opt | O_NONBLOCK); + + if (bind(cluster_sockfd, (void *) &addr, sizeof(addr)) < 0) + { + LOG(0, 0, 0, "Failed to bind cluster socket: %s\n", strerror(errno)); + return -1; + } + + strcpy(ifr.ifr_name, config->cluster_interface); + if (ioctl(cluster_sockfd, SIOCGIFADDR, &ifr) < 0) + { + LOG(0, 0, 0, "Failed to get interface address for (%s): %s\n", config->cluster_interface, strerror(errno)); + return -1; + } + + memcpy(&interface_addr, &ifr.ifr_addr, sizeof(interface_addr)); + my_address = interface_addr.sin_addr.s_addr; + + // Join multicast group. + mreq.imr_multiaddr.s_addr = config->cluster_address; + mreq.imr_interface = interface_addr.sin_addr; + + + opt = 0; // Turn off multicast loopback. + setsockopt(cluster_sockfd, IPPROTO_IP, IP_MULTICAST_LOOP, &opt, sizeof(opt)); + + if (config->cluster_mcast_ttl != 1) + { + uint8_t ttl = 0; + if (config->cluster_mcast_ttl > 0) + ttl = config->cluster_mcast_ttl < 256 ? config->cluster_mcast_ttl : 255; + + setsockopt(cluster_sockfd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); + } + + if (setsockopt(cluster_sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) + { + LOG(0, 0, 0, "Failed to setsockopt (join mcast group): %s\n", strerror(errno)); + return -1; + } + + if (setsockopt(cluster_sockfd, IPPROTO_IP, IP_MULTICAST_IF, &interface_addr, sizeof(interface_addr)) < 0) + { + LOG(0, 0, 0, "Failed to setsockopt (set mcast interface): %s\n", strerror(errno)); + return -1; + } + + config->cluster_last_hb = TIME; + config->cluster_seq_number = -1; + + return cluster_sockfd; +} + + +// +// Send a chunk of data to the entire cluster (usually via the multicast +// address ). +// + +static int cluster_send_data(void *data, int datalen) +{ + struct sockaddr_in addr = {0}; + + if (!cluster_sockfd) return -1; + if (!config->cluster_address) return 0; + + addr.sin_addr.s_addr = config->cluster_address; + addr.sin_port = htons(CLUSTERPORT); + addr.sin_family = AF_INET; + + LOG(5, 0, 0, "Cluster send data: %d bytes\n", datalen); + + if (sendto(cluster_sockfd, data, datalen, MSG_NOSIGNAL, (void *) &addr, sizeof(addr)) < 0) + { + LOG(0, 0, 0, "sendto: %s\n", strerror(errno)); + return -1; + } + + return 0; +} + +// +// Add a chunk of data to a heartbeat packet. +// Maintains the format. Assumes that the caller +// has passed in a big enough buffer! +// +static void add_type(uint8_t **p, int type, int more, uint8_t *data, int size) +{ + *((uint32_t *) (*p)) = type; + *p += sizeof(uint32_t); + + *((uint32_t *)(*p)) = more; + *p += sizeof(uint32_t); + + if (data && size > 0) { + memcpy(*p, data, size); + *p += size; + } +} + +// advertise our presence via BGP or gratuitous ARP +static void advertise_routes(void) +{ +#ifdef BGP + if (bgp_configured) + bgp_enable_routing(1); + else +#endif /* BGP */ + if (config->send_garp) + send_garp(config->bind_address); // Start taking traffic. +} + +// withdraw our routes (BGP only) +static void withdraw_routes(void) +{ +#ifdef BGP + if (bgp_configured) + bgp_enable_routing(0); +#endif /* BGP */ +} + +static void cluster_uptodate(void) +{ + if (config->cluster_iam_uptodate) + return; + + if (config->cluster_undefined_sessions || config->cluster_undefined_tunnels) + return; + + config->cluster_iam_uptodate = 1; + + LOG(0, 0, 0, "Now uptodate with master.\n"); + advertise_routes(); +} + +// +// Send a unicast UDP packet to a peer with 'data' as the +// contents. +// +static int peer_send_data(in_addr_t peer, uint8_t *data, int size) +{ + struct sockaddr_in addr = {0}; + + if (!cluster_sockfd) return -1; + if (!config->cluster_address) return 0; + + if (!peer) // Odd?? + return -1; + + addr.sin_addr.s_addr = peer; + addr.sin_port = htons(CLUSTERPORT); + addr.sin_family = AF_INET; + + LOG_HEX(5, "Peer send", data, size); + + if (sendto(cluster_sockfd, data, size, MSG_NOSIGNAL, (void *) &addr, sizeof(addr)) < 0) + { + LOG(0, 0, 0, "sendto: %s\n", strerror(errno)); + return -1; + } + + return 0; +} + +// +// Send a structured message to a peer with a single element of type 'type'. +// +static int peer_send_message(in_addr_t peer, int type, int more, uint8_t *data, int size) +{ + uint8_t buf[65536]; // Vast overkill. + uint8_t *p = buf; + + LOG(4, 0, 0, "Sending message to peer (type %d, more %d, size %d)\n", type, more, size); + add_type(&p, type, more, data, size); + + return peer_send_data(peer, buf, (p-buf) ); +} + +// send a packet to the master +static int _forward_packet(uint8_t *data, int size, in_addr_t addr, int port, int type) +{ + uint8_t buf[65536]; // Vast overkill. + uint8_t *p = buf; + + if (!config->cluster_master_address) // No election has been held yet. Just skip it. + return -1; + + LOG(4, 0, 0, "Forwarding packet from %s to master (size %d)\n", fmtaddr(addr, 0), size); + + STAT(c_forwarded); + add_type(&p, type, addr, (uint8_t *) &port, sizeof(port)); // ick. should be uint16_t + memcpy(p, data, size); + p += size; + + return peer_send_data(config->cluster_master_address, buf, (p - buf)); +} + +// +// Forward a state changing packet to the master. +// +// The master just processes the payload as if it had +// received it off the tun device. +// +int master_forward_packet(uint8_t *data, int size, in_addr_t addr, int port) +{ + return _forward_packet(data, size, addr, port, C_FORWARD); +} + +// Forward a DAE RADIUS packet to the master. +int master_forward_dae_packet(uint8_t *data, int size, in_addr_t addr, int port) +{ + return _forward_packet(data, size, addr, port, C_FORWARD_DAE); +} + +// +// Forward a throttled packet to the master for handling. +// +// The master just drops the packet into the appropriate +// token bucket queue, and lets normal processing take care +// of it. +// +int master_throttle_packet(int tbfid, uint8_t *data, int size) +{ + uint8_t buf[65536]; // Vast overkill. + uint8_t *p = buf; + + if (!config->cluster_master_address) // No election has been held yet. Just skip it. + return -1; + + LOG(4, 0, 0, "Throttling packet master (size %d, tbfid %d)\n", size, tbfid); + + add_type(&p, C_THROTTLE, tbfid, data, size); + + return peer_send_data(config->cluster_master_address, buf, (p-buf) ); + +} + +// +// Forward a walled garden packet to the master for handling. +// +// The master just writes the packet straight to the tun +// device (where is will normally loop through the +// firewall rules, and come back in on the tun device) +// +// (Note that this must be called with the tun header +// as the start of the data). +int master_garden_packet(sessionidt s, uint8_t *data, int size) +{ + uint8_t buf[65536]; // Vast overkill. + uint8_t *p = buf; + + if (!config->cluster_master_address) // No election has been held yet. Just skip it. + return -1; + + LOG(4, 0, 0, "Walled garden packet to master (size %d)\n", size); + + add_type(&p, C_GARDEN, s, data, size); + + return peer_send_data(config->cluster_master_address, buf, (p-buf)); + +} + +// +// Send a chunk of data as a heartbeat.. +// We save it in the history buffer as we do so. +// +static void send_heartbeat(int seq, uint8_t *data, int size) +{ + int i; + + if (size > sizeof(past_hearts[0].data)) + { + LOG(0, 0, 0, "Tried to heartbeat something larger than the maximum packet!\n"); + kill(0, SIGTERM); + exit(1); + } + i = seq % HB_HISTORY_SIZE; + past_hearts[i].seq = seq; + past_hearts[i].size = size; + memcpy(&past_hearts[i].data, data, size); // Save it. + cluster_send_data(data, size); +} + +// +// Send an 'i am alive' message to every machine in the cluster. +// +void cluster_send_ping(time_t basetime) +{ + uint8_t buff[100 + sizeof(pingt)]; + uint8_t *p = buff; + pingt x; + + if (config->cluster_iam_master && basetime) // We're heartbeating so no need to ping. + return; + + LOG(5, 0, 0, "Sending cluster ping...\n"); + + x.ver = 1; + x.addr = config->bind_address; + x.undef = config->cluster_undefined_sessions + config->cluster_undefined_tunnels; + x.basetime = basetime; + + add_type(&p, C_PING, basetime, (uint8_t *) &x, sizeof(x)); + cluster_send_data(buff, (p-buff) ); +} + +// +// Walk the session counters looking for non-zero ones to send +// to the master. We send up to 600 of them at one time. +// We examine a maximum of 3000 sessions. +// (50k max session should mean that we normally +// examine the entire session table every 25 seconds). + +#define MAX_B_RECS (600) +void master_update_counts(void) +{ + int i, c; + bytest b[MAX_B_RECS+1]; + + if (config->cluster_iam_master) // Only happens on the slaves. + return; + + if (!config->cluster_master_address) // If we don't have a master, skip it for a while. + return; + + // C_BYTES format changed in 2.1.0 (cluster version 5) + // during upgrade from previous versions, hang onto our counters + // for a bit until the new master comes up + if (config->cluster_last_hb_ver < 5) + return; + + i = MAX_B_RECS * 5; // Examine max 3000 sessions; + if (config->cluster_highest_sessionid > i) + i = config->cluster_highest_sessionid; + + for ( c = 0; i > 0 ; --i) { + // Next session to look at. + walk_session_number++; + if ( walk_session_number > config->cluster_highest_sessionid) + walk_session_number = 1; + + if (!sess_local[walk_session_number].cin && !sess_local[walk_session_number].cout) + continue; // Unchanged. Skip it. + + b[c].sid = walk_session_number; + b[c].pin = sess_local[walk_session_number].pin; + b[c].pout = sess_local[walk_session_number].pout; + b[c].cin = sess_local[walk_session_number].cin; + b[c].cout = sess_local[walk_session_number].cout; + + // Reset counters. + sess_local[walk_session_number].pin = sess_local[walk_session_number].pout = 0; + sess_local[walk_session_number].cin = sess_local[walk_session_number].cout = 0; + + if (++c > MAX_B_RECS) // Send a max of 600 elements in a packet. + break; + } + + if (!c) // Didn't find any that changes. Get out of here! + return; + + + // Forward the data to the master. + LOG(4, 0, 0, "Sending byte counters to master (%d elements)\n", c); + peer_send_message(config->cluster_master_address, C_BYTES, c, (uint8_t *) &b, sizeof(b[0]) * c); + return; +} + +// +// On the master, check how our slaves are going. If +// one of them's not up-to-date we'll heartbeat faster. +// If we don't have any of them, then we need to turn +// on our own packet handling! +// +void cluster_check_slaves(void) +{ + int i; + static int have_peers = 0; + int had_peers = have_peers; + clockt t = TIME; + + if (!config->cluster_iam_master) + return; // Only runs on the master... + + config->cluster_iam_uptodate = 1; // cleared in loop below + + for (i = have_peers = 0; i < num_peers; i++) + { + if ((peers[i].timestamp + config->cluster_hb_timeout) < t) + continue; // Stale peer! Skip them. + + if (!peers[i].basetime) + continue; // Shutdown peer! Skip them. + + if (peers[i].uptodate) + have_peers++; + else + config->cluster_iam_uptodate = 0; // Start fast heartbeats + } + + // in a cluster, withdraw/add routes when we get a peer/lose peers + if (have_peers != had_peers) + { + if (had_peers < config->cluster_master_min_adv && + have_peers >= config->cluster_master_min_adv) + withdraw_routes(); + + else if (had_peers >= config->cluster_master_min_adv && + have_peers < config->cluster_master_min_adv) + advertise_routes(); + } +} + +// +// Check that we have a master. If it's been too +// long since we heard from a master then hold an election. +// +void cluster_check_master(void) +{ + int i, count, tcount, high_unique_id = 0; + int last_free = 0; + clockt t = TIME; + static int probed = 0; + int have_peers; + + if (config->cluster_iam_master) + return; // Only runs on the slaves... + + // If the master is late (missed 2 hearbeats by a second and a + // hair) it may be that the switch has dropped us from the + // multicast group, try unicasting probes to the master + // which will hopefully respond with a unicast heartbeat that + // will allow us to limp along until the querier next runs. + if (config->cluster_master_address + && TIME > (config->cluster_last_hb + 2 * config->cluster_hb_interval + 11)) + { + if (!probed || (TIME > (probed + 2 * config->cluster_hb_interval))) + { + probed = TIME; + LOG(1, 0, 0, "Heartbeat from master %.1fs late, probing...\n", + 0.1 * (TIME - (config->cluster_last_hb + config->cluster_hb_interval))); + + peer_send_message(config->cluster_master_address, + C_LASTSEEN, config->cluster_seq_number, NULL, 0); + } + } else { // We got a recent heartbeat; reset the probe flag. + probed = 0; + } + + if (TIME < (config->cluster_last_hb + config->cluster_hb_timeout)) + return; // Everything's ok! + + config->cluster_last_hb = TIME + 1; // Just the one election thanks. + config->cluster_master_address = 0; + + LOG(0, 0, 0, "Master timed out! Holding election...\n"); + + // In the process of shutting down, can't be master + if (main_quit) + return; + + for (i = have_peers = 0; i < num_peers; i++) + { + if ((peers[i].timestamp + config->cluster_hb_timeout) < t) + continue; // Stale peer! Skip them. + + if (!peers[i].basetime) + continue; // Shutdown peer! Skip them. + + if (peers[i].basetime < basetime) { + LOG(1, 0, 0, "Expecting %s to become master\n", fmtaddr(peers[i].peer, 0)); + return; // They'll win the election. Get out of here. + } + + if (peers[i].basetime == basetime && + peers[i].peer > my_address) { + LOG(1, 0, 0, "Expecting %s to become master\n", fmtaddr(peers[i].peer, 0)); + return; // They'll win the election. Wait for them to come up. + } + + if (peers[i].uptodate) + have_peers++; + } + + // Wow. it's been ages since I last heard a heartbeat + // and I'm better than an of my peers so it's time + // to become a master!!! + + config->cluster_iam_master = 1; + + LOG(0, 0, 0, "I am declaring myself the master!\n"); + + if (have_peers < config->cluster_master_min_adv) + advertise_routes(); + else + withdraw_routes(); + + if (config->cluster_seq_number == -1) + config->cluster_seq_number = 0; + + // + // Go through and mark all the tunnels as defined. + // Count the highest used tunnel number as well. + // + config->cluster_highest_tunnelid = 0; + for (i = 0, tcount = 0; i < MAXTUNNEL; ++i) { + if (tunnel[i].state == TUNNELUNDEF) + tunnel[i].state = TUNNELFREE; + + if (tunnel[i].state != TUNNELFREE && i > config->cluster_highest_tunnelid) + config->cluster_highest_tunnelid = i; + } + + // + // Go through and mark all the sessions as being defined. + // reset the idle timeouts. + // add temporary byte counters to permanent ones. + // Re-string the free list. + // Find the ID of the highest session. + last_free = 0; + high_unique_id = 0; + config->cluster_highest_sessionid = 0; + for (i = 0, count = 0; i < MAXSESSION; ++i) { + if (session[i].tunnel == T_UNDEF) { + session[i].tunnel = T_FREE; + ++count; + } + + if (!session[i].opened) { // Unused session. Add to free list. + memset(&session[i], 0, sizeof(session[i])); + session[i].tunnel = T_FREE; + session[last_free].next = i; + session[i].next = 0; + last_free = i; + continue; + } + + // Reset idle timeouts.. + session[i].last_packet = time_now; + + // Reset die relative to our uptime rather than the old master's + if (session[i].die) session[i].die = TIME; + + // Accumulate un-sent byte/packet counters. + increment_counter(&session[i].cin, &session[i].cin_wrap, sess_local[i].cin); + increment_counter(&session[i].cout, &session[i].cout_wrap, sess_local[i].cout); + session[i].cin_delta += sess_local[i].cin; + session[i].cout_delta += sess_local[i].cout; + + session[i].pin += sess_local[i].pin; + session[i].pout += sess_local[i].pout; + + sess_local[i].cin = sess_local[i].cout = 0; + sess_local[i].pin = sess_local[i].pout = 0; + + sess_local[i].radius = 0; // Reset authentication as the radius blocks aren't up to date. + + if (session[i].unique_id >= high_unique_id) // This is different to the index into the session table!!! + high_unique_id = session[i].unique_id+1; + + session[i].tbf_in = session[i].tbf_out = 0; // Remove stale pointers from old master. + throttle_session(i, session[i].throttle_in, session[i].throttle_out); + + config->cluster_highest_sessionid = i; + } + + session[last_free].next = 0; // End of chain. + last_id = high_unique_id; // Keep track of the highest used session ID. + + become_master(); + + rebuild_address_pool(); + + // If we're not the very first master, this is a big issue! + if(count>0) + LOG(0, 0, 0, "Warning: Fixed %d uninitialized sessions in becoming master!\n", count); + + config->cluster_undefined_sessions = 0; + config->cluster_undefined_tunnels = 0; + config->cluster_iam_uptodate = 1; // assume all peers are up-to-date + + // FIXME. We need to fix up the tunnel control message + // queue here! There's a number of other variables we + // should also update. +} + + +// +// Check that our session table is validly matching what the +// master has in mind. +// +// In particular, if we have too many sessions marked 'undefined' +// we fix it up here, and we ensure that the 'first free session' +// pointer is valid. +// +static void cluster_check_sessions(int highsession, int freesession_ptr, int hightunnel) +{ + int i; + + sessionfree = freesession_ptr; // Keep the freesession ptr valid. + + if (config->cluster_iam_uptodate) + return; + + if (highsession > config->cluster_undefined_sessions && hightunnel > config->cluster_undefined_tunnels) + return; + + // Clear out defined sessions, counting the number of + // undefs remaining. + config->cluster_undefined_sessions = 0; + for (i = 1 ; i < MAXSESSION; ++i) { + if (i > highsession) { + if (session[i].tunnel == T_UNDEF) session[i].tunnel = T_FREE; // Defined. + continue; + } + + if (session[i].tunnel == T_UNDEF) + ++config->cluster_undefined_sessions; + } + + // Clear out defined tunnels, counting the number of + // undefs remaining. + config->cluster_undefined_tunnels = 0; + for (i = 1 ; i < MAXTUNNEL; ++i) { + if (i > hightunnel) { + if (tunnel[i].state == TUNNELUNDEF) tunnel[i].state = TUNNELFREE; // Defined. + continue; + } + + if (tunnel[i].state == TUNNELUNDEF) + ++config->cluster_undefined_tunnels; + } + + + if (config->cluster_undefined_sessions || config->cluster_undefined_tunnels) { + LOG(2, 0, 0, "Cleared undefined sessions/tunnels. %d sess (high %d), %d tunn (high %d)\n", + config->cluster_undefined_sessions, highsession, config->cluster_undefined_tunnels, hightunnel); + return; + } + + // Are we up to date? + + if (!config->cluster_iam_uptodate) + cluster_uptodate(); +} + +static int hb_add_type(uint8_t **p, int type, int id) +{ + switch (type) { + case C_CSESSION: { // Compressed C_SESSION. + uint8_t c[sizeof(sessiont) * 2]; // Bigger than worst case. + uint8_t *d = (uint8_t *) &session[id]; + uint8_t *orig = d; + int size; + + size = rle_compress( &d, sizeof(sessiont), c, sizeof(c) ); + + // Did we compress the full structure, and is the size actually + // reduced?? + if ( (d - orig) == sizeof(sessiont) && size < sizeof(sessiont) ) { + add_type(p, C_CSESSION, id, c, size); + break; + } + // Failed to compress : Fall through. + } + case C_SESSION: + add_type(p, C_SESSION, id, (uint8_t *) &session[id], sizeof(sessiont)); + break; + + case C_CTUNNEL: { // Compressed C_TUNNEL + uint8_t c[sizeof(tunnelt) * 2]; // Bigger than worst case. + uint8_t *d = (uint8_t *) &tunnel[id]; + uint8_t *orig = d; + int size; + + size = rle_compress( &d, sizeof(tunnelt), c, sizeof(c) ); + + // Did we compress the full structure, and is the size actually + // reduced?? + if ( (d - orig) == sizeof(tunnelt) && size < sizeof(tunnelt) ) { + add_type(p, C_CTUNNEL, id, c, size); + break; + } + // Failed to compress : Fall through. + } + case C_TUNNEL: + add_type(p, C_TUNNEL, id, (uint8_t *) &tunnel[id], sizeof(tunnelt)); + break; + default: + LOG(0, 0, 0, "Found an invalid type in heart queue! (%d)\n", type); + kill(0, SIGTERM); + exit(1); + } + return 0; +} + +// +// Send a heartbeat, incidently sending out any queued changes.. +// +void cluster_heartbeat() +{ + int i, count = 0, tcount = 0; + uint8_t buff[MAX_HEART_SIZE + sizeof(heartt) + sizeof(int) ]; + heartt h; + uint8_t *p = buff; + + if (!config->cluster_iam_master) // Only the master does this. + return; + + config->cluster_table_version += config->cluster_num_changes; + + // Fill out the heartbeat header. + memset(&h, 0, sizeof(h)); + + h.version = HB_VERSION; + h.seq = config->cluster_seq_number; + h.basetime = basetime; + h.clusterid = config->bind_address; // Will this do?? + h.basetime = basetime; + h.highsession = config->cluster_highest_sessionid; + h.freesession = sessionfree; + h.hightunnel = config->cluster_highest_tunnelid; + h.size_sess = sizeof(sessiont); // Just in case. + h.size_tunn = sizeof(tunnelt); + h.interval = config->cluster_hb_interval; + h.timeout = config->cluster_hb_timeout; + h.table_version = config->cluster_table_version; + + add_type(&p, C_HEARTBEAT, HB_VERSION, (uint8_t *) &h, sizeof(h)); + + for (i = 0; i < config->cluster_num_changes; ++i) { + hb_add_type(&p, cluster_changes[i].type, cluster_changes[i].id); + } + + if (p > (buff + sizeof(buff))) { // Did we somehow manage to overun the buffer? + LOG(0, 0, 0, "FATAL: Overran the heartbeat buffer! This is fatal. Exiting. (size %d)\n", (int) (p - buff)); + kill(0, SIGTERM); + exit(1); + } + + // + // Fill out the packet with sessions from the session table... + // (not forgetting to leave space so we can get some tunnels in too ) + while ( (p + sizeof(uint32_t) * 2 + sizeof(sessiont) * 2 ) < (buff + MAX_HEART_SIZE) ) { + + if (!walk_session_number) // session #0 isn't valid. + ++walk_session_number; + + if (count >= config->cluster_highest_sessionid) // If we're a small cluster, don't go wild. + break; + + hb_add_type(&p, C_CSESSION, walk_session_number); + walk_session_number = (1+walk_session_number)%(config->cluster_highest_sessionid+1); // +1 avoids divide by zero. + + ++count; // Count the number of extra sessions we're sending. + } + + // + // Fill out the packet with tunnels from the tunnel table... + // This effectively means we walk the tunnel table more quickly + // than the session table. This is good because stuffing up a + // tunnel is a much bigger deal than stuffing up a session. + // + while ( (p + sizeof(uint32_t) * 2 + sizeof(tunnelt) ) < (buff + MAX_HEART_SIZE) ) { + + if (!walk_tunnel_number) // tunnel #0 isn't valid. + ++walk_tunnel_number; + + if (tcount >= config->cluster_highest_tunnelid) + break; + + hb_add_type(&p, C_CTUNNEL, walk_tunnel_number); + walk_tunnel_number = (1+walk_tunnel_number)%(config->cluster_highest_tunnelid+1); // +1 avoids divide by zero. + + ++tcount; + } + + // + // Did we do something wrong? + if (p > (buff + sizeof(buff))) { // Did we somehow manage to overun the buffer? + LOG(0, 0, 0, "Overran the heartbeat buffer now! This is fatal. Exiting. (size %d)\n", (int) (p - buff)); + kill(0, SIGTERM); + exit(1); + } + + LOG(3, 0, 0, "Sending v%d heartbeat #%d, change #%" PRIu64 " with %d changes " + "(%d x-sess, %d x-tunnels, %d highsess, %d hightun, size %d)\n", + HB_VERSION, h.seq, h.table_version, config->cluster_num_changes, + count, tcount, config->cluster_highest_sessionid, + config->cluster_highest_tunnelid, (int) (p - buff)); + + config->cluster_num_changes = 0; + + send_heartbeat(h.seq, buff, (p-buff) ); // Send out the heartbeat to the cluster, keeping a copy of it. + + config->cluster_seq_number = (config->cluster_seq_number+1)%HB_MAX_SEQ; // Next seq number to use. +} + +// +// A structure of type 'type' has changed; Add it to the queue to send. +// +static int type_changed(int type, int id) +{ + int i; + + for (i = 0 ; i < config->cluster_num_changes ; ++i) + if ( cluster_changes[i].id == id && + cluster_changes[i].type == type) + return 0; // Already marked for change. + + cluster_changes[i].type = type; + cluster_changes[i].id = id; + ++config->cluster_num_changes; + + if (config->cluster_num_changes > MAX_CHANGES) + cluster_heartbeat(); // flush now + + return 1; +} + + +// A particular session has been changed! +int cluster_send_session(int sid) +{ + if (!config->cluster_iam_master) { + LOG(0, sid, 0, "I'm not a master, but I just tried to change a session!\n"); + return -1; + } + + if (forked) { + LOG(0, sid, 0, "cluster_send_session called from child process!\n"); + return -1; + } + + return type_changed(C_CSESSION, sid); +} + +// A particular tunnel has been changed! +int cluster_send_tunnel(int tid) +{ + if (!config->cluster_iam_master) { + LOG(0, 0, tid, "I'm not a master, but I just tried to change a tunnel!\n"); + return -1; + } + + return type_changed(C_CTUNNEL, tid); +} + + +// +// We're a master, and a slave has just told us that it's +// missed a packet. We'll resend it every packet since +// the last one it's seen. +// +static int cluster_catchup_slave(int seq, in_addr_t slave) +{ + int s; + int diff; + + LOG(1, 0, 0, "Slave %s sent LASTSEEN with seq %d\n", fmtaddr(slave, 0), seq); + if (!config->cluster_iam_master) { + LOG(1, 0, 0, "Got LASTSEEN but I'm not a master! Redirecting it to %s.\n", + fmtaddr(config->cluster_master_address, 0)); + + peer_send_message(slave, C_MASTER, config->cluster_master_address, NULL, 0); + return 0; + } + + diff = config->cluster_seq_number - seq; // How many packet do we need to send? + if (diff < 0) + diff += HB_MAX_SEQ; + + if (diff >= HB_HISTORY_SIZE) { // Ouch. We don't have the packet to send it! + LOG(0, 0, 0, "A slave asked for message %d when our seq number is %d. Killing it.\n", + seq, config->cluster_seq_number); + return peer_send_message(slave, C_KILL, seq, NULL, 0);// Kill the slave. Nothing else to do. + } + + LOG(1, 0, 0, "Sending %d catchup packets to slave %s\n", diff, fmtaddr(slave, 0) ); + + // Now resend every packet that it missed, in order. + while (seq != config->cluster_seq_number) { + s = seq % HB_HISTORY_SIZE; + if (seq != past_hearts[s].seq) { + LOG(0, 0, 0, "Tried to re-send heartbeat for %s but %d doesn't match %d! (%d,%d)\n", + fmtaddr(slave, 0), seq, past_hearts[s].seq, s, config->cluster_seq_number); + return -1; // What to do here!? + } + peer_send_data(slave, past_hearts[s].data, past_hearts[s].size); + seq = (seq+1)%HB_MAX_SEQ; // Increment to next seq number. + } + return 0; // All good! +} + +// +// We've heard from another peer! Add it to the list +// that we select from at election time. +// +static int cluster_add_peer(in_addr_t peer, time_t basetime, pingt *pp, int size) +{ + int i; + in_addr_t clusterid; + pingt p; + + // Allow for backward compatability. + // Just the ping packet into a new structure to allow + // for the possibility that we might have received + // more or fewer elements than we were expecting. + if (size > sizeof(p)) + size = sizeof(p); + + memset( (void *) &p, 0, sizeof(p) ); + memcpy( (void *) &p, (void *) pp, size); + + clusterid = p.addr; + if (clusterid != config->bind_address) + { + // Is this for us? + LOG(4, 0, 0, "Skipping ping from %s (different cluster)\n", fmtaddr(peer, 0)); + return 0; + } + + for (i = 0; i < num_peers ; ++i) + { + if (peers[i].peer != peer) + continue; + + // This peer already exists. Just update the timestamp. + peers[i].basetime = basetime; + peers[i].timestamp = TIME; + peers[i].uptodate = !p.undef; + break; + } + + // Is this the master shutting down?? + if (peer == config->cluster_master_address) { + LOG(3, 0, 0, "Master %s %s\n", fmtaddr(config->cluster_master_address, 0), + basetime ? "has restarted!" : "shutting down..."); + + config->cluster_master_address = 0; + config->cluster_last_hb = 0; // Force an election. + cluster_check_master(); + } + + if (i >= num_peers) + { + LOG(4, 0, 0, "Adding %s as a peer\n", fmtaddr(peer, 0)); + + // Not found. Is there a stale slot to re-use? + for (i = 0; i < num_peers ; ++i) + { + if (!peers[i].basetime) // Shutdown + break; + + if ((peers[i].timestamp + config->cluster_hb_timeout * 10) < TIME) // Stale. + break; + } + + if (i >= CLUSTER_MAX_SIZE) + { + // Too many peers!! + LOG(0, 0, 0, "Tried to add %s as a peer, but I already have %d of them!\n", fmtaddr(peer, 0), i); + return -1; + } + + peers[i].peer = peer; + peers[i].basetime = basetime; + peers[i].timestamp = TIME; + peers[i].uptodate = !p.undef; + if (i == num_peers) + ++num_peers; + + LOG(1, 0, 0, "Added %s as a new peer. Now %d peers\n", fmtaddr(peer, 0), num_peers); + } + + return 1; +} + +// A slave responds with C_MASTER when it gets a message which should have gone to a master. +static int cluster_set_master(in_addr_t peer, in_addr_t master) +{ + if (config->cluster_iam_master) // Sanity... + return 0; + + LOG(3, 0, 0, "Peer %s set the master to %s...\n", fmtaddr(peer, 0), + fmtaddr(master, 1)); + + config->cluster_master_address = master; + if (master) + { + // catchup with new master + peer_send_message(master, C_LASTSEEN, config->cluster_seq_number, NULL, 0); + + // delay next election + config->cluster_last_hb = TIME; + } + + // run election (or reset "probed" if master was set) + cluster_check_master(); + return 0; +} + +/* Handle the slave updating the byte counters for the master. */ +// +// Note that we don't mark the session as dirty; We rely on +// the slow table walk to propogate this back out to the slaves. +// +static int cluster_handle_bytes(uint8_t *data, int size) +{ + bytest *b; + + b = (bytest *) data; + + LOG(3, 0, 0, "Got byte counter update (size %d)\n", size); + + /* Loop around, adding the byte + counts to each of the sessions. */ + + while (size >= sizeof(*b) ) { + if (b->sid > MAXSESSION) { + LOG(0, 0, 0, "Got C_BYTES with session #%d!\n", b->sid); + return -1; /* Abort processing */ + } + + session[b->sid].pin += b->pin; + session[b->sid].pout += b->pout; + + increment_counter(&session[b->sid].cin, &session[b->sid].cin_wrap, b->cin); + increment_counter(&session[b->sid].cout, &session[b->sid].cout_wrap, b->cout); + + session[b->sid].cin_delta += b->cin; + session[b->sid].cout_delta += b->cout; + + if (b->cin) + session[b->sid].last_packet = time_now; // Reset idle timer! + + size -= sizeof(*b); + ++b; + } + + if (size != 0) + LOG(0, 0, 0, "Got C_BYTES with %d bytes of trailing junk!\n", size); + + return size; +} + +// +// Handle receiving a session structure in a heartbeat packet. +// +static int cluster_recv_session(int more, uint8_t *p) +{ + if (more >= MAXSESSION) { + LOG(0, 0, 0, "DANGER: Received a heartbeat session id > MAXSESSION!\n"); + return -1; + } + + if (session[more].tunnel == T_UNDEF) { + if (config->cluster_iam_uptodate) { // Sanity. + LOG(0, 0, 0, "I thought I was uptodate but I just found an undefined session!\n"); + } else { + --config->cluster_undefined_sessions; + } + } + + load_session(more, (sessiont *) p); // Copy session into session table.. + + LOG(5, more, 0, "Received session update (%d undef)\n", config->cluster_undefined_sessions); + + if (!config->cluster_iam_uptodate) + cluster_uptodate(); // Check to see if we're up to date. + + return 0; +} + +static int cluster_recv_tunnel(int more, uint8_t *p) +{ + if (more >= MAXTUNNEL) { + LOG(0, 0, 0, "DANGER: Received a tunnel session id > MAXTUNNEL!\n"); + return -1; + } + + if (tunnel[more].state == TUNNELUNDEF) { + if (config->cluster_iam_uptodate) { // Sanity. + LOG(0, 0, 0, "I thought I was uptodate but I just found an undefined tunnel!\n"); + } else { + --config->cluster_undefined_tunnels; + } + } + + memcpy(&tunnel[more], p, sizeof(tunnel[more]) ); + + // + // Clear tunnel control messages. These are dynamically allocated. + // If we get unlucky, this may cause the tunnel to drop! + // + tunnel[more].controls = tunnel[more].controle = NULL; + tunnel[more].controlc = 0; + + LOG(5, 0, more, "Received tunnel update\n"); + + if (!config->cluster_iam_uptodate) + cluster_uptodate(); // Check to see if we're up to date. + + return 0; +} + + +// pre v5 heartbeat session structure +struct oldsession { + sessionidt next; + sessionidt far; + tunnelidt tunnel; + in_addr_t ip; + int ip_pool_index; + unsigned long unique_id; + uint16_t nr; + uint16_t ns; + uint32_t magic; + uint32_t cin, cout; + uint32_t pin, pout; + uint32_t total_cin; + uint32_t total_cout; + uint32_t id; + uint16_t throttle_in; + uint16_t throttle_out; + clockt opened; + clockt die; + time_t last_packet; + in_addr_t dns1, dns2; + routet route[MAXROUTE]; + uint16_t radius; + uint16_t mru; + uint16_t tbf_in; + uint16_t tbf_out; + uint8_t l2tp_flags; + uint8_t reserved_old_snoop; + uint8_t walled_garden; + uint8_t flags1; + char random_vector[MAXTEL]; + int random_vector_length; + char user[129]; + char called[MAXTEL]; + char calling[MAXTEL]; + uint32_t tx_connect_speed; + uint32_t rx_connect_speed; + uint32_t flags; +#define SF_IPCP_ACKED 1 // Has this session seen an IPCP Ack? +#define SF_LCP_ACKED 2 // LCP negotiated +#define SF_CCP_ACKED 4 // CCP negotiated + in_addr_t snoop_ip; + uint16_t snoop_port; + uint16_t sid; + uint8_t filter_in; + uint8_t filter_out; + char reserved[18]; +}; + +static uint8_t *convert_session(struct oldsession *old) +{ + static sessiont new; + int i; + + memset(&new, 0, sizeof(new)); + + new.next = old->next; + new.far = old->far; + new.tunnel = old->tunnel; + new.flags = old->l2tp_flags; + new.ip = old->ip; + new.ip_pool_index = old->ip_pool_index; + new.unique_id = old->unique_id; + new.magic = old->magic; + new.pin = old->pin; + new.pout = old->pout; + new.cin = old->total_cin; + new.cout = old->total_cout; + new.cin_delta = old->cin; + new.cout_delta = old->cout; + new.throttle_in = old->throttle_in; + new.throttle_out = old->throttle_out; + new.filter_in = old->filter_in; + new.filter_out = old->filter_out; + new.mru = old->mru; + new.opened = old->opened; + new.die = old->die; + new.last_packet = old->last_packet; + new.dns1 = old->dns1; + new.dns2 = old->dns2; + new.tbf_in = old->tbf_in; + new.tbf_out = old->tbf_out; + new.random_vector_length = old->random_vector_length; + new.tx_connect_speed = old->tx_connect_speed; + new.rx_connect_speed = old->rx_connect_speed; + new.snoop_ip = old->snoop_ip; + new.snoop_port = old->snoop_port; + new.walled_garden = old->walled_garden; + + memcpy(new.random_vector, old->random_vector, sizeof(new.random_vector)); + memcpy(new.user, old->user, sizeof(new.user)); + memcpy(new.called, old->called, sizeof(new.called)); + memcpy(new.calling, old->calling, sizeof(new.calling)); + + for (i = 0; i < MAXROUTE; i++) + memcpy(&new.route[i], &old->route[i], sizeof(new.route[i])); + + if (new.opened) + { + new.ppp.phase = Establish; + if (old->flags & (SF_IPCP_ACKED|SF_LCP_ACKED)) + { + new.ppp.phase = Network; + new.ppp.lcp = Opened; + new.ppp.ipcp = (old->flags & SF_IPCP_ACKED) ? Opened : Starting; + new.ppp.ccp = (old->flags & SF_CCP_ACKED) ? Opened : Stopped; + } + + // no PPPv6 in old session + new.ppp.ipv6cp = Stopped; + } + + return (uint8_t *) &new; +} + +// +// Process a heartbeat.. +// +// v3: added interval, timeout +// v4: added table_version +// v5: added ipv6, re-ordered session structure +static int cluster_process_heartbeat(uint8_t *data, int size, int more, uint8_t *p, in_addr_t addr) +{ + heartt *h; + int s = size - (p-data); + int i, type; + int hb_ver = more; + +#if HB_VERSION != 5 +# error "need to update cluster_process_heartbeat()" +#endif + + // we handle versions 3 through 5 + if (hb_ver < 3 || hb_ver > HB_VERSION) { + LOG(0, 0, 0, "Received a heartbeat version that I don't support (%d)!\n", hb_ver); + return -1; // Ignore it?? + } + + if (size > sizeof(past_hearts[0].data)) { + LOG(0, 0, 0, "Received an oversize heartbeat from %s (%d)!\n", fmtaddr(addr, 0), size); + return -1; + } + + if (s < sizeof(*h)) + goto shortpacket; + + h = (heartt *) p; + p += sizeof(*h); + s -= sizeof(*h); + + if (h->clusterid != config->bind_address) + return -1; // It's not part of our cluster. + + if (config->cluster_iam_master) { // Sanity... + // Note that this MUST match the election process above! + + LOG(0, 0, 0, "I just got a heartbeat from master %s, but _I_ am the master!\n", fmtaddr(addr, 0)); + if (!h->basetime) { + LOG(0, 0, 0, "Heartbeat with zero basetime! Ignoring\n"); + return -1; // Skip it. + } + + if (hb_ver >= 4) { + if (h->table_version > config->cluster_table_version) { + LOG(0, 0, 0, "They've seen more state changes (%" PRIu64 " vs my %" PRIu64 ") so I'm gone!\n", + h->table_version, config->cluster_table_version); + + kill(0, SIGTERM); + exit(1); + } + if (h->table_version < config->cluster_table_version) + return -1; + } + + if (basetime > h->basetime) { + LOG(0, 0, 0, "They're an older master than me so I'm gone!\n"); + kill(0, SIGTERM); + exit(1); + } + + if (basetime < h->basetime) + return -1; + + if (my_address < addr) { // Tie breaker. + LOG(0, 0, 0, "They're a higher IP address than me, so I'm gone!\n"); + kill(0, SIGTERM); + exit(1); + } + + // + // Send it a unicast heartbeat to see give it a chance to die. + // NOTE: It's actually safe to do seq-number - 1 without checking + // for wrap around. + // + cluster_catchup_slave(config->cluster_seq_number - 1, addr); + + return -1; // Skip it. + } + + // + // Try and guard against a stray master appearing. + // + // Ignore heartbeats received from another master before the + // timeout (less a smidgen) for the old master has elapsed. + // + // Note that after a clean failover, the cluster_master_address + // is cleared, so this doesn't run. + // + if (config->cluster_master_address && addr != config->cluster_master_address) { + LOG(0, 0, 0, "Ignoring stray heartbeat from %s, current master %s has not yet timed out (last heartbeat %.1f seconds ago).\n", + fmtaddr(addr, 0), fmtaddr(config->cluster_master_address, 1), + 0.1 * (TIME - config->cluster_last_hb)); + return -1; // ignore + } + + if (config->cluster_seq_number == -1) // Don't have one. Just align to the master... + config->cluster_seq_number = h->seq; + + config->cluster_last_hb = TIME; // Reset to ensure that we don't become master!! + config->cluster_last_hb_ver = hb_ver; // remember what cluster version the master is using + + if (config->cluster_seq_number != h->seq) { // Out of sequence heartbeat! + static int lastseen_seq = 0; + static time_t lastseen_time = 0; + + // limit to once per second for a particular seq# + int ask = (config->cluster_seq_number != lastseen_seq || time_now != lastseen_time); + + LOG(1, 0, 0, "HB: Got seq# %d but was expecting %d. %s.\n", + h->seq, config->cluster_seq_number, + ask ? "Asking for resend" : "Ignoring"); + + if (ask) + { + lastseen_seq = config->cluster_seq_number; + lastseen_time = time_now; + peer_send_message(addr, C_LASTSEEN, config->cluster_seq_number, NULL, 0); + } + + config->cluster_last_hb = TIME; // Reset to ensure that we don't become master!! + + // Just drop the packet. The master will resend it as part of the catchup. + + return 0; + } + // Save the packet in our buffer. + // This is needed in case we become the master. + config->cluster_seq_number = (h->seq+1)%HB_MAX_SEQ; + i = h->seq % HB_HISTORY_SIZE; + past_hearts[i].seq = h->seq; + past_hearts[i].size = size; + memcpy(&past_hearts[i].data, data, size); // Save it. + + + // Check that we don't have too many undefined sessions, and + // that the free session pointer is correct. + cluster_check_sessions(h->highsession, h->freesession, h->hightunnel); + + if (h->interval != config->cluster_hb_interval) + { + LOG(2, 0, 0, "Master set ping/heartbeat interval to %u (was %u)\n", + h->interval, config->cluster_hb_interval); + + config->cluster_hb_interval = h->interval; + } + + if (h->timeout != config->cluster_hb_timeout) + { + LOG(2, 0, 0, "Master set heartbeat timeout to %u (was %u)\n", + h->timeout, config->cluster_hb_timeout); + + config->cluster_hb_timeout = h->timeout; + } + + // Ok. process the packet... + while ( s > 0) { + + type = *((uint32_t *) p); + p += sizeof(uint32_t); + s -= sizeof(uint32_t); + + more = *((uint32_t *) p); + p += sizeof(uint32_t); + s -= sizeof(uint32_t); + + switch (type) { + case C_CSESSION: { // Compressed session structure. + uint8_t c[ sizeof(sessiont) + 2]; + int size; + uint8_t *orig_p = p; + + size = rle_decompress((uint8_t **) &p, s, c, sizeof(c) ); + s -= (p - orig_p); + + // session struct changed with v5 + if (hb_ver < 5) + { + if (size != sizeof(struct oldsession)) { + LOG(0, 0, 0, "DANGER: Received a v%d CSESSION that didn't decompress correctly!\n", hb_ver); + // Now what? Should exit! No-longer up to date! + break; + } + cluster_recv_session(more, convert_session((struct oldsession *) c)); + break; + } + + if (size != sizeof(sessiont) ) { // Ouch! Very very bad! + LOG(0, 0, 0, "DANGER: Received a CSESSION that didn't decompress correctly!\n"); + // Now what? Should exit! No-longer up to date! + break; + } + + cluster_recv_session(more, c); + break; + } + case C_SESSION: + if (hb_ver < 5) + { + if (s < sizeof(struct oldsession)) + goto shortpacket; + + cluster_recv_session(more, convert_session((struct oldsession *) p)); + + p += sizeof(struct oldsession); + s -= sizeof(struct oldsession); + break; + } + + if ( s < sizeof(session[more])) + goto shortpacket; + + cluster_recv_session(more, p); + + p += sizeof(session[more]); + s -= sizeof(session[more]); + break; + + case C_CTUNNEL: { // Compressed tunnel structure. + uint8_t c[ sizeof(tunnelt) + 2]; + int size; + uint8_t *orig_p = p; + + size = rle_decompress((uint8_t **) &p, s, c, sizeof(c)); + s -= (p - orig_p); + + if (size != sizeof(tunnelt) ) { // Ouch! Very very bad! + LOG(0, 0, 0, "DANGER: Received a CTUNNEL that didn't decompress correctly!\n"); + // Now what? Should exit! No-longer up to date! + break; + } + + cluster_recv_tunnel(more, c); + break; + + } + case C_TUNNEL: + if ( s < sizeof(tunnel[more])) + goto shortpacket; + + cluster_recv_tunnel(more, p); + + p += sizeof(tunnel[more]); + s -= sizeof(tunnel[more]); + break; + default: + LOG(0, 0, 0, "DANGER: I received a heartbeat element where I didn't understand the type! (%d)\n", type); + return -1; // can't process any more of the packet!! + } + } + + if (config->cluster_master_address != addr) + { + LOG(0, 0, 0, "My master just changed from %s to %s!\n", + fmtaddr(config->cluster_master_address, 0), fmtaddr(addr, 1)); + + config->cluster_master_address = addr; + } + + config->cluster_last_hb = TIME; // Successfully received a heartbeat! + config->cluster_table_version = h->table_version; + return 0; + +shortpacket: + LOG(0, 0, 0, "I got an incomplete heartbeat packet! This means I'm probably out of sync!!\n"); + return -1; +} + +// +// We got a packet on the cluster port! +// Handle pings, lastseens, and heartbeats! +// +int processcluster(uint8_t *data, int size, in_addr_t addr) +{ + int type, more; + uint8_t *p = data; + int s = size; + + if (addr == my_address) + return -1; // Ignore it. Something looped back the multicast! + + LOG(5, 0, 0, "Process cluster: %d bytes from %s\n", size, fmtaddr(addr, 0)); + + if (s <= 0) // Any data there?? + return -1; + + if (s < 8) + goto shortpacket; + + type = *((uint32_t *) p); + p += sizeof(uint32_t); + s -= sizeof(uint32_t); + + more = *((uint32_t *) p); + p += sizeof(uint32_t); + s -= sizeof(uint32_t); + + switch (type) + { + case C_PING: // Update the peers table. + return cluster_add_peer(addr, more, (pingt *) p, s); + + case C_MASTER: // Our master is wrong + return cluster_set_master(addr, more); + + case C_LASTSEEN: // Catch up a slave (slave missed a packet). + return cluster_catchup_slave(more, addr); + + case C_FORWARD: // Forwarded control packet. pass off to processudp. + case C_FORWARD_DAE: // Forwarded DAE packet. pass off to processdae. + if (!config->cluster_iam_master) + { + LOG(0, 0, 0, "I'm not the master, but I got a C_FORWARD_%s from %s?\n", + type == C_FORWARD_DAE ? "_DAE" : "", fmtaddr(addr, 0)); + + return -1; + } + else + { + struct sockaddr_in a; + a.sin_addr.s_addr = more; + + a.sin_port = *(int *) p; + s -= sizeof(int); + p += sizeof(int); + + LOG(4, 0, 0, "Got a forwarded %spacket... (%s:%d)\n", + type == C_FORWARD_DAE ? "DAE " : "", fmtaddr(more, 0), a.sin_port); + + STAT(recv_forward); + if (type == C_FORWARD_DAE) + { + struct in_addr local; + local.s_addr = config->bind_address ? config->bind_address : my_address; + processdae(p, s, &a, sizeof(a), &local); + } + else + processudp(p, s, &a); + + return 0; + } + + case C_THROTTLE: { // Receive a forwarded packet from a slave. + if (!config->cluster_iam_master) { + LOG(0, 0, 0, "I'm not the master, but I got a C_THROTTLE from %s?\n", fmtaddr(addr, 0)); + return -1; + } + + tbf_queue_packet(more, p, s); // The TBF id tells wether it goes in or out. + return 0; + } + case C_GARDEN: + // Receive a walled garden packet from a slave. + if (!config->cluster_iam_master) { + LOG(0, 0, 0, "I'm not the master, but I got a C_GARDEN from %s?\n", fmtaddr(addr, 0)); + return -1; + } + + tun_write(p, s); + return 0; + + case C_BYTES: + if (!config->cluster_iam_master) { + LOG(0, 0, 0, "I'm not the master, but I got a C_BYTES from %s?\n", fmtaddr(addr, 0)); + return -1; + } + + return cluster_handle_bytes(p, s); + + case C_KILL: // The master asked us to die!? (usually because we're too out of date). + if (config->cluster_iam_master) { + LOG(0, 0, 0, "_I_ am master, but I received a C_KILL from %s! (Seq# %d)\n", fmtaddr(addr, 0), more); + return -1; + } + if (more != config->cluster_seq_number) { + LOG(0, 0, 0, "The master asked us to die but the seq number didn't match!?\n"); + return -1; + } + + if (addr != config->cluster_master_address) { + LOG(0, 0, 0, "Received a C_KILL from %s which doesn't match config->cluster_master_address (%s)\n", + fmtaddr(addr, 0), fmtaddr(config->cluster_master_address, 1)); + // We can only warn about it. The master might really have switched! + } + + LOG(0, 0, 0, "Received a valid C_KILL: I'm going to die now.\n"); + kill(0, SIGTERM); + exit(0); // Lets be paranoid; + return -1; // Just signalling the compiler. + + case C_HEARTBEAT: + LOG(4, 0, 0, "Got a heartbeat from %s\n", fmtaddr(addr, 0)); + return cluster_process_heartbeat(data, size, more, p, addr); + + default: + LOG(0, 0, 0, "Strange type packet received on cluster socket (%d)\n", type); + return -1; + } + return 0; + +shortpacket: + LOG(0, 0, 0, "I got a _short_ cluster heartbeat packet! This means I'm probably out of sync!!\n"); + return -1; +} + +//==================================================================================================== + +int cmd_show_cluster(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + + if (CLI_HELP_REQUESTED) + return CLI_HELP_NO_ARGS; + + cli_print(cli, "Cluster status : %s", config->cluster_iam_master ? "Master" : "Slave" ); + cli_print(cli, "My address : %s", fmtaddr(my_address, 0)); + cli_print(cli, "VIP address : %s", fmtaddr(config->bind_address, 0)); + cli_print(cli, "Multicast address: %s", fmtaddr(config->cluster_address, 0)); + cli_print(cli, "Multicast i'face : %s", config->cluster_interface); + + if (!config->cluster_iam_master) { + cli_print(cli, "My master : %s (last heartbeat %.1f seconds old)", + config->cluster_master_address + ? fmtaddr(config->cluster_master_address, 0) + : "Not defined", + 0.1 * (TIME - config->cluster_last_hb)); + cli_print(cli, "Uptodate : %s", config->cluster_iam_uptodate ? "Yes" : "No"); + cli_print(cli, "Table version # : %" PRIu64, config->cluster_table_version); + cli_print(cli, "Next sequence number expected: %d", config->cluster_seq_number); + cli_print(cli, "%d sessions undefined of %d", config->cluster_undefined_sessions, config->cluster_highest_sessionid); + cli_print(cli, "%d tunnels undefined of %d", config->cluster_undefined_tunnels, config->cluster_highest_tunnelid); + } else { + cli_print(cli, "Table version # : %" PRIu64, config->cluster_table_version); + cli_print(cli, "Next heartbeat # : %d", config->cluster_seq_number); + cli_print(cli, "Highest session : %d", config->cluster_highest_sessionid); + cli_print(cli, "Highest tunnel : %d", config->cluster_highest_tunnelid); + cli_print(cli, "%d changes queued for sending", config->cluster_num_changes); + } + cli_print(cli, "%d peers.", num_peers); + + if (num_peers) + cli_print(cli, "%20s %10s %8s", "Address", "Basetime", "Age"); + for (i = 0; i < num_peers; ++i) { + cli_print(cli, "%20s %10u %8d", fmtaddr(peers[i].peer, 0), + peers[i].basetime, TIME - peers[i].timestamp); + } + return CLI_OK; +} + +// +// Simple run-length-encoding compression. +// Format is +// 1 byte < 128 = count of non-zero bytes following. // Not legal to be zero. +// n non-zero bytes; +// or +// 1 byte > 128 = (count - 128) run of zero bytes. // +// repeat. +// count == 0 indicates end of compressed stream. +// +// Compress from 'src' into 'dst'. return number of bytes +// used from 'dst'. +// Updates *src_p to indicate 1 past last bytes used. +// +// We could get an extra byte in the zero runs by storing (count-1) +// but I'm playing it safe. +// +// Worst case is a 50% expansion in space required (trying to +// compress { 0x00, 0x01 } * N ) +static int rle_compress(uint8_t **src_p, int ssize, uint8_t *dst, int dsize) +{ + int count; + int orig_dsize = dsize; + uint8_t *x, *src; + src = *src_p; + + while (ssize > 0 && dsize > 2) { + count = 0; + x = dst++; --dsize; // Reserve space for count byte.. + + if (*src) { // Copy a run of non-zero bytes. + while (*src && count < 127 && ssize > 0 && dsize > 1) { // Count number of non-zero bytes. + *dst++ = *src++; + --dsize; --ssize; + ++count; + } + *x = count; // Store number of non-zero bytes. Guarenteed to be non-zero! + + } else { // Compress a run of zero bytes. + while (*src == 0 && count < 127 && ssize > 0) { + ++src; + --ssize; + ++count; + } + *x = count | 0x80 ; + } + } + + *dst++ = 0x0; // Add Stop byte. + --dsize; + + *src_p = src; + return (orig_dsize - dsize); +} + +// +// Decompress the buffer into **p. +// 'psize' is the size of the decompression buffer available. +// +// Returns the number of bytes decompressed. +// +// Decompresses from '*src_p' into 'dst'. +// Return the number of dst bytes used. +// Updates the 'src_p' pointer to point to the +// first un-used byte. +static int rle_decompress(uint8_t **src_p, int ssize, uint8_t *dst, int dsize) +{ + int count; + int orig_dsize = dsize; + uint8_t *src = *src_p; + + while (ssize >0 && dsize > 0) { // While there's more to decompress, and there's room in the decompress buffer... + count = *src++; --ssize; // get the count byte from the source. + if (count == 0x0) // End marker reached? If so, finish. + break; + + if (count & 0x80) { // Decompress a run of zeros + for (count &= 0x7f ; count > 0 && dsize > 0; --count) { + *dst++ = 0x0; + --dsize; + } + } else { // Copy run of non-zero bytes. + for ( ; count > 0 && ssize && dsize; --count) { // Copy non-zero bytes across. + *dst++ = *src++; + --ssize; --dsize; + } + } + } + *src_p = src; + return (orig_dsize - dsize); +} diff --git a/cluster.h b/cluster.h new file mode 100644 index 0000000..fe95218 --- /dev/null +++ b/cluster.h @@ -0,0 +1,89 @@ +// L2TPNS Clustering Stuff +// $Id: cluster.h,v 1.14 2005/07/31 10:04:10 bodea Exp $ + +#ifndef __CLUSTER_H__ +#define __CLUSTER_H__ + + +#define C_HEARTBEAT 1 +#define C_ACK 2 +#define C_PING 3 +#define C_TUNNEL 4 // Tunnel structure. +#define C_SESSION 5 // Session structure. +#define C_GOODBYE 6 +#define C_LASTSEEN 7 // Tell master the last heartbeat that I handled. +#define C_KILL 8 // Tell a slave to die. +#define C_FORWARD 9 // Forwarded packet.. +#define C_BYTES 10 // Update byte counters. +#define C_THROTTLE 11 // A packet for the master to throttle. (The TBF tells direction). +#define C_CSESSION 12 // Compressed session structure. +#define C_CTUNNEL 13 // Compressed tunnel structure. +#define C_GARDEN 14 // Gardened packet +#define C_MASTER 15 // Tell a slave the address of the master. +#define C_FORWARD_DAE 16 // A DAE packet for the master to handle + +#define HB_VERSION 5 // Protocol version number.. +#define HB_MAX_SEQ (1<<30) // Maximum sequence number. (MUST BE A POWER OF 2!) +#define HB_HISTORY_SIZE 64 // How many old heartbeats we remember?? (Must be a factor of HB_MAX_SEQ) + +#define PING_INTERVAL 5 // 0.5 seconds. Needs to be short to keep session tables fresh. +#define HB_TIMEOUT (15*2*PING_INTERVAL) // 15 seconds without heartbeat triggers an election.. + +#define CLUSTERPORT 32792 +#define CLUSTER_MAX_SIZE 32 // No more than 32 machines in a cluster! + +#define DEFAULT_MCAST_ADDR "239.192.13.13" // Need an assigned number! +#define DEFAULT_MCAST_INTERFACE "eth0" + +typedef struct { + uint32_t version; // protocol version. + uint32_t seq; // Sequence number for this heatbeat. + uint32_t basetime; // What time I started + uint32_t clusterid; // Id for this cluster? + + uint32_t highsession; // Id of the highest in-use session. + uint32_t freesession; // Id of the first free session. + uint32_t hightunnel; // Id of the highest used tunnel. + uint32_t size_sess; // Size of the session structure. + + uint32_t size_tunn; // size of the tunnel structure. + uint32_t interval; // ping/heartbeat interval + uint32_t timeout; // heartbeat timeout + + uint64_t table_version; // # state changes processed by cluster + + char reserved[128 - 13*sizeof(uint32_t)]; // Pad out to 128 bytes. +} heartt; + +typedef struct { /* Used to update byte counters on the */ + /* master. */ + uint32_t sid; + uint32_t pin; + uint32_t pout; + uint32_t cin; + uint32_t cout; +} bytest; + +typedef struct { + in_addr_t addr; // peer address + uint32_t ver; // version of structure. + uint32_t undef; // Number of undefined structures. 0 if up-to-date. + uint32_t basetime; // start time of this peer. +} pingt; + +int cluster_init(void); +int processcluster(uint8_t *buf, int size, in_addr_t addr); +int cluster_send_session(int sid); +int cluster_send_tunnel(int tid); +int master_forward_packet(uint8_t *data, int size, in_addr_t addr, int port); +int master_forward_dae_packet(uint8_t *data, int size, in_addr_t addr, int port); +int master_throttle_packet(int tid, uint8_t *data, int size); +int master_garden_packet(sessionidt s, uint8_t *data, int size); +void master_update_counts(void); +void cluster_send_ping(time_t basetime); +void cluster_heartbeat(void); +void cluster_check_master(void); +void cluster_check_slaves(void); +int cmd_show_cluster(struct cli_def *cli, char *command, char **argv, int argc); + +#endif /* __CLUSTER_H__ */ diff --git a/constants.c b/constants.c new file mode 100644 index 0000000..4efe717 --- /dev/null +++ b/constants.c @@ -0,0 +1,227 @@ +// L2TPNS: constants + +char const *cvs_id_constants = "$Id: constants.c,v 1.7 2005/07/31 10:04:10 bodea Exp $"; + +#include +#include "constants.h" + +#define CONSTANT(table, ...) \ + static char const *table ## s[] = { \ + __VA_ARGS__ \ + }; \ + char const *table(int index) \ + { \ + static char n[16]; \ + if (index >= 0 && index < sizeof(table ## s) / sizeof(table ## s[0]) \ + && table ## s[index]) \ + return table ## s[index]; \ + snprintf(n, sizeof(n), "%d", index); \ + return n; \ + } + +CONSTANT(l2tp_code, + 0, // 0 + "SCCRQ", // 1 + "SCCRP", // 2 + "SCCCN", // 3 + "StopCCN", // 4 + 0, // 5 + "HELLO", // 6 + "OCRQ", // 7 + "OCRP", // 8 + "OCCN", // 9 + "ICRQ", // 10 + "ICRP", // 11 + "ICCN", // 12 + 0, // 13 + "CDN", // 14 + "WEN", // 15 + "SLI" // 16 +) + +CONSTANT(l2tp_avp_name, + "Message Type", // 0 + "Result Code", // 1 + "Protocol Version", // 2 + "Framing Capabilities", // 3 + "Bearer Capabilities", // 4 + "Tie Breaker", // 5 + "Firmware Revision", // 6 + "Host Name", // 7 + "Vendor Name", // 8 + "Assigned Tunnel ID", // 9 + "Receive Window Size", // 10 + "Challenge", // 11 + "Q.931 Cause Code", // 12 + "Challenge Response", // 13 + "Assigned Session ID", // 14 + "Call Serial Number", // 15 + "Minimum BPS", // 16 + "Maximum BPS", // 17 + "Bearer Type", // 18 (2 = Analog, 1 = Digital) + "Framing Type", // 19 (2 = Async, 1 = Sync) + 0, // 20 + "Called Number", // 21 + "Calling Number", // 22 + "Sub Address", // 23 + "Tx Connect Speed", // 24 + "Physical Channel ID", // 25 + "Initial Received LCP CONFREQ", // 26 + "Last Sent LCP CONFREQ", // 27 + "Last Received LCP CONFREQ", // 28 + "Proxy Authen Type", // 29 + "Proxy Authen Name", // 30 + "Proxy Authen Challenge", // 31 + "Proxy Authen ID", // 32 + "Proxy Authen Response", // 33 + "Call Errors", // 34 + "ACCM", // 35 + "Random Vector", // 36 + "Private Group ID", // 37 + "Rx Connect Speed", // 38 + "Sequencing Required" // 39 +) + +CONSTANT(l2tp_stopccn_result_code, + 0, // 0 + "General request to clear control connection", // 1 + "General error--Error Code indicates the problem", // 2 + "Control channel already exists", // 3 + "Requester is not authorized to establish a" + " control channel", // 4 + "The protocol version of the requester is not" + " supported", // 5 + "Requester is being shut down", // 6 + "Finite State Machine error" // 7 +) + +CONSTANT(l2tp_cdn_result_code, + 0, // 0 + "Call disconnected due to loss of carrier", // 1 + "Call disconnected for the reason indicated in" + " error code", // 2 + "Call disconnected for administrative reasons", // 3 + "Call failed due to lack of appropriate facilities" + " being available (temporary condition)", // 4 + "Call failed due to lack of appropriate facilities" + " being available (permanent condition)", // 5 + "Invalid destination", // 6 + "Call failed due to no carrier detected", // 7 + "Call failed due to detection of a busy signal", // 8 + "Call failed due to lack of a dial tone", // 9 + "Call was not established within time allotted by" + " LAC", // 10 + "Call was connected but no appropriate framing was" + " detected" // 11 +) + +CONSTANT(l2tp_error_code, + "No general error", // 0 + "No control connection exists yet for this LAC-LNS" + " pair", // 1 + "Length is wrong", // 2 + "One of the field values was out of range or" + " reserved field was non-zero", // 3 + "Insufficient resources to handle this operation" + " now", // 4 + "The Session ID is invalid in this context", // 5 + "A generic vendor-specific error occurred in the" + " LAC", // 6 + "Try another LNS", // 7 + "Session or tunnel was shutdown due to receipt of" + " an unknown AVP with the M-bit set" // 8 +) + +CONSTANT(ppp_phase, + "Dead", // 0 + "Establish", // 1 + "Authenticate", // 2 + "Network", // 3 + "Terminate", // 4 +) + +CONSTANT(ppp_state, + "Initial", // 0 + "Starting", // 1 + "Closed", // 2 + "Stopped", // 3 + "Closing", // 4 + "Stopping", // 5 + "Request-Sent", // 6 + "Ack-Received", // 7 + "Ack-Sent", // 8 + "Opened" // 9 +) + +CONSTANT(ppp_auth_type, + 0, // 0 + "Textual username/password exchange", // 1 + "PPP CHAP", // 2 + "PPP PAP", // 3 + "No Authentication", // 4 + "Microsoft CHAP Version 1 (MSCHAPv1)" // 5 +) + +CONSTANT(ppp_code, + 0, // 0 + "ConfigReq", // 1 + "ConfigAck", // 2 + "ConfigNak", // 3 + "ConfigRej", // 4 + "TerminateReq", // 5 + "TerminateAck", // 6 + "CodeRej", // 7 + "ProtocolRej", // 8 + "EchoReq", // 9 + "EchoReply", // 10 + "DiscardRequest", // 11 + "IdentRequest" // 12 +) + +CONSTANT(ppp_lcp_option, + 0, // 0 + "Maximum-Receive-Unit", // 1 + "Async-Control-Map", // 2 + "Authentication-Protocol", // 3 + "Quality-Protocol", // 4 + "Magic-Number", // 5 + 0, // 6 + "Protocol-Field-Compression", // 7 + "Address-and-Control-Field-Compression" // 8 +) + +CONSTANT(radius_state, + "RADIUSNULL", // 0 + "RADIUSCHAP", // 1 + "RADIUSAUTH", // 2 + "RADIUSSTART", // 3 + "RADIUSSTOP", // 4 + "RADIUSINTERIM", // 5 + "RADIUSWAIT" // 6 +) + +CONSTANT(radius_code, + 0, // 0 + "Access-Request", // 1 + "Access-Accept", // 2 + "Access-Reject", // 3 + "Accounting-Request", // 4 + "Accounting-Response", // 5 + 0, // 6 + 0, // 7 + 0, // 8 + 0, // 9 + 0, // 10 + "Access-Challenge", // 11 + "Status-Server", // 12 + "Status-Client", // 13 + 0, 0, 0, 0, 0, 0, // 14-19 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20-29 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 30-39 + "Disconnect-Request", // 40 + "Disconnect-ACK", // 41 + "Disconnect-NAK", // 42 + "CoA-Request", // 43 + "CoA-ACK", // 44 + "CoA-NAK" // 45 +) diff --git a/constants.h b/constants.h new file mode 100644 index 0000000..a9693c3 --- /dev/null +++ b/constants.h @@ -0,0 +1,17 @@ +#ifndef __CONSTANTS_H__ +#define __CONSTANTS_H__ + +char const *l2tp_code(int type); +char const *l2tp_avp_name(int avp); +char const *l2tp_stopccn_result_code(int code); +char const *l2tp_cdn_result_code(int code); +char const *l2tp_error_code(int code); +char const *ppp_phase(int code); +char const *ppp_state(int code); +char const *ppp_auth_type(int type); +char const *ppp_code(int type); +char const *ppp_lcp_option(int type); +char const *radius_state(int state); +char const *radius_code(int code); + +#endif /* __CONSTANTS_H__ */ diff --git a/control.c b/control.c new file mode 100644 index 0000000..b87ee07 --- /dev/null +++ b/control.c @@ -0,0 +1,163 @@ +// L2TPNS: control + +char const *cvs_id_control = "$Id: control.c,v 1.5 2005/07/31 10:04:10 bodea Exp $"; + +#include +#include "l2tpns.h" +#include "control.h" + +int pack_control(uint8_t *data, int len, uint8_t type, int argc, char *argv[]) +{ + struct nsctl_packet pkt; + struct nsctl_args arg; + char *p = pkt.argv; + int sz = (p - (char *) &pkt); + + if (len > sizeof(pkt)) + len = sizeof(pkt); + + if (argc > 0xff) + argc = 0xff; // paranoia + + pkt.magic = ntohs(NSCTL_MAGIC); + pkt.type = type; + pkt.argc = argc; + + while (argc-- > 0) + { + char *a = *argv++; + int s = strlen(a); + + if (s > sizeof(arg.value)) + s = sizeof(arg.value); // silently truncate + + arg.len = s; + s += sizeof(arg.len); + + if (sz + s > len) + return -1; // overflow + + if (arg.len) + memcpy(arg.value, a, arg.len); + + memcpy(p, &arg, s); + sz += s; + p += s; + } + + /* + * terminate: this is both a sanity check and additionally + * ensures that there's a spare byte in the packet to null + * terminate the last argument when unpacking (see unpack_control) + */ + if (sz + sizeof(arg.len) > len) + return -1; // overflow + + arg.len = 0xff; + memcpy(p, &arg.len, sizeof(arg.len)); + + sz += sizeof(arg.len); + memcpy(data, &pkt, sz); + + return sz; +} + +int unpack_control(struct nsctl *control, uint8_t *data, int len) +{ + struct nsctl_packet pkt; + char *p = pkt.argv; + int sz = (p - (char *) &pkt); + int i; + + if (len < sz) + return NSCTL_ERR_SHORT; + + if (len > sizeof(pkt)) + return NSCTL_ERR_LONG; + + memcpy(&pkt, data, len); + if (ntohs(pkt.magic) != NSCTL_MAGIC) + return NSCTL_ERR_MAGIC; + + switch (pkt.type) + { + case NSCTL_REQ_LOAD: + case NSCTL_REQ_UNLOAD: + case NSCTL_REQ_HELP: + case NSCTL_REQ_CONTROL: + case NSCTL_RES_OK: + case NSCTL_RES_ERR: + control->type = pkt.type; + break; + + default: + return NSCTL_ERR_TYPE; + } + + control->argc = pkt.argc; + for (i = 0; i <= control->argc; i++) + { + unsigned s; + + if (len < sz + 1) + return NSCTL_ERR_SHORT; + + s = (uint8_t) *p; + *p++ = 0; // null terminate previous arg + sz++; + + if (i < control->argc) + { + if (len < sz + s) + return NSCTL_ERR_SHORT; + + control->argv[i] = p; + p += s; + sz += s; + } + else + { + /* check for terminator */ + if (s != 0xff) + return NSCTL_ERR_SHORT; + } + } + + if (sz != len) + return NSCTL_ERR_LONG; // trailing cr*p + + return control->type; +} + +void dump_control(struct nsctl *control, FILE *stream) +{ + char *type = "*unknown*"; + + if (!stream) + stream = stdout; + + switch (control->type) + { + case NSCTL_REQ_LOAD: type = "NSCTL_REQ_LOAD"; break; + case NSCTL_REQ_UNLOAD: type = "NSCTL_REQ_UNLOAD"; break; + case NSCTL_REQ_HELP: type = "NSCTL_REQ_HELP"; break; + case NSCTL_REQ_CONTROL: type = "NSCTL_REQ_CONTROL"; break; + case NSCTL_RES_OK: type = "NSCTL_RES_OK"; break; + case NSCTL_RES_ERR: type = "NSCTL_RES_ERR"; break; + } + + fprintf(stream, "Control packet:\n"); + fprintf(stream, " Type: %d (%s)\n", (int) control->type, type); + fprintf(stream, " Args: %d", (int) control->argc); + if (control->argc) + { + int i; + fprintf(stream, " (\""); + for (i = 0; i < control->argc; i++) + fprintf(stream, "%s%s", i ? "\", \"" : "", control->argv[i]); + + fprintf(stream, "\")"); + } + + fprintf(stream, "\n\n"); +} diff --git a/control.h b/control.h new file mode 100644 index 0000000..e1f7d54 --- /dev/null +++ b/control.h @@ -0,0 +1,54 @@ +#ifndef __CONTROL_H__ +#define __CONTROL_H__ + +#define NSCTL_PORT 1702 +#define NSCTL_MAGIC 0x9013 + +/* builtin commands */ +#define NSCTL_REQUEST (1 << 4) +#define NSCTL_REQ_LOAD (NSCTL_REQUEST | 1) +#define NSCTL_REQ_UNLOAD (NSCTL_REQUEST | 2) +#define NSCTL_REQ_HELP (NSCTL_REQUEST | 3) + +/* general control message, passed to plugins */ +#define NSCTL_REQ_CONTROL (NSCTL_REQUEST | 4) + +/* response messages */ +#define NSCTL_RESPONSE (1 << 5) +#define NSCTL_RES_OK (NSCTL_RESPONSE | 1) +#define NSCTL_RES_ERR (NSCTL_RESPONSE | 2) + +/* unpack errors */ +#define NSCTL_ERR_SHORT -1 // short packet +#define NSCTL_ERR_LONG -2 // packet exceeds max, or trailing cr*p +#define NSCTL_ERR_MAGIC -3 // invalid magic number +#define NSCTL_ERR_TYPE -4 // unrecognised type + +#define NSCTL_MAX_PKT_SZ 4096 + +struct nsctl_packet { + uint16_t magic; + uint8_t type; + uint8_t argc; + char argv[NSCTL_MAX_PKT_SZ - 4]; +} __attribute__ ((packed)); + +#define NSCTL_MAX_ARG_SZ 512 + +struct nsctl_args { + uint8_t len; + char value[NSCTL_MAX_ARG_SZ - 1]; +} __attribute__ ((packed)); + +/* parsed packet */ +struct nsctl { + uint8_t type; + uint8_t argc; + char *argv[0xff]; +}; + +int pack_control(uint8_t *data, int len, uint8_t type, int argc, char *argv[]); +int unpack_control(struct nsctl *packet, uint8_t *data, int len); +void dump_control(struct nsctl *control, FILE *stream); + +#endif /* __CONTROL_H__ */ diff --git a/etc/ip_pool.default b/etc/ip_pool.default new file mode 100644 index 0000000..9ae05e9 --- /dev/null +++ b/etc/ip_pool.default @@ -0,0 +1,2 @@ +10.10.10.0/24 +10.13.10.0/24 diff --git a/etc/l2tpns.logrotate b/etc/l2tpns.logrotate new file mode 100644 index 0000000..d937c3a --- /dev/null +++ b/etc/l2tpns.logrotate @@ -0,0 +1,9 @@ +/var/log/l2tpns { + daily + missingok + rotate 14 + compress + postrotate + /usr/bin/killall -HUP l2tpns + endscript +} diff --git a/etc/startup-config.default b/etc/startup-config.default new file mode 100644 index 0000000..0c5051c --- /dev/null +++ b/etc/startup-config.default @@ -0,0 +1,117 @@ +# Debugging level +set debug 3 + +# Log file: comment out to use stderr, use "syslog:facility" for syslog +set log_file "/var/log/l2tpns" + +# Write pid to this file +set pid_file "/var/run/l2tpns.pid" + +# Shared secret with LAC +set l2tp_secret "secret" + +# MTU of interface for L2TP traffic +#set l2tp_mtu 1500 + +# PPP counter and timer values +#set ppp_restart_time 3 +#set ppp_max_configure 10 +#set ppp_max_failure 5 + +# Only 2 DNS server entries are allowed +set primary_dns 10.0.0.1 +set secondary_dns 10.0.0.2 + +# Can have multiple radius server entries, but ony one radius secret +set primary_radius 10.0.0.3 +#set primary_radius_port 1645 +#set secondary_radius 0.0.0.0 +#set secondary_radius_port 1645 +set radius_secret "secret" + +# Acceptable authentication types (pap, chap) in order of preference +#set radius_authtypes "pap" + +# Turn on or off Radius Accounting +#set radius_accounting no + +# Port for DAE RADIUS requests +#set radius_dae_port 3799 + +# Allow multiple logins for the same username +#set allow_duplicate_users no + +# Write usage accounting files into specified directory +set accounting_dir "/var/run/l2tpns/acct" + +# Listen address for L2TP +#set bind_address 1.1.1.1 + +# Send a gratiuitous ARP for bind address +#set send_garp no + +# Gateway address given to clients +#set peer_address 0.0.0.0 + +# Default throttle rate in kb/s +#set throttle_speed 0 + +# Number of buckets to allocate for throttling +#set throttle_buckets 3000 + +# If set to anything other than 0, setuid when initialised. +#set setuid 0 + +# If set to true, dump current speed to stderr every second +#set dump_speed no + +# Number of packets to read from tun/udp/cluster fd when select +# returns readable +#set multi_read_count 10 + +# Set scheduling priority of process to SCHED_FIFO +#set scheduler_fifo no + +# Lock pages into memory +#set lock_pages no + +# Maximum number of host unreachable packets to send per second +#set icmp_rate 0 + +# Maximum number of downstream packets per 0.1s to handle for each +# session (0 = ulimited) +#set packet_limit 0 + +# Cluster multicast address, interface +#set cluster_address 239.192.13.13 +#set cluster_interface eth0 + +# Cluster multicast TTL +#set cluster_mcast_ttl 1 + +# Cluster timers (1/10th second) +#set cluster_hb_interval 5 +#set cluster_hb_timeout 150 + +# Minimum number of slaves before master withdraws routes +#set cluster_master_min_adv 1 + +# Drop/kill sessions +#load plugin "sessionctl" + +# Throttle/snoop based on RADIUS +#load plugin "autothrottle" +#load plugin "autosnoop" + +# Control throttle/snoop with nsctl +#load plugin "throttlectl" +#load plugin "snoopctl" + +# Punt RX speed if not supplied +#load plugin "setrxspeed" + +# Remove domain from username +#load plugin "stripdomain" + +# Walled garden +#load plugin "garden" diff --git a/etc/users.default b/etc/users.default new file mode 100644 index 0000000..dd67351 --- /dev/null +++ b/etc/users.default @@ -0,0 +1 @@ +# List username:password combinations here for cli users diff --git a/fake_epoll.h b/fake_epoll.h new file mode 100644 index 0000000..d89f86a --- /dev/null +++ b/fake_epoll.h @@ -0,0 +1,179 @@ +/* kludge up some limited epoll semantics using select for 2.4 kernels */ +/* $Id: fake_epoll.h,v 1.1 2005/06/04 15:42:35 bodea Exp $ */ + +#ifndef __FAKE_EPOLL_H__ +#define __FAKE_EPOLL_H__ + +#define EPOLLIN 0x01 +#define EPOLLOUT 0x04 +#define EPOLLERR 0x08 +#define EPOLLHUP 0x10 + +#define EPOLL_CTL_ADD 1 +#define EPOLL_CTL_DEL 2 +#define EPOLL_CTL_MOD 3 + +struct epoll_event { + uint32_t events; + union epoll_data { + void *ptr; + int fd; + uint32_t u32; + uint64_t u64; + } data; +}; + +int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); + +#ifdef FAKE_EPOLL_IMPLEMENTATION + +#include + +static fd_set _epoll_read_set; +static fd_set _epoll_write_set; +static int _epoll_fds; +static struct epoll_event *_epoll_data[128]; + +static int epoll_create(int size __attribute__ ((unused))) +{ + static int once = 0; + if (once++) + { + errno = ENFILE; /* only support one instance */ + return -1; + } + + FD_ZERO(&_epoll_read_set); + FD_ZERO(&_epoll_write_set); + _epoll_fds = 0; + + memset(_epoll_data, 0, sizeof(_epoll_data)); + + return 1; /* "descriptor" */ +} + +int epoll_ctl(int epfd __attribute__ ((unused)), int op, int fd, + struct epoll_event *event) +{ + if (fd > (sizeof(_epoll_data)/sizeof(*_epoll_data)) - 1) + { + errno = EINVAL; + return -1; + } + + switch (op) + { + case EPOLL_CTL_ADD: + if (event->events & EPOLLIN) + FD_SET(fd, &_epoll_read_set); + + if (event->events & EPOLLOUT) + FD_SET(fd, &_epoll_write_set); + + if (fd >= _epoll_fds) + _epoll_fds = fd + 1; + + if (_epoll_data[fd]) + free(_epoll_data[fd]); + + if (!(_epoll_data[fd] = malloc(sizeof(*_epoll_data)))) + { + errno = ENOMEM; + return -1; + } + + memcpy(_epoll_data[fd], &event->data, sizeof(*_epoll_data)); + break; + + case EPOLL_CTL_MOD: + if (event->events & EPOLLIN) + FD_SET(fd, &_epoll_read_set); + else + FD_CLR(fd, &_epoll_read_set); + + if (event->events & EPOLLOUT) + FD_SET(fd, &_epoll_write_set); + else + FD_CLR(fd, &_epoll_write_set); + + memcpy(_epoll_data[fd], &event->data, sizeof(*_epoll_data)); + break; + + case EPOLL_CTL_DEL: + FD_CLR(fd, &_epoll_read_set); + FD_CLR(fd, &_epoll_write_set); + + free(_epoll_data[fd]); + _epoll_data[fd] = 0; + + if (fd == _epoll_fds - 1) + { + _epoll_fds = 0; + while (fd-- > 0) + { + if (FD_ISSET(fd, &_epoll_read_set) || + FD_ISSET(fd, &_epoll_write_set)) + { + _epoll_fds = fd + 1; + break; + } + } + } + + break; + } + + return 0; +} + +static int epoll_wait(int epfd __attribute__ ((unused)), + struct epoll_event *events, int maxevents, int timout) +{ + fd_set r; + fd_set w; + struct timeval t; + struct timeval *tp; + int n; + int e; + int i; + + memcpy(&r, &_epoll_read_set, sizeof(r)); + memcpy(&w, &_epoll_write_set, sizeof(w)); + + if (timout >= 0) + { + t.tv_sec = 0; + t.tv_usec = timout * 1000; + tp = &t; + } + else + tp = 0; + + n = select(_epoll_fds, &r, &w, 0, tp); + if (n > maxevents) + n = maxevents; + + for (i = e = 0; n > 0 && i < _epoll_fds; i++) + { + if (!_epoll_data[i]) + continue; + + events[e].events = 0; + if (FD_ISSET(i, &r)) + events[e].events |= EPOLLIN; + + if (FD_ISSET(i, &w)) + events[e].events |= EPOLLOUT; + + if (events[e].events) + { + memcpy(&events[e++].data, _epoll_data[i], sizeof(events[0].data)); + n--; + } + } + + return e; +} + +#endif /* FAKE_EPOLL_IMPLEMENTATION */ +#endif /* __FAKE_EPOLL_H__ */ diff --git a/garden.c b/garden.c new file mode 100644 index 0000000..44b9597 --- /dev/null +++ b/garden.c @@ -0,0 +1,297 @@ +#include +#include +#include +#include +#include +#include "l2tpns.h" +#include "plugin.h" +#include "control.h" + +/* walled garden */ + +char const *cvs_id = "$Id: garden.c,v 1.25 2006/02/23 01:07:23 bodea Exp $"; + +int plugin_api_version = PLUGIN_API_VERSION; +static struct pluginfuncs *f = 0; + +static int iam_master = 0; // We're all slaves! Slaves I tell you! + +char *up_commands[] = { + "iptables -t nat -N garden >/dev/null 2>&1", // Create a chain that all gardened users will go through + "iptables -t nat -F garden", + ". " PLUGINCONF "/build-garden", // Populate with site-specific DNAT rules + "iptables -t nat -N garden_users >/dev/null 2>&1", // Empty chain, users added/removed by garden_session + "iptables -t nat -F garden_users", + "iptables -t nat -A PREROUTING -j garden_users", // DNAT any users on the garden_users chain + "sysctl -w net.ipv4.netfilter.ip_conntrack_max=512000" // lots of entries + " net.ipv4.netfilter.ip_conntrack_tcp_timeout_established=18000 >/dev/null", // 5hrs + NULL, +}; + +char *down_commands[] = { + "iptables -t nat -F PREROUTING", + "iptables -t nat -F garden_users", + "iptables -t nat -X garden_users", + "iptables -t nat -F garden", + "iptables -t nat -X garden", + "rmmod iptable_nat", // Should also remove ip_conntrack, but + // doing so can take hours... literally. + // If a master is re-started as a slave, + // either rmmod manually, or reboot. + NULL, +}; + +#define F_UNGARDEN 0 +#define F_GARDEN 1 +#define F_CLEANUP 2 + +int garden_session(sessiont *s, int flag, char *newuser); + +int plugin_post_auth(struct param_post_auth *data) +{ + // Ignore if user authentication was successful + if (data->auth_allowed) + return PLUGIN_RET_OK; + + f->log(3, f->get_id_by_session(data->s), data->s->tunnel, + "Walled Garden allowing login\n"); + + data->auth_allowed = 1; + data->s->walled_garden = 1; + return PLUGIN_RET_OK; +} + +int plugin_new_session(struct param_new_session *data) +{ + if (!iam_master) + return PLUGIN_RET_OK; // Slaves don't do walled garden processing. + + if (data->s->walled_garden) + garden_session(data->s, F_GARDEN, 0); + + return PLUGIN_RET_OK; +} + +int plugin_kill_session(struct param_new_session *data) +{ + if (!iam_master) + return PLUGIN_RET_OK; // Slaves don't do walled garden processing. + + if (data->s->walled_garden) + garden_session(data->s, F_CLEANUP, 0); + + return PLUGIN_RET_OK; +} + +char *plugin_control_help[] = { + " garden USER|SID Put user into the walled garden", + " ungarden SID [USER] Release session from garden", + 0 +}; + +int plugin_control(struct param_control *data) +{ + sessionidt session; + sessiont *s = 0; + int flag; + char *end; + + if (data->argc < 1) + return PLUGIN_RET_OK; + + if (strcmp(data->argv[0], "garden") && strcmp(data->argv[0], "ungarden")) + return PLUGIN_RET_OK; // not for us + + if (!iam_master) + return PLUGIN_RET_NOTMASTER; + + flag = data->argv[0][0] == 'g' ? F_GARDEN : F_UNGARDEN; + + if (data->argc < 2 || data->argc > 3 || (data->argc > 2 && flag == F_GARDEN)) + { + data->response = NSCTL_RES_ERR; + data->additional = flag == F_GARDEN + ? "requires username or session id" + : "requires session id and optional username"; + + return PLUGIN_RET_STOP; + } + + if (!(session = strtol(data->argv[1], &end, 10)) || *end) + { + if (flag) + session = f->get_session_by_username(data->argv[1]); + else + session = 0; // can't ungarden by username + } + + if (session) + s = f->get_session_by_id(session); + + if (!s || !s->ip) + { + data->response = NSCTL_RES_ERR; + data->additional = "session not found"; + return PLUGIN_RET_STOP; + } + + if (s->walled_garden == flag) + { + data->response = NSCTL_RES_ERR; + data->additional = flag ? "already in walled garden" : "not in walled garden"; + return PLUGIN_RET_STOP; + } + + garden_session(s, flag, data->argc > 2 ? data->argv[2] : 0); + f->session_changed(session); + + data->response = NSCTL_RES_OK; + data->additional = 0; + + return PLUGIN_RET_STOP; +} + +int plugin_become_master(void) +{ + int i; + iam_master = 1; // We just became the master. Wow! + + for (i = 0; up_commands[i] && *up_commands[i]; i++) + { + f->log(3, 0, 0, "Running %s\n", up_commands[i]); + system(up_commands[i]); + } + + return PLUGIN_RET_OK; +} + +// Called for each active session after becoming master +int plugin_new_session_master(sessiont *s) +{ + if (s->walled_garden) + garden_session(s, F_GARDEN, 0); + + return PLUGIN_RET_OK; +} + +int garden_session(sessiont *s, int flag, char *newuser) +{ + char cmd[2048]; + sessionidt sess; + + if (!s) return 0; + if (!s->opened) return 0; + + sess = f->get_id_by_session(s); + if (flag == F_GARDEN) + { + f->log(2, sess, s->tunnel, "Garden user %s (%s)\n", s->user, + f->fmtaddr(htonl(s->ip), 0)); + + snprintf(cmd, sizeof(cmd), + "iptables -t nat -A garden_users -s %s -j garden", + f->fmtaddr(htonl(s->ip), 0)); + + f->log(3, sess, s->tunnel, "%s\n", cmd); + system(cmd); + s->walled_garden = 1; + } + else + { + sessionidt other; + int count = 40; + + // Normal User + f->log(2, sess, s->tunnel, "Un-Garden user %s (%s)\n", s->user, f->fmtaddr(htonl(s->ip), 0)); + if (newuser) + { + snprintf(s->user, MAXUSER, "%s", newuser); + f->log(2, sess, s->tunnel, " Setting username to %s\n", s->user); + } + + // Kick off any duplicate usernames + // but make sure not to kick off ourself + if (s->ip && !s->die && (other = f->get_session_by_username(s->user)) && + s != f->get_session_by_id(other)) + { + f->sessionkill(other, + "Duplicate session when user released from walled garden"); + } + + /* Clean up counters */ + s->pin = s->pout = 0; + s->cin = s->cout = 0; + s->cin_delta = s->cout_delta = 0; + s->cin_wrap = s->cout_wrap = 0; + + snprintf(cmd, sizeof(cmd), + "iptables -t nat -D garden_users -s %s -j garden", + f->fmtaddr(htonl(s->ip), 0)); + + f->log(3, sess, s->tunnel, "%s\n", cmd); + while (--count) + { + int status = system(cmd); + if (WEXITSTATUS(status) != 0) break; + } + + s->walled_garden = 0; + + if (flag != F_CLEANUP) + { + /* OK, we're up! */ + uint16_t r = f->radiusnew(f->get_id_by_session(s)); + if (r) f->radiussend(r, RADIUSSTART); + } + } + + return 1; +} + +int plugin_init(struct pluginfuncs *funcs) +{ + FILE *tables; + int found_nat = 0; + + if (!funcs) + return 0; + + f = funcs; + + if ((tables = fopen("/proc/net/ip_tables_names", "r"))) + { + char buf[1024]; + while (fgets(buf, sizeof(buf), tables) && !found_nat) + found_nat = !strcmp(buf, "nat\n"); + + fclose(tables); + } + + /* master killed/crashed? */ + if (found_nat) + { + int i; + for (i = 0; down_commands[i] && *down_commands[i]; i++) + { + f->log(3, 0, 0, "Running %s\n", down_commands[i]); + system(down_commands[i]); + } + } + + return 1; +} + +void plugin_done() +{ + int i; + + if (!iam_master) // Never became master. nothing to do. + return; + + for (i = 0; down_commands[i] && *down_commands[i]; i++) + { + f->log(3, 0, 0, "Running %s\n", down_commands[i]); + system(down_commands[i]); + } +} + diff --git a/icmp.c b/icmp.c new file mode 100644 index 0000000..f98e401 --- /dev/null +++ b/icmp.c @@ -0,0 +1,177 @@ +// L2TPNS: icmp + +char const *cvs_id_icmp = "$Id: icmp.c,v 1.10 2005/08/10 11:25:56 bodea Exp $"; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "l2tpns.h" + +static uint16_t _checksum(uint8_t *addr, int count); + +struct ipv6_pseudo_hdr { + struct in6_addr src; + struct in6_addr dest; + uint32_t ulp_length; + uint32_t zero : 24; + uint32_t nexthdr : 8; +}; + +void host_unreachable(in_addr_t destination, uint16_t id, in_addr_t source, uint8_t *packet, int packet_len) +{ + char buf[128] = {0}; + struct iphdr *iph; + struct icmphdr *icmp; + int len = 0, on = 1, icmp_socket; + struct sockaddr_in whereto = {0}; + + if ((icmp_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) + return; + + setsockopt(icmp_socket, IPPROTO_IP, IP_HDRINCL, (char *)&on, sizeof(on)); + + whereto.sin_addr.s_addr = destination; + whereto.sin_family = AF_INET; + + iph = (struct iphdr *)(buf); + len = sizeof(struct iphdr); + icmp = (struct icmphdr *)(buf + len); + len += sizeof(struct icmphdr); + + /* ip header + first 8 bytes of payload */ + if (packet_len > (sizeof(struct iphdr) + 8)) + packet_len = sizeof(struct iphdr) + 8; + + memcpy(buf + len, packet, packet_len); + len += packet_len; + + iph->tos = 0; + iph->id = id; + iph->frag_off = 0; + iph->ttl = 30; + iph->check = 0; + iph->version = 4; + iph->ihl = 5; + iph->protocol = 1; + iph->check = 0; + iph->daddr = destination; + iph->saddr = source; + + iph->tot_len = ntohs(len); + + icmp->type = ICMP_DEST_UNREACH; + icmp->code = ICMP_HOST_UNREACH; + icmp->checksum = _checksum((uint8_t *) icmp, sizeof(struct icmphdr) + packet_len); + + iph->check = _checksum((uint8_t *) iph, sizeof(struct iphdr)); + + sendto(icmp_socket, buf, len, 0, (struct sockaddr *)&whereto, sizeof(struct sockaddr)); + close(icmp_socket); +} + +static uint16_t _checksum(uint8_t *addr, int count) +{ + register long sum = 0; + + for (; count > 1; count -= 2) + { + sum += ntohs(*(uint32_t *) addr); + addr += 2; + } + + if (count > 1) sum += *(unsigned char *)addr; + + // take only 16 bits out of the 32 bit sum and add up the carries + while (sum >> 16) + sum = (sum & 0xFFFF) + (sum >> 16); + + // one's complement the result + sum = ~sum; + + return htons((uint16_t) sum); +} + +void send_ipv6_ra(sessionidt s, tunnelidt t, struct in6_addr *ip) +{ + struct nd_opt_prefix_info *pinfo; + struct ipv6_pseudo_hdr *phdr; + uint8_t b[MAXETHER + 20]; + uint8_t c[MAXETHER + 20]; + int l; + uint8_t *o; + + LOG(3, s, t, "Sending IPv6 RA\n"); + + memset(b, 0, sizeof(b)); + o = makeppp(b, sizeof(b), 0, 0, s, t, PPPIPV6); + + if (!o) + { + LOG(3, s, t, "failed to send IPv6 RA\n"); + return; + } + + *o = 0x60; // IPv6 + *(o+1) = 0; + *(o+5) = 48; // Length of payload (not header) + *(o+6) = 58; // icmp6 is next + *(o+7) = 255; // Hop limit + memset(o+8, 0, 16); // source = FE80::1 + *(o+8) = 0xFE; + *(o+9) = 0x80; + *(o+23) = 1; + if (ip != NULL) + memcpy(o+24, ip, 16); // dest = ip + else + { + // FF02::1 - all hosts + *(o+24) = 0xFF; + *(o+25) = 2; + *(o+39) = 1; + } + *(o+40) = 134; // RA message + *(o+41) = 0; // Code + *(o+42) = *(o+43) = 0; // Checksum + *(o+44) = 64; // Hop count + *(o+45) = 0; // Flags + *(o+46) = *(o+47) = 255; // Lifetime + *(uint32_t *)(o+48) = 0; // Reachable time + *(uint32_t *)(o+52) = 0; // Retrans timer + pinfo = (struct nd_opt_prefix_info *)(o+56); + pinfo->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION; + pinfo->nd_opt_pi_len = 4; + pinfo->nd_opt_pi_prefix_len = 64; // prefix length + pinfo->nd_opt_pi_flags_reserved = ND_OPT_PI_FLAG_ONLINK; + pinfo->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_AUTO; + pinfo->nd_opt_pi_valid_time = htonl(2592000); + pinfo->nd_opt_pi_preferred_time = htonl(604800); + pinfo->nd_opt_pi_reserved2 = 0; + pinfo->nd_opt_pi_prefix = config->ipv6_prefix; + l = sizeof(*pinfo) + 56; + + memset(c, 0, sizeof(c)); + phdr = (struct ipv6_pseudo_hdr *) c; + memcpy(&phdr->src, o+8, 16); + memcpy(&phdr->dest, o+24, 16); + phdr->ulp_length = htonl(l - 40); + phdr->nexthdr = IPPROTO_ICMPV6; + + memcpy(c + sizeof(*phdr), o + 40, l - 40); + + // Checksum is over the icmp6 payload plus the pseudo header + *(uint16_t *)(o+42) = _checksum(c, l - 40 + sizeof(*phdr)); + + tunnelsend(b, l + (o-b), t); // send it... + return; +} diff --git a/l2tpns.c b/l2tpns.c new file mode 100644 index 0000000..b4eca2e --- /dev/null +++ b/l2tpns.c @@ -0,0 +1,5489 @@ +// L2TP Network Server +// Adrian Kennard 2002 +// Copyright (c) 2003, 2004, 2005 Optus Internet Engineering +// Copyright (c) 2002 FireBrick (Andrews & Arnold Ltd / Watchfront Ltd) - GPL licenced +// vim: sw=8 ts=8 + +char const *cvs_id_l2tpns = "$Id: l2tpns.c,v 1.161.2.1 2006/06/22 15:30:50 bodea Exp $"; + +#include +#include +#include +#include +#include +#define SYSLOG_NAMES +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "md5.h" +#include "l2tpns.h" +#include "cluster.h" +#include "plugin.h" +#include "ll.h" +#include "constants.h" +#include "control.h" +#include "util.h" +#include "tbf.h" + +#ifdef BGP +#include "bgp.h" +#endif + +// Globals +configt *config = NULL; // all configuration +int tunfd = -1; // tun interface file handle. (network device) +int udpfd = -1; // UDP file handle +int controlfd = -1; // Control signal handle +int clifd = -1; // Socket listening for CLI connections. +int daefd = -1; // Socket listening for DAE connections. +int snoopfd = -1; // UDP file handle for sending out intercept data +int *radfds = NULL; // RADIUS requests file handles +int ifrfd = -1; // File descriptor for routing, etc +int ifr6fd = -1; // File descriptor for IPv6 routing, etc +int rand_fd = -1; // Random data source +int cluster_sockfd = -1; // Intra-cluster communications socket. +int epollfd = -1; // event polling +time_t basetime = 0; // base clock +char hostname[1000] = ""; // us. +static int tunidx; // ifr_ifindex of tun device +static int syslog_log = 0; // are we logging to syslog +static FILE *log_stream = 0; // file handle for direct logging (i.e. direct into file, not via syslog). +uint32_t last_id = 0; // Unique ID for radius accounting + +// calculated from config->l2tp_mtu +uint16_t MRU = 0; // PPP MRU +uint16_t MSS = 0; // TCP MSS + +struct cli_session_actions *cli_session_actions = NULL; // Pending session changes requested by CLI +struct cli_tunnel_actions *cli_tunnel_actions = NULL; // Pending tunnel changes required by CLI + +static void *ip_hash[256]; // Mapping from IP address to session structures. +struct ipv6radix { + int sess; + struct ipv6radix *branch; +} ipv6_hash[256]; // Mapping from IPv6 address to session structures. + +// Traffic counters. +static uint32_t udp_rx = 0, udp_rx_pkt = 0, udp_tx = 0; +static uint32_t eth_rx = 0, eth_rx_pkt = 0; +uint32_t eth_tx = 0; + +static uint32_t ip_pool_size = 1; // Size of the pool of addresses used for dynamic address allocation. +time_t time_now = 0; // Current time in seconds since epoch. +static char time_now_string[64] = {0}; // Current time as a string. +static int time_changed = 0; // time_now changed +char main_quit = 0; // True if we're in the process of exiting. +static char main_reload = 0; // Re-load pending +linked_list *loaded_plugins; +linked_list *plugins[MAX_PLUGIN_TYPES]; + +#define membersize(STRUCT, MEMBER) sizeof(((STRUCT *)0)->MEMBER) +#define CONFIG(NAME, MEMBER, TYPE) { NAME, offsetof(configt, MEMBER), membersize(configt, MEMBER), TYPE } + +config_descriptt config_values[] = { + CONFIG("debug", debug, INT), + CONFIG("log_file", log_filename, STRING), + CONFIG("pid_file", pid_file, STRING), + CONFIG("random_device", random_device, STRING), + CONFIG("l2tp_secret", l2tp_secret, STRING), + CONFIG("l2tp_mtu", l2tp_mtu, INT), + CONFIG("ppp_restart_time", ppp_restart_time, INT), + CONFIG("ppp_max_configure", ppp_max_configure, INT), + CONFIG("ppp_max_failure", ppp_max_failure, INT), + CONFIG("primary_dns", default_dns1, IPv4), + CONFIG("secondary_dns", default_dns2, IPv4), + CONFIG("primary_radius", radiusserver[0], IPv4), + CONFIG("secondary_radius", radiusserver[1], IPv4), + CONFIG("primary_radius_port", radiusport[0], SHORT), + CONFIG("secondary_radius_port", radiusport[1], SHORT), + CONFIG("radius_accounting", radius_accounting, BOOL), + CONFIG("radius_interim", radius_interim, INT), + CONFIG("radius_secret", radiussecret, STRING), + CONFIG("radius_authtypes", radius_authtypes_s, STRING), + CONFIG("radius_dae_port", radius_dae_port, SHORT), + CONFIG("allow_duplicate_users", allow_duplicate_users, BOOL), + CONFIG("bind_address", bind_address, IPv4), + CONFIG("peer_address", peer_address, IPv4), + CONFIG("send_garp", send_garp, BOOL), + CONFIG("throttle_speed", rl_rate, UNSIGNED_LONG), + CONFIG("throttle_buckets", num_tbfs, INT), + CONFIG("accounting_dir", accounting_dir, STRING), + CONFIG("setuid", target_uid, INT), + CONFIG("dump_speed", dump_speed, BOOL), + CONFIG("multi_read_count", multi_read_count, INT), + CONFIG("scheduler_fifo", scheduler_fifo, BOOL), + CONFIG("lock_pages", lock_pages, BOOL), + CONFIG("icmp_rate", icmp_rate, INT), + CONFIG("packet_limit", max_packets, INT), + CONFIG("cluster_address", cluster_address, IPv4), + CONFIG("cluster_interface", cluster_interface, STRING), + CONFIG("cluster_mcast_ttl", cluster_mcast_ttl, INT), + CONFIG("cluster_hb_interval", cluster_hb_interval, INT), + CONFIG("cluster_hb_timeout", cluster_hb_timeout, INT), + CONFIG("cluster_master_min_adv", cluster_master_min_adv, INT), + CONFIG("ipv6_prefix", ipv6_prefix, IPv6), + { NULL, 0, 0, 0 }, +}; + +static char *plugin_functions[] = { + NULL, + "plugin_pre_auth", + "plugin_post_auth", + "plugin_packet_rx", + "plugin_packet_tx", + "plugin_timer", + "plugin_new_session", + "plugin_kill_session", + "plugin_control", + "plugin_radius_response", + "plugin_radius_reset", + "plugin_radius_account", + "plugin_become_master", + "plugin_new_session_master", +}; + +#define max_plugin_functions (sizeof(plugin_functions) / sizeof(char *)) + +// Counters for shutdown sessions +static sessiont shut_acct[8192]; +static sessionidt shut_acct_n = 0; + +tunnelt *tunnel = NULL; // Array of tunnel structures. +sessiont *session = NULL; // Array of session structures. +sessionlocalt *sess_local = NULL; // Array of local per-session counters. +radiust *radius = NULL; // Array of radius structures. +ippoolt *ip_address_pool = NULL; // Array of dynamic IP addresses. +ip_filtert *ip_filters = NULL; // Array of named filters. +static controlt *controlfree = 0; +struct Tstats *_statistics = NULL; +#ifdef RINGBUFFER +struct Tringbuffer *ringbuffer = NULL; +#endif + +static void cache_ipmap(in_addr_t ip, int s); +static void uncache_ipmap(in_addr_t ip); +static void cache_ipv6map(struct in6_addr ip, int prefixlen, int s); +static void free_ip_address(sessionidt s); +static void dump_acct_info(int all); +static void sighup_handler(int sig); +static void shutdown_handler(int sig); +static void sigchild_handler(int sig); +static void build_chap_response(uint8_t *challenge, uint8_t id, uint16_t challenge_length, uint8_t **challenge_response); +static void update_config(void); +static void read_config_file(void); +static void initplugins(void); +static int add_plugin(char *plugin_name); +static int remove_plugin(char *plugin_name); +static void plugins_done(void); +static void processcontrol(uint8_t *buf, int len, struct sockaddr_in *addr, int alen, struct in_addr *local); +static tunnelidt new_tunnel(void); +static void unhide_value(uint8_t *value, size_t len, uint16_t type, uint8_t *vector, size_t vec_len); + +// on slaves, alow BGP to withdraw cleanly before exiting +#define QUIT_DELAY 5 + +// quit actions (master) +#define QUIT_FAILOVER 1 // SIGTERM: exit when all control messages have been acked (for cluster failover) +#define QUIT_SHUTDOWN 2 // SIGQUIT: shutdown sessions/tunnels, reject new connections + +// return internal time (10ths since process startup), set f if given +// as a side-effect sets time_now, and time_changed +static clockt now(double *f) +{ + struct timeval t; + gettimeofday(&t, 0); + if (f) *f = t.tv_sec + t.tv_usec / 1000000.0; + if (t.tv_sec != time_now) + { + time_now = t.tv_sec; + time_changed++; + } + return (t.tv_sec - basetime) * 10 + t.tv_usec / 100000 + 1; +} + +// work out a retry time based on try number +// This is a straight bounded exponential backoff. +// Maximum re-try time is 32 seconds. (2^5). +clockt backoff(uint8_t try) +{ + if (try > 5) try = 5; // max backoff + return now(NULL) + 10 * (1 << try); +} + + +// +// Log a debug message. Typically called via the LOG macro +// +void _log(int level, sessionidt s, tunnelidt t, const char *format, ...) +{ + static char message[65536] = {0}; + va_list ap; + +#ifdef RINGBUFFER + if (ringbuffer) + { + if (++ringbuffer->tail >= RINGBUFFER_SIZE) + ringbuffer->tail = 0; + if (ringbuffer->tail == ringbuffer->head) + if (++ringbuffer->head >= RINGBUFFER_SIZE) + ringbuffer->head = 0; + + ringbuffer->buffer[ringbuffer->tail].level = level; + ringbuffer->buffer[ringbuffer->tail].session = s; + ringbuffer->buffer[ringbuffer->tail].tunnel = t; + va_start(ap, format); + vsnprintf(ringbuffer->buffer[ringbuffer->tail].message, 4095, format, ap); + va_end(ap); + } +#endif + + if (config->debug < level) return; + + va_start(ap, format); + vsnprintf(message, sizeof(message), format, ap); + + if (log_stream) + fprintf(log_stream, "%s %02d/%02d %s", time_now_string, t, s, message); + else if (syslog_log) + syslog(level + 2, "%02d/%02d %s", t, s, message); // We don't need LOG_EMERG or LOG_ALERT + + va_end(ap); +} + +void _log_hex(int level, const char *title, const uint8_t *data, int maxsize) +{ + int i, j; + const uint8_t *d = data; + + if (config->debug < level) return; + + // No support for _log_hex to syslog + if (log_stream) + { + _log(level, 0, 0, "%s (%d bytes):\n", title, maxsize); + setvbuf(log_stream, NULL, _IOFBF, 16384); + + for (i = 0; i < maxsize; ) + { + fprintf(log_stream, "%4X: ", i); + for (j = i; j < maxsize && j < (i + 16); j++) + { + fprintf(log_stream, "%02X ", d[j]); + if (j == i + 7) + fputs(": ", log_stream); + } + + for (; j < i + 16; j++) + { + fputs(" ", log_stream); + if (j == i + 7) + fputs(": ", log_stream); + } + + fputs(" ", log_stream); + for (j = i; j < maxsize && j < (i + 16); j++) + { + if (d[j] >= 0x20 && d[j] < 0x7f && d[j] != 0x20) + fputc(d[j], log_stream); + else + fputc('.', log_stream); + + if (j == i + 7) + fputs(" ", log_stream); + } + + i = j; + fputs("\n", log_stream); + } + + fflush(log_stream); + setbuf(log_stream, NULL); + } +} + +// update a counter, accumulating 2^32 wraps +void increment_counter(uint32_t *counter, uint32_t *wrap, uint32_t delta) +{ + uint32_t new = *counter + delta; + if (new < *counter) + (*wrap)++; + + *counter = new; +} + +// initialise the random generator +static void initrandom(char *source) +{ + static char path[sizeof(config->random_device)] = "*undefined*"; + + // reinitialise only if we are forced to do so or if the config has changed + if (source && !strncmp(path, source, sizeof(path))) + return; + + // close previous source, if any + if (rand_fd >= 0) + close(rand_fd); + + rand_fd = -1; + + if (source) + { + // register changes + snprintf(path, sizeof(path), "%s", source); + + if (*path == '/') + { + rand_fd = open(path, O_RDONLY|O_NONBLOCK); + if (rand_fd < 0) + LOG(0, 0, 0, "Error opening the random device %s: %s\n", + path, strerror(errno)); + } + } +} + +// fill buffer with random data +void random_data(uint8_t *buf, int len) +{ + int n = 0; + + CSTAT(random_data); + if (rand_fd >= 0) + { + n = read(rand_fd, buf, len); + if (n >= len) return; + if (n < 0) + { + if (errno != EAGAIN) + { + LOG(0, 0, 0, "Error reading from random source: %s\n", + strerror(errno)); + + // fall back to rand() + initrandom(NULL); + } + + n = 0; + } + } + + // append missing data + while (n < len) + // not using the low order bits from the prng stream + buf[n++] = (rand() >> 4) & 0xff; +} + +// Add a route +// +// This adds it to the routing table, advertises it +// via BGP if enabled, and stuffs it into the +// 'sessionbyip' cache. +// +// 'ip' and 'mask' must be in _host_ order. +// +static void routeset(sessionidt s, in_addr_t ip, in_addr_t mask, in_addr_t gw, int add) +{ + struct rtentry r; + int i; + + if (!mask) mask = 0xffffffff; + + ip &= mask; // Force the ip to be the first one in the route. + + memset(&r, 0, sizeof(r)); + r.rt_dev = config->tundevice; + r.rt_dst.sa_family = AF_INET; + *(uint32_t *) & (((struct sockaddr_in *) &r.rt_dst)->sin_addr.s_addr) = htonl(ip); + r.rt_gateway.sa_family = AF_INET; + *(uint32_t *) & (((struct sockaddr_in *) &r.rt_gateway)->sin_addr.s_addr) = htonl(gw); + r.rt_genmask.sa_family = AF_INET; + *(uint32_t *) & (((struct sockaddr_in *) &r.rt_genmask)->sin_addr.s_addr) = htonl(mask); + r.rt_flags = (RTF_UP | RTF_STATIC); + if (gw) + r.rt_flags |= RTF_GATEWAY; + else if (mask == 0xffffffff) + r.rt_flags |= RTF_HOST; + + LOG(1, s, 0, "Route %s %s/%s%s%s\n", add ? "add" : "del", + fmtaddr(htonl(ip), 0), fmtaddr(htonl(mask), 1), + gw ? " via" : "", gw ? fmtaddr(htonl(gw), 2) : ""); + + if (ioctl(ifrfd, add ? SIOCADDRT : SIOCDELRT, (void *) &r) < 0) + LOG(0, 0, 0, "routeset() error in ioctl: %s\n", strerror(errno)); + +#ifdef BGP + if (add) + bgp_add_route(htonl(ip), htonl(mask)); + else + bgp_del_route(htonl(ip), htonl(mask)); +#endif /* BGP */ + + // Add/Remove the IPs to the 'sessionbyip' cache. + // Note that we add the zero address in the case of + // a network route. Roll on CIDR. + + // Note that 's == 0' implies this is the address pool. + // We still cache it here, because it will pre-fill + // the malloc'ed tree. + + if (s) + { + if (!add) // Are we deleting a route? + s = 0; // Caching the session as '0' is the same as uncaching. + + for (i = ip; (i&mask) == (ip&mask) ; ++i) + cache_ipmap(i, s); + } +} + +void route6set(sessionidt s, struct in6_addr ip, int prefixlen, int add) +{ + struct in6_rtmsg rt; + char ipv6addr[INET6_ADDRSTRLEN]; + + if (ifr6fd < 0) + { + LOG(0, 0, 0, "Asked to set IPv6 route, but IPv6 not setup.\n"); + return; + } + + memset(&rt, 0, sizeof(rt)); + + memcpy(&rt.rtmsg_dst, &ip, sizeof(struct in6_addr)); + rt.rtmsg_dst_len = prefixlen; + rt.rtmsg_metric = 1; + rt.rtmsg_flags = RTF_UP; + rt.rtmsg_ifindex = tunidx; + + LOG(1, 0, 0, "Route %s %s/%d\n", + add ? "add" : "del", + inet_ntop(AF_INET6, &ip, ipv6addr, INET6_ADDRSTRLEN), + prefixlen); + + if (ioctl(ifr6fd, add ? SIOCADDRT : SIOCDELRT, (void *) &rt) < 0) + LOG(0, 0, 0, "route6set() error in ioctl: %s\n", + strerror(errno)); + + // FIXME: need to add BGP routing (RFC2858) + + if (s) + { + if (!add) // Are we deleting a route? + s = 0; // Caching the session as '0' is the same as uncaching. + + cache_ipv6map(ip, prefixlen, s); + } + + return; +} + +// defined in linux/ipv6.h, but tricky to include from user-space +// TODO: move routing to use netlink rather than ioctl +struct in6_ifreq { + struct in6_addr ifr6_addr; + __u32 ifr6_prefixlen; + unsigned int ifr6_ifindex; +}; + +// +// Set up TUN interface +static void inittun(void) +{ + struct ifreq ifr; + struct in6_ifreq ifr6; + struct sockaddr_in sin = {0}; + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TUN; + + tunfd = open(TUNDEVICE, O_RDWR); + if (tunfd < 0) + { // fatal + LOG(0, 0, 0, "Can't open %s: %s\n", TUNDEVICE, strerror(errno)); + exit(1); + } + { + int flags = fcntl(tunfd, F_GETFL, 0); + fcntl(tunfd, F_SETFL, flags | O_NONBLOCK); + } + if (ioctl(tunfd, TUNSETIFF, (void *) &ifr) < 0) + { + LOG(0, 0, 0, "Can't set tun interface: %s\n", strerror(errno)); + exit(1); + } + assert(strlen(ifr.ifr_name) < sizeof(config->tundevice)); + strncpy(config->tundevice, ifr.ifr_name, sizeof(config->tundevice) - 1); + ifrfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = config->bind_address ? config->bind_address : 0x01010101; // 1.1.1.1 + memcpy(&ifr.ifr_addr, &sin, sizeof(struct sockaddr)); + + if (ioctl(ifrfd, SIOCSIFADDR, (void *) &ifr) < 0) + { + LOG(0, 0, 0, "Error setting tun address: %s\n", strerror(errno)); + exit(1); + } + /* Bump up the qlen to deal with bursts from the network */ + ifr.ifr_qlen = 1000; + if (ioctl(ifrfd, SIOCSIFTXQLEN, (void *) &ifr) < 0) + { + LOG(0, 0, 0, "Error setting tun queue length: %s\n", strerror(errno)); + exit(1); + } + /* set MTU to modem MRU */ + ifr.ifr_mtu = MRU; + if (ioctl(ifrfd, SIOCSIFMTU, (void *) &ifr) < 0) + { + LOG(0, 0, 0, "Error setting tun MTU: %s\n", strerror(errno)); + exit(1); + } + ifr.ifr_flags = IFF_UP; + if (ioctl(ifrfd, SIOCSIFFLAGS, (void *) &ifr) < 0) + { + LOG(0, 0, 0, "Error setting tun flags: %s\n", strerror(errno)); + exit(1); + } + if (ioctl(ifrfd, SIOCGIFINDEX, (void *) &ifr) < 0) + { + LOG(0, 0, 0, "Error getting tun ifindex: %s\n", strerror(errno)); + exit(1); + } + tunidx = ifr.ifr_ifindex; + + // Only setup IPv6 on the tun device if we have a configured prefix + if (config->ipv6_prefix.s6_addr[0]) { + ifr6fd = socket(PF_INET6, SOCK_DGRAM, 0); + + // Link local address is FE80::1 + memset(&ifr6.ifr6_addr, 0, sizeof(ifr6.ifr6_addr)); + ifr6.ifr6_addr.s6_addr[0] = 0xFE; + ifr6.ifr6_addr.s6_addr[1] = 0x80; + ifr6.ifr6_addr.s6_addr[15] = 1; + ifr6.ifr6_prefixlen = 64; + ifr6.ifr6_ifindex = ifr.ifr_ifindex; + if (ioctl(ifr6fd, SIOCSIFADDR, (void *) &ifr6) < 0) + { + LOG(0, 0, 0, "Error setting tun IPv6 link local address:" + " %s\n", strerror(errno)); + } + + // Global address is prefix::1 + memset(&ifr6.ifr6_addr, 0, sizeof(ifr6.ifr6_addr)); + ifr6.ifr6_addr = config->ipv6_prefix; + ifr6.ifr6_addr.s6_addr[15] = 1; + ifr6.ifr6_prefixlen = 64; + ifr6.ifr6_ifindex = ifr.ifr_ifindex; + if (ioctl(ifr6fd, SIOCSIFADDR, (void *) &ifr6) < 0) + { + LOG(0, 0, 0, "Error setting tun IPv6 global address: %s\n", + strerror(errno)); + } + } +} + +// set up UDP ports +static void initudp(void) +{ + int on = 1; + struct sockaddr_in addr; + + // Tunnel + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(L2TPPORT); + addr.sin_addr.s_addr = config->bind_address; + udpfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + setsockopt(udpfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + { + int flags = fcntl(udpfd, F_GETFL, 0); + fcntl(udpfd, F_SETFL, flags | O_NONBLOCK); + } + if (bind(udpfd, (void *) &addr, sizeof(addr)) < 0) + { + LOG(0, 0, 0, "Error in UDP bind: %s\n", strerror(errno)); + exit(1); + } + + // Control + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(NSCTL_PORT); + controlfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + setsockopt(controlfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + setsockopt(controlfd, SOL_IP, IP_PKTINFO, &on, sizeof(on)); // recvfromto + if (bind(controlfd, (void *) &addr, sizeof(addr)) < 0) + { + LOG(0, 0, 0, "Error in control bind: %s\n", strerror(errno)); + exit(1); + } + + // Dynamic Authorization Extensions to RADIUS + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(config->radius_dae_port); + daefd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + setsockopt(daefd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + setsockopt(daefd, SOL_IP, IP_PKTINFO, &on, sizeof(on)); // recvfromto + if (bind(daefd, (void *) &addr, sizeof(addr)) < 0) + { + LOG(0, 0, 0, "Error in DAE bind: %s\n", strerror(errno)); + exit(1); + } + + // Intercept + snoopfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); +} + +// +// Find session by IP, < 1 for not found +// +// Confusingly enough, this 'ip' must be +// in _network_ order. This being the common +// case when looking it up from IP packet headers. +// +// We actually use this cache for two things. +// #1. For used IP addresses, this maps to the +// session ID that it's used by. +// #2. For un-used IP addresses, this maps to the +// index into the pool table that contains that +// IP address. +// + +static int lookup_ipmap(in_addr_t ip) +{ + uint8_t *a = (uint8_t *) &ip; + uint8_t **d = (uint8_t **) ip_hash; + + if (!(d = (uint8_t **) d[(size_t) *a++])) return 0; + if (!(d = (uint8_t **) d[(size_t) *a++])) return 0; + if (!(d = (uint8_t **) d[(size_t) *a++])) return 0; + + return (int) (intptr_t) d[(size_t) *a]; +} + +static int lookup_ipv6map(struct in6_addr ip) +{ + struct ipv6radix *curnode; + int i; + int s; + char ipv6addr[INET6_ADDRSTRLEN]; + + curnode = &ipv6_hash[ip.s6_addr[0]]; + i = 1; + s = curnode->sess; + + while (s == 0 && i < 15 && curnode->branch != NULL) + { + curnode = &curnode->branch[ip.s6_addr[i]]; + s = curnode->sess; + i++; + } + + LOG(4, s, session[s].tunnel, "Looking up address %s and got %d\n", + inet_ntop(AF_INET6, &ip, ipv6addr, + INET6_ADDRSTRLEN), + s); + + return s; +} + +sessionidt sessionbyip(in_addr_t ip) +{ + int s = lookup_ipmap(ip); + CSTAT(sessionbyip); + + if (s > 0 && s < MAXSESSION && session[s].opened) + return (sessionidt) s; + + return 0; +} + +sessionidt sessionbyipv6(struct in6_addr ip) +{ + int s; + CSTAT(sessionbyipv6); + + if (!memcmp(&config->ipv6_prefix, &ip, 8) || + (ip.s6_addr[0] == 0xFE && + ip.s6_addr[1] == 0x80 && + ip.s6_addr16[1] == 0 && + ip.s6_addr16[2] == 0 && + ip.s6_addr16[3] == 0)) { + s = lookup_ipmap(*(in_addr_t *) &ip.s6_addr[8]); + } else { + s = lookup_ipv6map(ip); + } + + if (s > 0 && s < MAXSESSION && session[s].opened) + return s; + + return 0; +} + +// +// Take an IP address in HOST byte order and +// add it to the sessionid by IP cache. +// +// (It's actually cached in network order) +// +static void cache_ipmap(in_addr_t ip, int s) +{ + in_addr_t nip = htonl(ip); // MUST be in network order. I.e. MSB must in be ((char *) (&ip))[0] + uint8_t *a = (uint8_t *) &nip; + uint8_t **d = (uint8_t **) ip_hash; + int i; + + for (i = 0; i < 3; i++) + { + if (!d[(size_t) a[i]]) + { + if (!(d[(size_t) a[i]] = calloc(256, sizeof(void *)))) + return; + } + + d = (uint8_t **) d[(size_t) a[i]]; + } + + d[(size_t) a[3]] = (uint8_t *) (intptr_t) s; + + if (s > 0) + LOG(4, s, session[s].tunnel, "Caching ip address %s\n", fmtaddr(nip, 0)); + + else if (s == 0) + LOG(4, 0, 0, "Un-caching ip address %s\n", fmtaddr(nip, 0)); + // else a map to an ip pool index. +} + +static void uncache_ipmap(in_addr_t ip) +{ + cache_ipmap(ip, 0); // Assign it to the NULL session. +} + +static void cache_ipv6map(struct in6_addr ip, int prefixlen, int s) +{ + int i; + int bytes; + struct ipv6radix *curnode; + char ipv6addr[INET6_ADDRSTRLEN]; + + curnode = &ipv6_hash[ip.s6_addr[0]]; + + bytes = prefixlen >> 3; + i = 1; + while (i < bytes) { + if (curnode->branch == NULL) + { + if (!(curnode->branch = calloc(256, + sizeof (struct ipv6radix)))) + return; + } + curnode = &curnode->branch[ip.s6_addr[i]]; + i++; + } + + curnode->sess = s; + + if (s > 0) + LOG(4, s, session[s].tunnel, "Caching ip address %s/%d\n", + inet_ntop(AF_INET6, &ip, ipv6addr, + INET6_ADDRSTRLEN), + prefixlen); + else if (s == 0) + LOG(4, 0, 0, "Un-caching ip address %s/%d\n", + inet_ntop(AF_INET6, &ip, ipv6addr, + INET6_ADDRSTRLEN), + prefixlen); +} + +// +// CLI list to dump current ipcache. +// +int cmd_show_ipcache(struct cli_def *cli, char *command, char **argv, int argc) +{ + char **d = (char **) ip_hash, **e, **f, **g; + int i, j, k, l; + int count = 0; + + if (CLI_HELP_REQUESTED) + return CLI_HELP_NO_ARGS; + + cli_print(cli, "%7s %s", "Sess#", "IP Address"); + + for (i = 0; i < 256; ++i) + { + if (!d[i]) + continue; + e = (char **) d[i]; + for (j = 0; j < 256; ++j) + { + if (!e[j]) + continue; + f = (char **) e[j]; + for (k = 0; k < 256; ++k) + { + if (!f[k]) + continue; + g = (char **)f[k]; + for (l = 0; l < 256; ++l) + { + if (!g[l]) + continue; + cli_print(cli, "%7d %d.%d.%d.%d", (int) (intptr_t) g[l], i, j, k, l); + ++count; + } + } + } + } + cli_print(cli, "%d entries in cache", count); + return CLI_OK; +} + + +// Find session by username, 0 for not found +// walled garden users aren't authenticated, so the username is +// reasonably useless. Ignore them to avoid incorrect actions +// +// This is VERY inefficent. Don't call it often. :) +// +sessionidt sessionbyuser(char *username) +{ + int s; + CSTAT(sessionbyuser); + + for (s = 1; s <= config->cluster_highest_sessionid ; ++s) + { + if (!session[s].opened) + continue; + + if (session[s].walled_garden) + continue; // Skip walled garden users. + + if (!strncmp(session[s].user, username, 128)) + return s; + + } + return 0; // Not found. +} + +void send_garp(in_addr_t ip) +{ + int s; + struct ifreq ifr; + uint8_t mac[6]; + + s = socket(PF_INET, SOCK_DGRAM, 0); + if (s < 0) + { + LOG(0, 0, 0, "Error creating socket for GARP: %s\n", strerror(errno)); + return; + } + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, "eth0", sizeof(ifr.ifr_name) - 1); + if (ioctl(s, SIOCGIFHWADDR, &ifr) < 0) + { + LOG(0, 0, 0, "Error getting eth0 hardware address for GARP: %s\n", strerror(errno)); + close(s); + return; + } + memcpy(mac, &ifr.ifr_hwaddr.sa_data, 6*sizeof(char)); + if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) + { + LOG(0, 0, 0, "Error getting eth0 interface index for GARP: %s\n", strerror(errno)); + close(s); + return; + } + close(s); + sendarp(ifr.ifr_ifindex, mac, ip); +} + +static sessiont *sessiontbysessionidt(sessionidt s) +{ + if (!s || s >= MAXSESSION) return NULL; + return &session[s]; +} + +static sessionidt sessionidtbysessiont(sessiont *s) +{ + sessionidt val = s-session; + if (s < session || val >= MAXSESSION) return 0; + return val; +} + +// actually send a control message for a specific tunnel +void tunnelsend(uint8_t * buf, uint16_t l, tunnelidt t) +{ + struct sockaddr_in addr; + + CSTAT(tunnelsend); + + if (!t) + { + LOG(0, 0, t, "tunnelsend called with 0 as tunnel id\n"); + STAT(tunnel_tx_errors); + return; + } + + if (!tunnel[t].ip) + { + LOG(1, 0, t, "Error sending data out tunnel: no remote endpoint (tunnel not set up)\n"); + STAT(tunnel_tx_errors); + return; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + *(uint32_t *) & addr.sin_addr = htonl(tunnel[t].ip); + addr.sin_port = htons(tunnel[t].port); + + // sequence expected, if sequence in message + if (*buf & 0x08) *(uint16_t *) (buf + ((*buf & 0x40) ? 10 : 8)) = htons(tunnel[t].nr); + + // If this is a control message, deal with retries + if (*buf & 0x80) + { + tunnel[t].last = time_now; // control message sent + tunnel[t].retry = backoff(tunnel[t].try); // when to resend + if (tunnel[t].try) + { + STAT(tunnel_retries); + LOG(3, 0, t, "Control message resend try %d\n", tunnel[t].try); + } + } + + if (sendto(udpfd, buf, l, 0, (void *) &addr, sizeof(addr)) < 0) + { + LOG(0, ntohs((*(uint16_t *) (buf + 6))), t, "Error sending data out tunnel: %s (udpfd=%d, buf=%p, len=%d, dest=%s)\n", + strerror(errno), udpfd, buf, l, inet_ntoa(addr.sin_addr)); + STAT(tunnel_tx_errors); + return; + } + + LOG_HEX(5, "Send Tunnel Data", buf, l); + STAT(tunnel_tx_packets); + INC_STAT(tunnel_tx_bytes, l); +} + +// +// Tiny helper function to write data to +// the 'tun' device. +// +int tun_write(uint8_t * data, int size) +{ + return write(tunfd, data, size); +} + +// adjust tcp mss to avoid fragmentation (called only for tcp packets with syn set) +void adjust_tcp_mss(sessionidt s, tunnelidt t, uint8_t *buf, int len, uint8_t *tcp) +{ + int d = (tcp[12] >> 4) * 4; + uint8_t *mss = 0; + uint8_t *opts; + uint8_t *data; + uint16_t orig; + uint32_t sum; + + if ((tcp[13] & 0x3f) & ~(TCP_FLAG_SYN|TCP_FLAG_ACK)) // only want SYN and SYN,ACK + return; + + if (tcp + d > buf + len) // short? + return; + + opts = tcp + 20; + data = tcp + d; + + while (opts < data) + { + if (*opts == 2 && opts[1] == 4) // mss option (2), length 4 + { + mss = opts + 2; + if (mss + 2 > data) return; // short? + break; + } + + if (*opts == 0) return; // end of options + if (*opts == 1 || !opts[1]) // no op (one byte), or no length (prevent loop) + opts++; + else + opts += opts[1]; // skip over option + } + + if (!mss) return; // not found + orig = ntohs(*(uint16_t *) mss); + + if (orig <= MSS) return; // mss OK + + LOG(5, s, t, "TCP: %s:%u -> %s:%u SYN%s: adjusted mss from %u to %u\n", + fmtaddr(*(in_addr_t *) (buf + 12), 0), ntohs(*(uint16_t *) tcp), + fmtaddr(*(in_addr_t *) (buf + 16), 1), ntohs(*(uint16_t *) (tcp + 2)), + (tcp[13] & TCP_FLAG_ACK) ? ",ACK" : "", orig, MSS); + + // set mss + *(int16_t *) mss = htons(MSS); + + // adjust checksum (see rfc1141) + sum = orig + (~MSS & 0xffff); + sum += ntohs(*(uint16_t *) (tcp + 16)); + sum = (sum & 0xffff) + (sum >> 16); + *(uint16_t *) (tcp + 16) = htons(sum + (sum >> 16)); +} + +// process outgoing (to tunnel) IP +// +static void processipout(uint8_t *buf, int len) +{ + sessionidt s; + sessiont *sp; + tunnelidt t; + in_addr_t ip; + + uint8_t *data = buf; // Keep a copy of the originals. + int size = len; + + uint8_t b[MAXETHER + 20]; + + CSTAT(processipout); + + if (len < MIN_IP_SIZE) + { + LOG(1, 0, 0, "Short IP, %d bytes\n", len); + STAT(tun_rx_errors); + return; + } + if (len >= MAXETHER) + { + LOG(1, 0, 0, "Oversize IP packet %d bytes\n", len); + STAT(tun_rx_errors); + return; + } + + // Skip the tun header + buf += 4; + len -= 4; + + // Got an IP header now + if (*(uint8_t *)(buf) >> 4 != 4) + { + LOG(1, 0, 0, "IP: Don't understand anything except IPv4\n"); + return; + } + + ip = *(uint32_t *)(buf + 16); + if (!(s = sessionbyip(ip))) + { + // Is this a packet for a session that doesn't exist? + static int rate = 0; // Number of ICMP packets we've sent this second. + static int last = 0; // Last time we reset the ICMP packet counter 'rate'. + + if (last != time_now) + { + last = time_now; + rate = 0; + } + + if (rate++ < config->icmp_rate) // Only send a max of icmp_rate per second. + { + LOG(4, 0, 0, "IP: Sending ICMP host unreachable to %s\n", fmtaddr(*(in_addr_t *)(buf + 12), 0)); + host_unreachable(*(in_addr_t *)(buf + 12), *(uint16_t *)(buf + 4), + config->bind_address ? config->bind_address : my_address, buf, len); + } + return; + } + t = session[s].tunnel; + sp = &session[s]; + + // DoS prevention: enforce a maximum number of packets per 0.1s for a session + if (config->max_packets > 0) + { + if (sess_local[s].last_packet_out == TIME) + { + int max = config->max_packets; + + // All packets for throttled sessions are handled by the + // master, so further limit by using the throttle rate. + // A bit of a kludge, since throttle rate is in kbps, + // but should still be generous given our average DSL + // packet size is 200 bytes: a limit of 28kbps equates + // to around 180 packets per second. + if (!config->cluster_iam_master && sp->throttle_out && sp->throttle_out < max) + max = sp->throttle_out; + + if (++sess_local[s].packets_out > max) + { + sess_local[s].packets_dropped++; + return; + } + } + else + { + if (sess_local[s].packets_dropped) + { + INC_STAT(tun_rx_dropped, sess_local[s].packets_dropped); + LOG(3, s, t, "Dropped %u/%u packets to %s for %suser %s\n", + sess_local[s].packets_dropped, sess_local[s].packets_out, + fmtaddr(ip, 0), sp->throttle_out ? "throttled " : "", + sp->user); + } + + sess_local[s].last_packet_out = TIME; + sess_local[s].packets_out = 1; + sess_local[s].packets_dropped = 0; + } + } + + // run access-list if any + if (session[s].filter_out && !ip_filter(buf, len, session[s].filter_out - 1)) + return; + + // adjust MSS on SYN and SYN,ACK packets with options + if ((ntohs(*(uint16_t *) (buf + 6)) & 0x1fff) == 0 && buf[9] == IPPROTO_TCP) // first tcp fragment + { + int ihl = (buf[0] & 0xf) * 4; // length of IP header + if (len >= ihl + 20 && (buf[ihl + 13] & TCP_FLAG_SYN) && ((buf[ihl + 12] >> 4) > 5)) + adjust_tcp_mss(s, t, buf, len, buf + ihl); + } + + if (sp->tbf_out) + { + // Are we throttling this session? + if (config->cluster_iam_master) + tbf_queue_packet(sp->tbf_out, data, size); + else + master_throttle_packet(sp->tbf_out, data, size); + return; + } + + if (sp->walled_garden && !config->cluster_iam_master) + { + // We are walled-gardening this + master_garden_packet(s, data, size); + return; + } + + LOG(5, s, t, "Ethernet -> Tunnel (%d bytes)\n", len); + + // Add on L2TP header + { + uint8_t *p = makeppp(b, sizeof(b), buf, len, s, t, PPPIP); + if (!p) return; + tunnelsend(b, len + (p-b), t); // send it... + } + + // Snooping this session, send it to intercept box + if (sp->snoop_ip && sp->snoop_port) + snoop_send_packet(buf, len, sp->snoop_ip, sp->snoop_port); + + increment_counter(&sp->cout, &sp->cout_wrap, len); // byte count + sp->cout_delta += len; + sp->pout++; + udp_tx += len; + + sess_local[s].cout += len; // To send to master.. + sess_local[s].pout++; +} + +// process outgoing (to tunnel) IPv6 +// +static void processipv6out(uint8_t * buf, int len) +{ + sessionidt s; + sessiont *sp; + tunnelidt t; + in_addr_t ip; + struct in6_addr ip6; + + uint8_t *data = buf; // Keep a copy of the originals. + int size = len; + + uint8_t b[MAXETHER + 20]; + + CSTAT(processipv6out); + + if (len < MIN_IP_SIZE) + { + LOG(1, 0, 0, "Short IPv6, %d bytes\n", len); + STAT(tunnel_tx_errors); + return; + } + if (len >= MAXETHER) + { + LOG(1, 0, 0, "Oversize IPv6 packet %d bytes\n", len); + STAT(tunnel_tx_errors); + return; + } + + // Skip the tun header + buf += 4; + len -= 4; + + // Got an IP header now + if (*(uint8_t *)(buf) >> 4 != 6) + { + LOG(1, 0, 0, "IP: Don't understand anything except IPv6\n"); + return; + } + + ip6 = *(struct in6_addr *)(buf+24); + s = sessionbyipv6(ip6); + + if (s == 0) + { + ip = *(uint32_t *)(buf + 32); + s = sessionbyip(ip); + } + + if (s == 0) + { + // Is this a packet for a session that doesn't exist? + static int rate = 0; // Number of ICMP packets we've sent this second. + static int last = 0; // Last time we reset the ICMP packet counter 'rate'. + + if (last != time_now) + { + last = time_now; + rate = 0; + } + + if (rate++ < config->icmp_rate) // Only send a max of icmp_rate per second. + { + // FIXME: Should send icmp6 host unreachable + } + return; + } + t = session[s].tunnel; + sp = &session[s]; + + // FIXME: add DoS prevention/filters? + + if (sp->tbf_out) + { + // Are we throttling this session? + if (config->cluster_iam_master) + tbf_queue_packet(sp->tbf_out, data, size); + else + master_throttle_packet(sp->tbf_out, data, size); + return; + } + else if (sp->walled_garden && !config->cluster_iam_master) + { + // We are walled-gardening this + master_garden_packet(s, data, size); + return; + } + + LOG(5, s, t, "Ethernet -> Tunnel (%d bytes)\n", len); + + // Add on L2TP header + { + uint8_t *p = makeppp(b, sizeof(b), buf, len, s, t, PPPIPV6); + if (!p) return; + tunnelsend(b, len + (p-b), t); // send it... + } + + // Snooping this session, send it to intercept box + if (sp->snoop_ip && sp->snoop_port) + snoop_send_packet(buf, len, sp->snoop_ip, sp->snoop_port); + + increment_counter(&sp->cout, &sp->cout_wrap, len); // byte count + sp->cout_delta += len; + sp->pout++; + udp_tx += len; + + sess_local[s].cout += len; // To send to master.. + sess_local[s].pout++; +} + +// +// Helper routine for the TBF filters. +// Used to send queued data in to the user! +// +static void send_ipout(sessionidt s, uint8_t *buf, int len) +{ + sessiont *sp; + tunnelidt t; + in_addr_t ip; + + uint8_t b[MAXETHER + 20]; + + if (len < 0 || len > MAXETHER) + { + LOG(1, 0, 0, "Odd size IP packet: %d bytes\n", len); + return; + } + + // Skip the tun header + buf += 4; + len -= 4; + + ip = *(in_addr_t *)(buf + 16); + + if (!session[s].ip) + return; + + t = session[s].tunnel; + sp = &session[s]; + + LOG(5, s, t, "Ethernet -> Tunnel (%d bytes)\n", len); + + // Add on L2TP header + { + uint8_t *p = makeppp(b, sizeof(b), buf, len, s, t, PPPIP); + if (!p) return; + tunnelsend(b, len + (p-b), t); // send it... + } + + // Snooping this session. + if (sp->snoop_ip && sp->snoop_port) + snoop_send_packet(buf, len, sp->snoop_ip, sp->snoop_port); + + increment_counter(&sp->cout, &sp->cout_wrap, len); // byte count + sp->cout_delta += len; + sp->pout++; + udp_tx += len; + + sess_local[s].cout += len; // To send to master.. + sess_local[s].pout++; +} + +// add an AVP (16 bit) +static void control16(controlt * c, uint16_t avp, uint16_t val, uint8_t m) +{ + uint16_t l = (m ? 0x8008 : 0x0008); + *(uint16_t *) (c->buf + c->length + 0) = htons(l); + *(uint16_t *) (c->buf + c->length + 2) = htons(0); + *(uint16_t *) (c->buf + c->length + 4) = htons(avp); + *(uint16_t *) (c->buf + c->length + 6) = htons(val); + c->length += 8; +} + +// add an AVP (32 bit) +static void control32(controlt * c, uint16_t avp, uint32_t val, uint8_t m) +{ + uint16_t l = (m ? 0x800A : 0x000A); + *(uint16_t *) (c->buf + c->length + 0) = htons(l); + *(uint16_t *) (c->buf + c->length + 2) = htons(0); + *(uint16_t *) (c->buf + c->length + 4) = htons(avp); + *(uint32_t *) (c->buf + c->length + 6) = htonl(val); + c->length += 10; +} + +// add an AVP (string) +static void controls(controlt * c, uint16_t avp, char *val, uint8_t m) +{ + uint16_t l = ((m ? 0x8000 : 0) + strlen(val) + 6); + *(uint16_t *) (c->buf + c->length + 0) = htons(l); + *(uint16_t *) (c->buf + c->length + 2) = htons(0); + *(uint16_t *) (c->buf + c->length + 4) = htons(avp); + memcpy(c->buf + c->length + 6, val, strlen(val)); + c->length += 6 + strlen(val); +} + +// add a binary AVP +static void controlb(controlt * c, uint16_t avp, uint8_t *val, unsigned int len, uint8_t m) +{ + uint16_t l = ((m ? 0x8000 : 0) + len + 6); + *(uint16_t *) (c->buf + c->length + 0) = htons(l); + *(uint16_t *) (c->buf + c->length + 2) = htons(0); + *(uint16_t *) (c->buf + c->length + 4) = htons(avp); + memcpy(c->buf + c->length + 6, val, len); + c->length += 6 + len; +} + +// new control connection +static controlt *controlnew(uint16_t mtype) +{ + controlt *c; + if (!controlfree) + c = malloc(sizeof(controlt)); + else + { + c = controlfree; + controlfree = c->next; + } + assert(c); + c->next = 0; + *(uint16_t *) (c->buf + 0) = htons(0xC802); // flags/ver + c->length = 12; + control16(c, 0, mtype, 1); + return c; +} + +// send zero block if nothing is waiting +// (ZLB send). +static void controlnull(tunnelidt t) +{ + uint8_t buf[12]; + if (tunnel[t].controlc) // Messages queued; They will carry the ack. + return; + + *(uint16_t *) (buf + 0) = htons(0xC802); // flags/ver + *(uint16_t *) (buf + 2) = htons(12); // length + *(uint16_t *) (buf + 4) = htons(tunnel[t].far); // tunnel + *(uint16_t *) (buf + 6) = htons(0); // session + *(uint16_t *) (buf + 8) = htons(tunnel[t].ns); // sequence + *(uint16_t *) (buf + 10) = htons(tunnel[t].nr); // sequence + tunnelsend(buf, 12, t); +} + +// add a control message to a tunnel, and send if within window +static void controladd(controlt *c, sessionidt far, tunnelidt t) +{ + *(uint16_t *) (c->buf + 2) = htons(c->length); // length + *(uint16_t *) (c->buf + 4) = htons(tunnel[t].far); // tunnel + *(uint16_t *) (c->buf + 6) = htons(far); // session + *(uint16_t *) (c->buf + 8) = htons(tunnel[t].ns); // sequence + tunnel[t].ns++; // advance sequence + // link in message in to queue + if (tunnel[t].controlc) + tunnel[t].controle->next = c; + else + tunnel[t].controls = c; + + tunnel[t].controle = c; + tunnel[t].controlc++; + + // send now if space in window + if (tunnel[t].controlc <= tunnel[t].window) + { + tunnel[t].try = 0; // first send + tunnelsend(c->buf, c->length, t); + } +} + +// +// Throttle or Unthrottle a session +// +// Throttle the data from/to through a session to no more than +// 'rate_in' kbit/sec in (from user) or 'rate_out' kbit/sec out (to +// user). +// +// If either value is -1, the current value is retained for that +// direction. +// +void throttle_session(sessionidt s, int rate_in, int rate_out) +{ + if (!session[s].opened) + return; // No-one home. + + if (!*session[s].user) + return; // User not logged in + + if (rate_in >= 0) + { + int bytes = rate_in * 1024 / 8; // kbits to bytes + if (session[s].tbf_in) + free_tbf(session[s].tbf_in); + + if (rate_in > 0) + session[s].tbf_in = new_tbf(s, bytes * 2, bytes, send_ipin); + else + session[s].tbf_in = 0; + + session[s].throttle_in = rate_in; + } + + if (rate_out >= 0) + { + int bytes = rate_out * 1024 / 8; + if (session[s].tbf_out) + free_tbf(session[s].tbf_out); + + if (rate_out > 0) + session[s].tbf_out = new_tbf(s, bytes * 2, bytes, send_ipout); + else + session[s].tbf_out = 0; + + session[s].throttle_out = rate_out; + } +} + +// add/remove filters from session (-1 = no change) +void filter_session(sessionidt s, int filter_in, int filter_out) +{ + if (!session[s].opened) + return; // No-one home. + + if (!*session[s].user) + return; // User not logged in + + // paranoia + if (filter_in > MAXFILTER) filter_in = -1; + if (filter_out > MAXFILTER) filter_out = -1; + if (session[s].filter_in > MAXFILTER) session[s].filter_in = 0; + if (session[s].filter_out > MAXFILTER) session[s].filter_out = 0; + + if (filter_in >= 0) + { + if (session[s].filter_in) + ip_filters[session[s].filter_in - 1].used--; + + if (filter_in > 0) + ip_filters[filter_in - 1].used++; + + session[s].filter_in = filter_in; + } + + if (filter_out >= 0) + { + if (session[s].filter_out) + ip_filters[session[s].filter_out - 1].used--; + + if (filter_out > 0) + ip_filters[filter_out - 1].used++; + + session[s].filter_out = filter_out; + } +} + +// start tidy shutdown of session +void sessionshutdown(sessionidt s, char const *reason, int cdn_result, int cdn_error, int term_cause) +{ + int walled_garden = session[s].walled_garden; + + + CSTAT(sessionshutdown); + + if (!session[s].opened) + { + LOG(3, s, session[s].tunnel, "Called sessionshutdown on an unopened session.\n"); + return; // not a live session + } + + if (!session[s].die) + { + struct param_kill_session data = { &tunnel[session[s].tunnel], &session[s] }; + LOG(2, s, session[s].tunnel, "Shutting down session %d: %s\n", s, reason); + run_plugins(PLUGIN_KILL_SESSION, &data); + } + + if (session[s].ip && !walled_garden && !session[s].die) + { + // RADIUS Stop message + uint16_t r = radiusnew(s); + if (r) + { + // stop, if not already trying + if (radius[r].state != RADIUSSTOP) + { + radius[r].term_cause = term_cause; + radius[r].term_msg = reason; + radiussend(r, RADIUSSTOP); + } + } + else + LOG(1, s, session[s].tunnel, "No free RADIUS sessions for Stop message\n"); + + // Save counters to dump to accounting file + if (*config->accounting_dir && shut_acct_n < sizeof(shut_acct) / sizeof(*shut_acct)) + memcpy(&shut_acct[shut_acct_n++], &session[s], sizeof(session[s])); + } + + if (session[s].ip) + { // IP allocated, clear and unroute + int r; + int routed = 0; + for (r = 0; r < MAXROUTE && session[s].route[r].ip; r++) + { + if ((session[s].ip & session[s].route[r].mask) == + (session[s].route[r].ip & session[s].route[r].mask)) + routed++; + + routeset(s, session[s].route[r].ip, session[s].route[r].mask, 0, 0); + session[s].route[r].ip = 0; + } + + if (session[s].ip_pool_index == -1) // static ip + { + if (!routed) routeset(s, session[s].ip, 0, 0, 0); + session[s].ip = 0; + } + else + free_ip_address(s); + + // unroute IPv6, if setup + if (session[s].ppp.ipv6cp == Opened && session[s].ipv6prefixlen) + route6set(s, session[s].ipv6route, session[s].ipv6prefixlen, 0); + } + + if (session[s].throttle_in || session[s].throttle_out) // Unthrottle if throttled. + throttle_session(s, 0, 0); + + if (cdn_result) + { // Send CDN + controlt *c = controlnew(14); // sending CDN + if (cdn_error) + { + uint8_t buf[4]; + *(uint16_t *) buf = htons(cdn_result); + *(uint16_t *) (buf+2) = htons(cdn_error); + controlb(c, 1, buf, 4, 1); + } + else + control16(c, 1, cdn_result, 1); + + control16(c, 14, s, 1); // assigned session (our end) + controladd(c, session[s].far, session[s].tunnel); // send the message + } + + if (!session[s].die) + session[s].die = TIME + 150; // Clean up in 15 seconds + + // update filter refcounts + if (session[s].filter_in) ip_filters[session[s].filter_in - 1].used--; + if (session[s].filter_out) ip_filters[session[s].filter_out - 1].used--; + + // clear PPP state + memset(&session[s].ppp, 0, sizeof(session[s].ppp)); + sess_local[s].lcp.restart = 0; + sess_local[s].ipcp.restart = 0; + sess_local[s].ipv6cp.restart = 0; + sess_local[s].ccp.restart = 0; + + cluster_send_session(s); +} + +void sendipcp(sessionidt s, tunnelidt t) +{ + uint8_t buf[MAXETHER]; + uint8_t *q; + + CSTAT(sendipcp); + LOG(3, s, t, "IPCP: send ConfigReq\n"); + + if (!session[s].unique_id) + { + if (!++last_id) ++last_id; // skip zero + session[s].unique_id = last_id; + } + + q = makeppp(buf, sizeof(buf), 0, 0, s, t, PPPIPCP); + if (!q) return; + + *q = ConfigReq; + q[1] = session[s].unique_id & 0xf; // ID, dont care, we only send one type of request + *(uint16_t *) (q + 2) = htons(10); // packet length + q[4] = 3; // ip address option + q[5] = 6; // option length + *(in_addr_t *) (q + 6) = config->peer_address ? config->peer_address : + config->bind_address ? config->bind_address : + my_address; // send my IP + + tunnelsend(buf, 10 + (q - buf), t); // send it + restart_timer(s, ipcp); +} + +void sendipv6cp(sessionidt s, tunnelidt t) +{ + uint8_t buf[MAXETHER]; + uint8_t *q; + + CSTAT(sendipv6cp); + LOG(3, s, t, "IPV6CP: send ConfigReq\n"); + + q = makeppp(buf, sizeof(buf), 0, 0, s, t, PPPIPV6CP); + if (!q) return; + + *q = ConfigReq; + q[1] = session[s].unique_id & 0xf; // ID, don't care, we + // only send one type + // of request + *(uint16_t *) (q + 2) = htons(14); + q[4] = 1; // interface identifier option + q[5] = 10; // option length + *(uint32_t *) (q + 6) = 0; // We'll be prefix::1 + *(uint32_t *) (q + 10) = 0; + q[13] = 1; + + tunnelsend(buf, 14 + (q - buf), t); // send it + restart_timer(s, ipv6cp); +} + +static void sessionclear(sessionidt s) +{ + memset(&session[s], 0, sizeof(session[s])); + memset(&sess_local[s], 0, sizeof(sess_local[s])); + memset(&cli_session_actions[s], 0, sizeof(cli_session_actions[s])); + + session[s].tunnel = T_FREE; // Mark it as free. + session[s].next = sessionfree; + sessionfree = s; +} + +// kill a session now +void sessionkill(sessionidt s, char *reason) +{ + + CSTAT(sessionkill); + + if (!session[s].opened) // not alive + return; + + if (session[s].next) + { + LOG(0, s, session[s].tunnel, "Tried to kill a session with next pointer set (%d)\n", session[s].next); + return; + } + + session[s].die = TIME; + sessionshutdown(s, reason, CDN_ADMIN_DISC, TERM_ADMIN_RESET); // close radius/routes, etc. + if (sess_local[s].radius) + radiusclear(sess_local[s].radius, s); // cant send clean accounting data, session is killed + + LOG(2, s, session[s].tunnel, "Kill session %d (%s): %s\n", s, session[s].user, reason); + sessionclear(s); + cluster_send_session(s); +} + +static void tunnelclear(tunnelidt t) +{ + if (!t) return; + memset(&tunnel[t], 0, sizeof(tunnel[t])); + tunnel[t].state = TUNNELFREE; +} + +// kill a tunnel now +static void tunnelkill(tunnelidt t, char *reason) +{ + sessionidt s; + controlt *c; + + CSTAT(tunnelkill); + + tunnel[t].state = TUNNELDIE; + + // free control messages + while ((c = tunnel[t].controls)) + { + controlt * n = c->next; + tunnel[t].controls = n; + tunnel[t].controlc--; + c->next = controlfree; + controlfree = c; + } + // kill sessions + for (s = 1; s <= config->cluster_highest_sessionid ; ++s) + if (session[s].tunnel == t) + sessionkill(s, reason); + + // free tunnel + tunnelclear(t); + LOG(1, 0, t, "Kill tunnel %d: %s\n", t, reason); + cli_tunnel_actions[t].action = 0; + cluster_send_tunnel(t); +} + +// shut down a tunnel cleanly +static void tunnelshutdown(tunnelidt t, char *reason, int result, int error, char *msg) +{ + sessionidt s; + + CSTAT(tunnelshutdown); + + if (!tunnel[t].last || !tunnel[t].far || tunnel[t].state == TUNNELFREE) + { + // never set up, can immediately kill + tunnelkill(t, reason); + return; + } + LOG(1, 0, t, "Shutting down tunnel %d (%s)\n", t, reason); + + // close session + for (s = 1; s <= config->cluster_highest_sessionid ; ++s) + if (session[s].tunnel == t) + sessionshutdown(s, reason, CDN_NONE, TERM_ADMIN_RESET); + + tunnel[t].state = TUNNELDIE; + tunnel[t].die = TIME + 700; // Clean up in 70 seconds + cluster_send_tunnel(t); + // TBA - should we wait for sessions to stop? + if (result) + { + controlt *c = controlnew(4); // sending StopCCN + if (error) + { + uint8_t buf[64]; + int l = 4; + *(uint16_t *) buf = htons(result); + *(uint16_t *) (buf+2) = htons(error); + if (msg) + { + int m = strlen(msg); + if (m + 4 > sizeof(buf)) + m = sizeof(buf) - 4; + + memcpy(buf+4, msg, m); + l += m; + } + + controlb(c, 1, buf, l, 1); + } + else + control16(c, 1, result, 1); + + control16(c, 9, t, 1); // assigned tunnel (our end) + controladd(c, 0, t); // send the message + } +} + +// read and process packet on tunnel (UDP) +void processudp(uint8_t *buf, int len, struct sockaddr_in *addr) +{ + uint8_t *chapresponse = NULL; + uint16_t l = len, t = 0, s = 0, ns = 0, nr = 0; + uint8_t *p = buf + 2; + + + CSTAT(processudp); + + udp_rx += len; + udp_rx_pkt++; + LOG_HEX(5, "UDP Data", buf, len); + STAT(tunnel_rx_packets); + INC_STAT(tunnel_rx_bytes, len); + if (len < 6) + { + LOG(1, 0, 0, "Short UDP, %d bytes\n", len); + STAT(tunnel_rx_errors); + return; + } + if ((buf[1] & 0x0F) != 2) + { + LOG(1, 0, 0, "Bad L2TP ver %d\n", (buf[1] & 0x0F) != 2); + STAT(tunnel_rx_errors); + return; + } + if (*buf & 0x40) + { // length + l = ntohs(*(uint16_t *) p); + p += 2; + } + t = ntohs(*(uint16_t *) p); + p += 2; + s = ntohs(*(uint16_t *) p); + p += 2; + if (s >= MAXSESSION) + { + LOG(1, s, t, "Received UDP packet with invalid session ID\n"); + STAT(tunnel_rx_errors); + return; + } + if (t >= MAXTUNNEL) + { + LOG(1, s, t, "Received UDP packet with invalid tunnel ID\n"); + STAT(tunnel_rx_errors); + return; + } + if (*buf & 0x08) + { // ns/nr + ns = ntohs(*(uint16_t *) p); + p += 2; + nr = ntohs(*(uint16_t *) p); + p += 2; + } + if (*buf & 0x02) + { // offset + uint16_t o = ntohs(*(uint16_t *) p); + p += o + 2; + } + if ((p - buf) > l) + { + LOG(1, s, t, "Bad length %d>%d\n", (int) (p - buf), l); + STAT(tunnel_rx_errors); + return; + } + l -= (p - buf); + + // used to time out old tunnels + if (t && tunnel[t].state == TUNNELOPEN) + tunnel[t].lastrec = time_now; + + if (*buf & 0x80) + { // control + uint16_t message = 0xFFFF; // message type + uint8_t fatal = 0; + uint8_t mandatory = 0; + uint16_t asession = 0; // assigned session + uint32_t amagic = 0; // magic number + uint8_t aflags = 0; // flags from last LCF + uint16_t version = 0x0100; // protocol version (we handle 0.0 as well and send that back just in case) + char called[MAXTEL] = ""; // called number + char calling[MAXTEL] = ""; // calling number + + if (!config->cluster_iam_master) + { + master_forward_packet(buf, len, addr->sin_addr.s_addr, addr->sin_port); + return; + } + + // control messages must have bits 0x80|0x40|0x08 + // (type, length and sequence) set, and bits 0x02|0x01 + // (offset and priority) clear + if ((*buf & 0xCB) != 0xC8) + { + LOG(1, s, t, "Bad control header %02X\n", *buf); + STAT(tunnel_rx_errors); + return; + } + + // check for duplicate tunnel open message + if (!t && ns == 0) + { + int i; + + // + // Is this a duplicate of the first packet? (SCCRQ) + // + for (i = 1; i <= config->cluster_highest_tunnelid ; ++i) + { + if (tunnel[i].state != TUNNELOPENING || + tunnel[i].ip != ntohl(*(in_addr_t *) & addr->sin_addr) || + tunnel[i].port != ntohs(addr->sin_port) ) + continue; + t = i; + LOG(3, s, t, "Duplicate SCCRQ?\n"); + break; + } + } + + LOG(3, s, t, "Control message (%d bytes): (unacked %d) l-ns %d l-nr %d r-ns %d r-nr %d\n", + l, tunnel[t].controlc, tunnel[t].ns, tunnel[t].nr, ns, nr); + + // if no tunnel specified, assign one + if (!t) + { + if (!(t = new_tunnel())) + { + LOG(1, 0, 0, "No more tunnels\n"); + STAT(tunnel_overflow); + return; + } + tunnelclear(t); + tunnel[t].ip = ntohl(*(in_addr_t *) & addr->sin_addr); + tunnel[t].port = ntohs(addr->sin_port); + tunnel[t].window = 4; // default window + STAT(tunnel_created); + LOG(1, 0, t, " New tunnel from %s:%u ID %d\n", + fmtaddr(htonl(tunnel[t].ip), 0), tunnel[t].port, t); + } + + // If the 'ns' just received is not the 'nr' we're + // expecting, just send an ack and drop it. + // + // if 'ns' is less, then we got a retransmitted packet. + // if 'ns' is greater than missed a packet. Either way + // we should ignore it. + if (ns != tunnel[t].nr) + { + // is this the sequence we were expecting? + STAT(tunnel_rx_errors); + LOG(1, 0, t, " Out of sequence tunnel %d, (%d is not the expected %d)\n", + t, ns, tunnel[t].nr); + + if (l) // Is this not a ZLB? + controlnull(t); + return; + } + + // check sequence of this message + { + int skip = tunnel[t].window; // track how many in-window packets are still in queue + // some to clear maybe? + while (tunnel[t].controlc > 0 && (((tunnel[t].ns - tunnel[t].controlc) - nr) & 0x8000)) + { + controlt *c = tunnel[t].controls; + tunnel[t].controls = c->next; + tunnel[t].controlc--; + c->next = controlfree; + controlfree = c; + skip--; + tunnel[t].try = 0; // we have progress + } + + // receiver advance (do here so quoted correctly in any sends below) + if (l) tunnel[t].nr = (ns + 1); + if (skip < 0) skip = 0; + if (skip < tunnel[t].controlc) + { + // some control packets can now be sent that were previous stuck out of window + int tosend = tunnel[t].window - skip; + controlt *c = tunnel[t].controls; + while (c && skip) + { + c = c->next; + skip--; + } + while (c && tosend) + { + tunnel[t].try = 0; // first send + tunnelsend(c->buf, c->length, t); + c = c->next; + tosend--; + } + } + if (!tunnel[t].controlc) + tunnel[t].retry = 0; // caught up + } + if (l) + { // if not a null message + int result = 0; + int error = 0; + char *msg = 0; + + // Default disconnect cause/message on receipt of CDN. Set to + // more specific value from attribute 1 (result code) or 46 + // (disconnect cause) if present below. + int disc_cause_set = 0; + int disc_cause = TERM_NAS_REQUEST; + char const *disc_reason = "Closed (Received CDN)."; + + // process AVPs + while (l && !(fatal & 0x80)) // 0x80 = mandatory AVP + { + uint16_t n = (ntohs(*(uint16_t *) p) & 0x3FF); + uint8_t *b = p; + uint8_t flags = *p; + uint16_t mtype; + + if (n > l) + { + LOG(1, s, t, "Invalid length in AVP\n"); + STAT(tunnel_rx_errors); + return; + } + p += n; // next + l -= n; + if (flags & 0x3C) // reserved bits, should be clear + { + LOG(1, s, t, "Unrecognised AVP flags %02X\n", *b); + fatal = flags; + result = 2; // general error + error = 3; // reserved field non-zero + msg = 0; + continue; // next + } + b += 2; + if (*(uint16_t *) (b)) + { + LOG(2, s, t, "Unknown AVP vendor %d\n", ntohs(*(uint16_t *) (b))); + fatal = flags; + result = 2; // general error + error = 6; // generic vendor-specific error + msg = "unsupported vendor-specific"; + continue; // next + } + b += 2; + mtype = ntohs(*(uint16_t *) (b)); + b += 2; + n -= 6; + + if (flags & 0x40) + { + uint16_t orig_len; + + // handle hidden AVPs + if (!*config->l2tp_secret) + { + LOG(1, s, t, "Hidden AVP requested, but no L2TP secret.\n"); + fatal = flags; + result = 2; // general error + error = 6; // generic vendor-specific error + msg = "secret not specified"; + continue; + } + if (!session[s].random_vector_length) + { + LOG(1, s, t, "Hidden AVP requested, but no random vector.\n"); + fatal = flags; + result = 2; // general error + error = 6; // generic + msg = "no random vector"; + continue; + } + if (n < 8) + { + LOG(2, s, t, "Short hidden AVP.\n"); + fatal = flags; + result = 2; // general error + error = 2; // length is wrong + msg = 0; + continue; + } + + // Unhide the AVP + unhide_value(b, n, mtype, session[s].random_vector, session[s].random_vector_length); + + orig_len = ntohs(*(uint16_t *) b); + if (orig_len > n + 2) + { + LOG(1, s, t, "Original length %d too long in hidden AVP of length %d; wrong secret?\n", + orig_len, n); + + fatal = flags; + result = 2; // general error + error = 2; // length is wrong + msg = 0; + continue; + } + + b += 2; + n = orig_len; + } + + LOG(4, s, t, " AVP %d (%s) len %d%s%s\n", mtype, l2tp_avp_name(mtype), n, + flags & 0x40 ? ", hidden" : "", flags & 0x80 ? ", mandatory" : ""); + + switch (mtype) + { + case 0: // message type + message = ntohs(*(uint16_t *) b); + mandatory = flags & 0x80; + LOG(4, s, t, " Message type = %d (%s)\n", *b, l2tp_code(message)); + break; + case 1: // result code + { + uint16_t rescode = ntohs(*(uint16_t *) b); + char const *resdesc = "(unknown)"; + char const *errdesc = NULL; + int cause = 0; + + if (message == 4) + { /* StopCCN */ + resdesc = l2tp_stopccn_result_code(rescode); + cause = TERM_LOST_SERVICE; + } + else if (message == 14) + { /* CDN */ + resdesc = l2tp_cdn_result_code(rescode); + if (rescode == 1) + cause = TERM_LOST_CARRIER; + else + cause = TERM_ADMIN_RESET; + } + + LOG(4, s, t, " Result Code %d: %s\n", rescode, resdesc); + if (n >= 4) + { + uint16_t errcode = ntohs(*(uint16_t *)(b + 2)); + errdesc = l2tp_error_code(errcode); + LOG(4, s, t, " Error Code %d: %s\n", errcode, errdesc); + } + if (n > 4) + LOG(4, s, t, " Error String: %.*s\n", n-4, b+4); + + if (cause && disc_cause_set < mtype) // take cause from attrib 46 in preference + { + disc_cause_set = mtype; + disc_reason = errdesc ? errdesc : resdesc; + disc_cause = cause; + } + + break; + } + break; + case 2: // protocol version + { + version = ntohs(*(uint16_t *) (b)); + LOG(4, s, t, " Protocol version = %d\n", version); + if (version && version != 0x0100) + { // allow 0.0 and 1.0 + LOG(1, s, t, " Bad protocol version %04X\n", version); + fatal = flags; + result = 5; // unspported protocol version + error = 0x0100; // supported version + msg = 0; + continue; // next + } + } + break; + case 3: // framing capabilities + break; + case 4: // bearer capabilities + break; + case 5: // tie breaker + // We never open tunnels, so we don't care about tie breakers + continue; + case 6: // firmware revision + break; + case 7: // host name + memset(tunnel[t].hostname, 0, sizeof(tunnel[t].hostname)); + memcpy(tunnel[t].hostname, b, (n < sizeof(tunnel[t].hostname)) ? n : sizeof(tunnel[t].hostname) - 1); + LOG(4, s, t, " Tunnel hostname = \"%s\"\n", tunnel[t].hostname); + // TBA - to send to RADIUS + break; + case 8: // vendor name + memset(tunnel[t].vendor, 0, sizeof(tunnel[t].vendor)); + memcpy(tunnel[t].vendor, b, (n < sizeof(tunnel[t].vendor)) ? n : sizeof(tunnel[t].vendor) - 1); + LOG(4, s, t, " Vendor name = \"%s\"\n", tunnel[t].vendor); + break; + case 9: // assigned tunnel + tunnel[t].far = ntohs(*(uint16_t *) (b)); + LOG(4, s, t, " Remote tunnel id = %d\n", tunnel[t].far); + break; + case 10: // rx window + tunnel[t].window = ntohs(*(uint16_t *) (b)); + if (!tunnel[t].window) + tunnel[t].window = 1; // window of 0 is silly + LOG(4, s, t, " rx window = %d\n", tunnel[t].window); + break; + case 11: // Challenge + { + LOG(4, s, t, " LAC requested CHAP authentication for tunnel\n"); + build_chap_response(b, 2, n, &chapresponse); + } + break; + case 13: // Response + // Why did they send a response? We never challenge. + LOG(2, s, t, " received unexpected challenge response\n"); + break; + + case 14: // assigned session + asession = session[s].far = ntohs(*(uint16_t *) (b)); + LOG(4, s, t, " assigned session = %d\n", asession); + break; + case 15: // call serial number + LOG(4, s, t, " call serial number = %d\n", ntohl(*(uint32_t *)b)); + break; + case 18: // bearer type + LOG(4, s, t, " bearer type = %d\n", ntohl(*(uint32_t *)b)); + // TBA - for RADIUS + break; + case 19: // framing type + LOG(4, s, t, " framing type = %d\n", ntohl(*(uint32_t *)b)); + // TBA + break; + case 21: // called number + memset(called, 0, sizeof(called)); + memcpy(called, b, (n < sizeof(called)) ? n : sizeof(called) - 1); + LOG(4, s, t, " Called <%s>\n", called); + break; + case 22: // calling number + memset(calling, 0, sizeof(calling)); + memcpy(calling, b, (n < sizeof(calling)) ? n : sizeof(calling) - 1); + LOG(4, s, t, " Calling <%s>\n", calling); + break; + case 23: // subtype + break; + case 24: // tx connect speed + if (n == 4) + { + session[s].tx_connect_speed = ntohl(*(uint32_t *)b); + } + else + { + // AS5300s send connect speed as a string + char tmp[30]; + memset(tmp, 0, sizeof(tmp)); + memcpy(tmp, b, (n < sizeof(tmp)) ? n : sizeof(tmp) - 1); + session[s].tx_connect_speed = atol(tmp); + } + LOG(4, s, t, " TX connect speed <%u>\n", session[s].tx_connect_speed); + break; + case 38: // rx connect speed + if (n == 4) + { + session[s].rx_connect_speed = ntohl(*(uint32_t *)b); + } + else + { + // AS5300s send connect speed as a string + char tmp[30]; + memset(tmp, 0, sizeof(tmp)); + memcpy(tmp, b, (n < sizeof(tmp)) ? n : sizeof(tmp) - 1); + session[s].rx_connect_speed = atol(tmp); + } + LOG(4, s, t, " RX connect speed <%u>\n", session[s].rx_connect_speed); + break; + case 25: // Physical Channel ID + { + uint32_t tmp = ntohl(*(uint32_t *) b); + LOG(4, s, t, " Physical Channel ID <%X>\n", tmp); + break; + } + case 29: // Proxy Authentication Type + { + uint16_t atype = ntohs(*(uint16_t *)b); + LOG(4, s, t, " Proxy Auth Type %d (%s)\n", atype, ppp_auth_type(atype)); + break; + } + case 30: // Proxy Authentication Name + { + char authname[64]; + memset(authname, 0, sizeof(authname)); + memcpy(authname, b, (n < sizeof(authname)) ? n : sizeof(authname) - 1); + LOG(4, s, t, " Proxy Auth Name (%s)\n", + authname); + break; + } + case 31: // Proxy Authentication Challenge + { + LOG(4, s, t, " Proxy Auth Challenge\n"); + break; + } + case 32: // Proxy Authentication ID + { + uint16_t authid = ntohs(*(uint16_t *)(b)); + LOG(4, s, t, " Proxy Auth ID (%d)\n", authid); + break; + } + case 33: // Proxy Authentication Response + LOG(4, s, t, " Proxy Auth Response\n"); + break; + case 27: // last sent lcp + { // find magic number + uint8_t *p = b, *e = p + n; + while (p + 1 < e && p[1] && p + p[1] <= e) + { + if (*p == 5 && p[1] == 6) // Magic-Number + amagic = ntohl(*(uint32_t *) (p + 2)); + else if (*p == 7) // Protocol-Field-Compression + aflags |= SESSION_PFC; + else if (*p == 8) // Address-and-Control-Field-Compression + aflags |= SESSION_ACFC; + p += p[1]; + } + } + break; + case 28: // last recv lcp confreq + break; + case 26: // Initial Received LCP CONFREQ + break; + case 39: // seq required - we control it as an LNS anyway... + break; + case 36: // Random Vector + LOG(4, s, t, " Random Vector received. Enabled AVP Hiding.\n"); + memset(session[s].random_vector, 0, sizeof(session[s].random_vector)); + if (n > sizeof(session[s].random_vector)) + n = sizeof(session[s].random_vector); + memcpy(session[s].random_vector, b, n); + session[s].random_vector_length = n; + break; + case 46: // ppp disconnect cause + if (n >= 5) + { + uint16_t code = ntohs(*(uint16_t *) b); + uint16_t proto = ntohs(*(uint16_t *) (b + 2)); + uint8_t dir = *(b + 4); + + LOG(4, s, t, " PPP disconnect cause " + "(code=%u, proto=%04X, dir=%u, msg=\"%.*s\")\n", + code, proto, dir, n - 5, b + 5); + + disc_cause_set = mtype; + + switch (code) + { + case 1: // admin disconnect + disc_cause = TERM_ADMIN_RESET; + disc_reason = "Administrative disconnect"; + break; + case 3: // lcp terminate + if (dir != 2) break; // 1=peer (LNS), 2=local (LAC) + disc_cause = TERM_USER_REQUEST; + disc_reason = "Normal disconnection"; + break; + case 4: // compulsory encryption unavailable + if (dir != 1) break; // 1=refused by peer, 2=local + disc_cause = TERM_USER_ERROR; + disc_reason = "Compulsory encryption refused"; + break; + case 5: // lcp: fsm timeout + disc_cause = TERM_PORT_ERROR; + disc_reason = "LCP: FSM timeout"; + break; + case 6: // lcp: no recognisable lcp packets received + disc_cause = TERM_PORT_ERROR; + disc_reason = "LCP: no recognisable LCP packets"; + break; + case 7: // lcp: magic-no error (possibly looped back) + disc_cause = TERM_PORT_ERROR; + disc_reason = "LCP: magic-no error (possible loop)"; + break; + case 8: // lcp: echo request timeout + disc_cause = TERM_PORT_ERROR; + disc_reason = "LCP: echo request timeout"; + break; + case 13: // auth: fsm timeout + disc_cause = TERM_SERVICE_UNAVAILABLE; + disc_reason = "Authentication: FSM timeout"; + break; + case 15: // auth: unacceptable auth protocol + disc_cause = TERM_SERVICE_UNAVAILABLE; + disc_reason = "Unacceptable authentication protocol"; + break; + case 16: // auth: authentication failed + disc_cause = TERM_SERVICE_UNAVAILABLE; + disc_reason = "Authentication failed"; + break; + case 17: // ncp: fsm timeout + disc_cause = TERM_SERVICE_UNAVAILABLE; + disc_reason = "NCP: FSM timeout"; + break; + case 18: // ncp: no ncps available + disc_cause = TERM_SERVICE_UNAVAILABLE; + disc_reason = "NCP: no NCPs available"; + break; + case 19: // ncp: failure to converge on acceptable address + disc_cause = TERM_SERVICE_UNAVAILABLE; + disc_reason = (dir == 1) + ? "NCP: too many Configure-Naks received from peer" + : "NCP: too many Configure-Naks sent to peer"; + break; + case 20: // ncp: user not permitted to use any address + disc_cause = TERM_SERVICE_UNAVAILABLE; + disc_reason = (dir == 1) + ? "NCP: local link address not acceptable to peer" + : "NCP: remote link address not acceptable"; + break; + } + } + break; + default: + { + static char e[] = "unknown AVP 0xXXXX"; + LOG(2, s, t, " Unknown AVP type %d\n", mtype); + fatal = flags; + result = 2; // general error + error = 8; // unknown mandatory AVP + sprintf((msg = e) + 14, "%04x", mtype); + continue; // next + } + } + } + // process message + if (fatal & 0x80) + tunnelshutdown(t, "Invalid mandatory AVP", result, error, msg); + else + switch (message) + { + case 1: // SCCRQ - Start Control Connection Request + tunnel[t].state = TUNNELOPENING; + if (main_quit != QUIT_SHUTDOWN) + { + controlt *c = controlnew(2); // sending SCCRP + control16(c, 2, version, 1); // protocol version + control32(c, 3, 3, 1); // framing + controls(c, 7, hostname, 1); // host name + if (chapresponse) controlb(c, 13, chapresponse, 16, 1); // Challenge response + control16(c, 9, t, 1); // assigned tunnel + controladd(c, 0, t); // send the resply + } + else + { + tunnelshutdown(t, "Shutting down", 6, 0, 0); + } + break; + case 2: // SCCRP + tunnel[t].state = TUNNELOPEN; + break; + case 3: // SCCN + tunnel[t].state = TUNNELOPEN; + controlnull(t); // ack + break; + case 4: // StopCCN + controlnull(t); // ack + tunnelshutdown(t, "Stopped", 0, 0, 0); // Shut down cleanly + break; + case 6: // HELLO + controlnull(t); // simply ACK + break; + case 7: // OCRQ + // TBA + break; + case 8: // OCRO + // TBA + break; + case 9: // OCCN + // TBA + break; + case 10: // ICRQ + if (sessionfree && main_quit != QUIT_SHUTDOWN) + { + controlt *c = controlnew(11); // ICRP + + s = sessionfree; + sessionfree = session[s].next; + memset(&session[s], 0, sizeof(session[s])); + + if (s > config->cluster_highest_sessionid) + config->cluster_highest_sessionid = s; + + session[s].opened = time_now; + session[s].tunnel = t; + session[s].far = asession; + session[s].last_packet = time_now; + LOG(3, s, t, "New session (%d/%d)\n", tunnel[t].far, session[s].far); + control16(c, 14, s, 1); // assigned session + controladd(c, asession, t); // send the reply + + strncpy(session[s].called, called, sizeof(session[s].called) - 1); + strncpy(session[s].calling, calling, sizeof(session[s].calling) - 1); + + session[s].ppp.phase = Establish; + session[s].ppp.lcp = Starting; + + STAT(session_created); + break; + } + + { + controlt *c = controlnew(14); // CDN + if (!sessionfree) + { + STAT(session_overflow); + LOG(1, 0, t, "No free sessions\n"); + control16(c, 1, 4, 0); // temporary lack of resources + } + else + control16(c, 1, 2, 7); // shutting down, try another + + controladd(c, asession, t); // send the message + } + return; + case 11: // ICRP + // TBA + break; + case 12: // ICCN + if (amagic == 0) amagic = time_now; + session[s].magic = amagic; // set magic number + session[s].flags = aflags; // set flags received + session[s].mru = PPPoE_MRU; // default + controlnull(t); // ack + + // start LCP + sess_local[s].lcp_authtype = config->radius_authprefer; + sess_local[s].ppp_mru = MRU; + sendlcp(s, t); + change_state(s, lcp, RequestSent); + break; + + case 14: // CDN + controlnull(t); // ack + sessionshutdown(s, disc_reason, CDN_NONE, disc_cause); + break; + case 0xFFFF: + LOG(1, s, t, "Missing message type\n"); + break; + default: + STAT(tunnel_rx_errors); + if (mandatory) + tunnelshutdown(t, "Unknown message type", 2, 6, "unknown message type"); + else + LOG(1, s, t, "Unknown message type %d\n", message); + break; + } + if (chapresponse) free(chapresponse); + cluster_send_tunnel(t); + } + else + { + LOG(4, s, t, " Got a ZLB ack\n"); + } + } + else + { // data + uint16_t proto; + + LOG_HEX(5, "Receive Tunnel Data", p, l); + if (l > 2 && p[0] == 0xFF && p[1] == 0x03) + { // HDLC address header, discard + p += 2; + l -= 2; + } + if (l < 2) + { + LOG(1, s, t, "Short ppp length %d\n", l); + STAT(tunnel_rx_errors); + return; + } + if (*p & 1) + { + proto = *p++; + l--; + } + else + { + proto = ntohs(*(uint16_t *) p); + p += 2; + l -= 2; + } + + if (s && !session[s].opened) // Is something wrong?? + { + if (!config->cluster_iam_master) + { + // Pass it off to the master to deal with.. + master_forward_packet(buf, len, addr->sin_addr.s_addr, addr->sin_port); + return; + } + + + LOG(1, s, t, "UDP packet contains session which is not opened. Dropping packet.\n"); + STAT(tunnel_rx_errors); + return; + } + + if (proto == PPPPAP) + { + session[s].last_packet = time_now; + if (!config->cluster_iam_master) { master_forward_packet(buf, len, addr->sin_addr.s_addr, addr->sin_port); return; } + processpap(s, t, p, l); + } + else if (proto == PPPCHAP) + { + session[s].last_packet = time_now; + if (!config->cluster_iam_master) { master_forward_packet(buf, len, addr->sin_addr.s_addr, addr->sin_port); return; } + processchap(s, t, p, l); + } + else if (proto == PPPLCP) + { + session[s].last_packet = time_now; + if (!config->cluster_iam_master) { master_forward_packet(buf, len, addr->sin_addr.s_addr, addr->sin_port); return; } + processlcp(s, t, p, l); + } + else if (proto == PPPIPCP) + { + session[s].last_packet = time_now; + if (!config->cluster_iam_master) { master_forward_packet(buf, len, addr->sin_addr.s_addr, addr->sin_port); return; } + processipcp(s, t, p, l); + } + else if (proto == PPPIPV6CP && config->ipv6_prefix.s6_addr[0]) + { + session[s].last_packet = time_now; + if (!config->cluster_iam_master) { master_forward_packet(buf, len, addr->sin_addr.s_addr, addr->sin_port); return; } + processipv6cp(s, t, p, l); + } + else if (proto == PPPCCP) + { + session[s].last_packet = time_now; + if (!config->cluster_iam_master) { master_forward_packet(buf, len, addr->sin_addr.s_addr, addr->sin_port); return; } + processccp(s, t, p, l); + } + else if (proto == PPPIP) + { + if (session[s].die) + { + LOG(4, s, t, "Session %d is closing. Don't process PPP packets\n", s); + return; // closing session, PPP not processed + } + + session[s].last_packet = time_now; + if (session[s].walled_garden && !config->cluster_iam_master) + { + master_forward_packet(buf, len, addr->sin_addr.s_addr, addr->sin_port); + return; + } + + processipin(s, t, p, l); + } + else if (proto == PPPIPV6 && config->ipv6_prefix.s6_addr[0]) + { + if (session[s].die) + { + LOG(4, s, t, "Session %d is closing. Don't process PPP packets\n", s); + return; // closing session, PPP not processed + } + + session[s].last_packet = time_now; + if (session[s].walled_garden && !config->cluster_iam_master) + { + master_forward_packet(buf, len, addr->sin_addr.s_addr, addr->sin_port); + return; + } + + processipv6in(s, t, p, l); + } + else if (session[s].ppp.lcp == Opened) + { + session[s].last_packet = time_now; + if (!config->cluster_iam_master) { master_forward_packet(buf, len, addr->sin_addr.s_addr, addr->sin_port); return; } + protoreject(s, t, p, l, proto); + } + else + { + LOG(2, s, t, "Unknown PPP protocol 0x%04X received in LCP %s state\n", + proto, ppp_state(session[s].ppp.lcp)); + } + } +} + +// read and process packet on tun +static void processtun(uint8_t * buf, int len) +{ + LOG_HEX(5, "Receive TUN Data", buf, len); + STAT(tun_rx_packets); + INC_STAT(tun_rx_bytes, len); + + CSTAT(processtun); + + eth_rx_pkt++; + eth_rx += len; + if (len < 22) + { + LOG(1, 0, 0, "Short tun packet %d bytes\n", len); + STAT(tun_rx_errors); + return; + } + + if (*(uint16_t *) (buf + 2) == htons(PKTIP)) // IPv4 + processipout(buf, len); + else if (*(uint16_t *) (buf + 2) == htons(PKTIPV6) // IPV6 + && config->ipv6_prefix.s6_addr[0]) + processipv6out(buf, len); + + // Else discard. +} + +// Handle retries, timeouts. Runs every 1/10th sec, want to ensure +// that we look at the whole of the tunnel, radius and session tables +// every second +static void regular_cleanups(double period) +{ + // Next tunnel, radius and session to check for actions on. + static tunnelidt t = 0; + static int r = 0; + static sessionidt s = 0; + + int t_actions = 0; + int r_actions = 0; + int s_actions = 0; + + int t_slice; + int r_slice; + int s_slice; + + int i; + int a; + + // divide up tables into slices based on the last run + t_slice = config->cluster_highest_tunnelid * period; + r_slice = (MAXRADIUS - 1) * period; + s_slice = config->cluster_highest_sessionid * period; + + if (t_slice < 1) + t_slice = 1; + else if (t_slice > config->cluster_highest_tunnelid) + t_slice = config->cluster_highest_tunnelid; + + if (r_slice < 1) + r_slice = 1; + else if (r_slice > (MAXRADIUS - 1)) + r_slice = MAXRADIUS - 1; + + if (s_slice < 1) + s_slice = 1; + else if (s_slice > config->cluster_highest_sessionid) + s_slice = config->cluster_highest_sessionid; + + LOG(4, 0, 0, "Begin regular cleanup (last %f seconds ago)\n", period); + + for (i = 0; i < t_slice; i++) + { + t++; + if (t > config->cluster_highest_tunnelid) + t = 1; + + // check for expired tunnels + if (tunnel[t].die && tunnel[t].die <= TIME) + { + STAT(tunnel_timeout); + tunnelkill(t, "Expired"); + t_actions++; + continue; + } + // check for message resend + if (tunnel[t].retry && tunnel[t].controlc) + { + // resend pending messages as timeout on reply + if (tunnel[t].retry <= TIME) + { + controlt *c = tunnel[t].controls; + uint8_t w = tunnel[t].window; + tunnel[t].try++; // another try + if (tunnel[t].try > 5) + tunnelkill(t, "Timeout on control message"); // game over + else + while (c && w--) + { + tunnelsend(c->buf, c->length, t); + c = c->next; + } + + t_actions++; + } + } + // Send hello + if (tunnel[t].state == TUNNELOPEN && !tunnel[t].controlc && (time_now - tunnel[t].lastrec) > 60) + { + controlt *c = controlnew(6); // sending HELLO + controladd(c, 0, t); // send the message + LOG(3, 0, t, "Sending HELLO message\n"); + t_actions++; + } + + // Check for tunnel changes requested from the CLI + if ((a = cli_tunnel_actions[t].action)) + { + cli_tunnel_actions[t].action = 0; + if (a & CLI_TUN_KILL) + { + LOG(2, 0, t, "Dropping tunnel by CLI\n"); + tunnelshutdown(t, "Requested by administrator", 1, 0, 0); + t_actions++; + } + } + } + + for (i = 0; i < r_slice; i++) + { + r++; + if (r >= MAXRADIUS) + r = 1; + + if (!radius[r].state) + continue; + + if (radius[r].retry <= TIME) + { + radiusretry(r); + r_actions++; + } + } + + for (i = 0; i < s_slice; i++) + { + s++; + if (s > config->cluster_highest_sessionid) + s = 1; + + if (!session[s].opened) // Session isn't in use + continue; + + // check for expired sessions + if (session[s].die) + { + if (session[s].die <= TIME) + { + sessionkill(s, "Expired"); + s_actions++; + } + continue; + } + + // PPP timeouts + if (sess_local[s].lcp.restart <= time_now) + { + int next_state = session[s].ppp.lcp; + switch (session[s].ppp.lcp) + { + case RequestSent: + case AckReceived: + next_state = RequestSent; + + case AckSent: + if (sess_local[s].lcp.conf_sent < config->ppp_max_configure) + { + LOG(3, s, session[s].tunnel, "No ACK for LCP ConfigReq... resending\n"); + sendlcp(s, session[s].tunnel); + change_state(s, lcp, next_state); + } + else + { + sessionshutdown(s, "No response to LCP ConfigReq.", CDN_ADMIN_DISC, TERM_LOST_SERVICE); + STAT(session_timeout); + } + + s_actions++; + } + + if (session[s].die) + continue; + } + + if (sess_local[s].ipcp.restart <= time_now) + { + int next_state = session[s].ppp.ipcp; + switch (session[s].ppp.ipcp) + { + case RequestSent: + case AckReceived: + next_state = RequestSent; + + case AckSent: + if (sess_local[s].ipcp.conf_sent < config->ppp_max_configure) + { + LOG(3, s, session[s].tunnel, "No ACK for IPCP ConfigReq... resending\n"); + sendipcp(s, session[s].tunnel); + change_state(s, ipcp, next_state); + } + else + { + sessionshutdown(s, "No response to IPCP ConfigReq.", CDN_ADMIN_DISC, TERM_LOST_SERVICE); + STAT(session_timeout); + } + + s_actions++; + } + + if (session[s].die) + continue; + } + + if (sess_local[s].ipv6cp.restart <= time_now) + { + int next_state = session[s].ppp.ipv6cp; + switch (session[s].ppp.ipv6cp) + { + case RequestSent: + case AckReceived: + next_state = RequestSent; + + case AckSent: + if (sess_local[s].ipv6cp.conf_sent < config->ppp_max_configure) + { + LOG(3, s, session[s].tunnel, "No ACK for IPV6CP ConfigReq... resending\n"); + sendipv6cp(s, session[s].tunnel); + change_state(s, ipv6cp, next_state); + } + else + { + LOG(3, s, session[s].tunnel, "No ACK for IPV6CP ConfigReq\n"); + change_state(s, ipv6cp, Stopped); + } + + s_actions++; + } + } + + if (sess_local[s].ccp.restart <= time_now) + { + int next_state = session[s].ppp.ccp; + switch (session[s].ppp.ccp) + { + case RequestSent: + case AckReceived: + next_state = RequestSent; + + case AckSent: + if (sess_local[s].ccp.conf_sent < config->ppp_max_configure) + { + LOG(3, s, session[s].tunnel, "No ACK for CCP ConfigReq... resending\n"); + sendccp(s, session[s].tunnel); + change_state(s, ccp, next_state); + } + else + { + LOG(3, s, session[s].tunnel, "No ACK for CCP ConfigReq\n"); + change_state(s, ccp, Stopped); + } + + s_actions++; + } + } + + // Drop sessions who have not responded within IDLE_TIMEOUT seconds + if (session[s].last_packet && (time_now - session[s].last_packet >= IDLE_TIMEOUT)) + { + sessionshutdown(s, "No response to LCP ECHO requests.", CDN_ADMIN_DISC, TERM_LOST_SERVICE); + STAT(session_timeout); + s_actions++; + continue; + } + + // No data in ECHO_TIMEOUT seconds, send LCP ECHO + if (session[s].ppp.phase >= Establish && (time_now - session[s].last_packet >= ECHO_TIMEOUT) && + (time_now - sess_local[s].last_echo >= ECHO_TIMEOUT)) + { + uint8_t b[MAXETHER]; + + uint8_t *q = makeppp(b, sizeof(b), 0, 0, s, session[s].tunnel, PPPLCP); + if (!q) continue; + + *q = EchoReq; + *(uint8_t *)(q + 1) = (time_now % 255); // ID + *(uint16_t *)(q + 2) = htons(8); // Length + *(uint32_t *)(q + 4) = session[s].ppp.lcp == Opened ? htonl(session[s].magic) : 0; // Magic Number + + LOG(4, s, session[s].tunnel, "No data in %d seconds, sending LCP ECHO\n", + (int)(time_now - session[s].last_packet)); + tunnelsend(b, 24, session[s].tunnel); // send it + sess_local[s].last_echo = time_now; + s_actions++; + } + + // Check for actions requested from the CLI + if ((a = cli_session_actions[s].action)) + { + int send = 0; + + cli_session_actions[s].action = 0; + if (a & CLI_SESS_KILL) + { + LOG(2, s, session[s].tunnel, "Dropping session by CLI\n"); + sessionshutdown(s, "Requested by administrator.", CDN_ADMIN_DISC, TERM_ADMIN_RESET); + a = 0; // dead, no need to check for other actions + s_actions++; + } + + if (a & CLI_SESS_NOSNOOP) + { + LOG(2, s, session[s].tunnel, "Unsnooping session by CLI\n"); + session[s].snoop_ip = 0; + session[s].snoop_port = 0; + s_actions++; + send++; + } + else if (a & CLI_SESS_SNOOP) + { + LOG(2, s, session[s].tunnel, "Snooping session by CLI (to %s:%d)\n", + fmtaddr(cli_session_actions[s].snoop_ip, 0), + cli_session_actions[s].snoop_port); + + session[s].snoop_ip = cli_session_actions[s].snoop_ip; + session[s].snoop_port = cli_session_actions[s].snoop_port; + s_actions++; + send++; + } + + if (a & CLI_SESS_NOTHROTTLE) + { + LOG(2, s, session[s].tunnel, "Un-throttling session by CLI\n"); + throttle_session(s, 0, 0); + s_actions++; + send++; + } + else if (a & CLI_SESS_THROTTLE) + { + LOG(2, s, session[s].tunnel, "Throttling session by CLI (to %dkb/s up and %dkb/s down)\n", + cli_session_actions[s].throttle_in, + cli_session_actions[s].throttle_out); + + throttle_session(s, cli_session_actions[s].throttle_in, cli_session_actions[s].throttle_out); + s_actions++; + send++; + } + + if (a & CLI_SESS_NOFILTER) + { + LOG(2, s, session[s].tunnel, "Un-filtering session by CLI\n"); + filter_session(s, 0, 0); + s_actions++; + send++; + } + else if (a & CLI_SESS_FILTER) + { + LOG(2, s, session[s].tunnel, "Filtering session by CLI (in=%d, out=%d)\n", + cli_session_actions[s].filter_in, + cli_session_actions[s].filter_out); + + filter_session(s, cli_session_actions[s].filter_in, cli_session_actions[s].filter_out); + s_actions++; + send++; + } + + if (send) + cluster_send_session(s); + } + + // RADIUS interim accounting + if (config->radius_accounting && config->radius_interim > 0 + && session[s].ip && !session[s].walled_garden + && !sess_local[s].radius // RADIUS already in progress + && time_now - sess_local[s].last_interim >= config->radius_interim) + { + int rad = radiusnew(s); + if (!rad) + { + LOG(1, s, session[s].tunnel, "No free RADIUS sessions for Interim message\n"); + STAT(radius_overflow); + continue; + } + + LOG(3, s, session[s].tunnel, "Sending RADIUS Interim for %s (%u)\n", + session[s].user, session[s].unique_id); + + radiussend(rad, RADIUSINTERIM); + sess_local[s].last_interim = time_now; + s_actions++; + } + } + + LOG(4, 0, 0, "End regular cleanup: checked %d/%d/%d tunnels/radius/sessions; %d/%d/%d actions\n", + t_slice, r_slice, s_slice, t_actions, r_actions, s_actions); +} + +// +// Are we in the middle of a tunnel update, or radius +// requests?? +// +static int still_busy(void) +{ + int i; + static clockt last_talked = 0; + static clockt start_busy_wait = 0; + + if (!config->cluster_iam_master) + { +#ifdef BGP + static time_t stopped_bgp = 0; + if (bgp_configured) + { + if (!stopped_bgp) + { + LOG(1, 0, 0, "Shutting down in %d seconds, stopping BGP...\n", QUIT_DELAY); + + for (i = 0; i < BGP_NUM_PEERS; i++) + if (bgp_peers[i].state == Established) + bgp_stop(&bgp_peers[i]); + + stopped_bgp = time_now; + + // we don't want to become master + cluster_send_ping(0); + + return 1; + } + + if (time_now < (stopped_bgp + QUIT_DELAY)) + return 1; + } +#endif /* BGP */ + + return 0; + } + + if (main_quit == QUIT_SHUTDOWN) + { + static int dropped = 0; + if (!dropped) + { + int i; + + LOG(1, 0, 0, "Dropping sessions and tunnels\n"); + for (i = 1; i < MAXTUNNEL; i++) + if (tunnel[i].ip || tunnel[i].state) + tunnelshutdown(i, "L2TPNS Closing", 6, 0, 0); + + dropped = 1; + } + } + + if (start_busy_wait == 0) + start_busy_wait = TIME; + + for (i = config->cluster_highest_tunnelid ; i > 0 ; --i) + { + if (!tunnel[i].controlc) + continue; + + if (last_talked != TIME) + { + LOG(2, 0, 0, "Tunnel %d still has un-acked control messages.\n", i); + last_talked = TIME; + } + return 1; + } + + // We stop waiting for radius after BUSY_WAIT_TIME 1/10th seconds + if (abs(TIME - start_busy_wait) > BUSY_WAIT_TIME) + { + LOG(1, 0, 0, "Giving up waiting for RADIUS to be empty. Shutting down anyway.\n"); + return 0; + } + + for (i = 1; i < MAXRADIUS; i++) + { + if (radius[i].state == RADIUSNULL) + continue; + if (radius[i].state == RADIUSWAIT) + continue; + + if (last_talked != TIME) + { + LOG(2, 0, 0, "Radius session %d is still busy (sid %d)\n", i, radius[i].session); + last_talked = TIME; + } + return 1; + } + + return 0; +} + +#ifdef HAVE_EPOLL +# include +#else +# define FAKE_EPOLL_IMPLEMENTATION /* include the functions */ +# include "fake_epoll.h" +#endif + +// the base set of fds polled: cli, cluster, tun, udp, control, dae +#define BASE_FDS 6 + +// additional polled fds +#ifdef BGP +# define EXTRA_FDS BGP_NUM_PEERS +#else +# define EXTRA_FDS 0 +#endif + +// main loop - gets packets on tun or udp and processes them +static void mainloop(void) +{ + int i; + uint8_t buf[65536]; + clockt next_cluster_ping = 0; // send initial ping immediately + struct epoll_event events[BASE_FDS + RADIUS_FDS + EXTRA_FDS]; + int maxevent = sizeof(events)/sizeof(*events); + + if ((epollfd = epoll_create(maxevent)) < 0) + { + LOG(0, 0, 0, "epoll_create failed: %s\n", strerror(errno)); + exit(1); + } + + LOG(4, 0, 0, "Beginning of main loop. clifd=%d, cluster_sockfd=%d, tunfd=%d, udpfd=%d, controlfd=%d, daefd=%d\n", + clifd, cluster_sockfd, tunfd, udpfd, controlfd, daefd); + + /* setup our fds to poll for input */ + { + static struct event_data d[BASE_FDS]; + struct epoll_event e; + + e.events = EPOLLIN; + i = 0; + + d[i].type = FD_TYPE_CLI; + e.data.ptr = &d[i++]; + epoll_ctl(epollfd, EPOLL_CTL_ADD, clifd, &e); + + d[i].type = FD_TYPE_CLUSTER; + e.data.ptr = &d[i++]; + epoll_ctl(epollfd, EPOLL_CTL_ADD, cluster_sockfd, &e); + + d[i].type = FD_TYPE_TUN; + e.data.ptr = &d[i++]; + epoll_ctl(epollfd, EPOLL_CTL_ADD, tunfd, &e); + + d[i].type = FD_TYPE_UDP; + e.data.ptr = &d[i++]; + epoll_ctl(epollfd, EPOLL_CTL_ADD, udpfd, &e); + + d[i].type = FD_TYPE_CONTROL; + e.data.ptr = &d[i++]; + epoll_ctl(epollfd, EPOLL_CTL_ADD, controlfd, &e); + + d[i].type = FD_TYPE_DAE; + e.data.ptr = &d[i++]; + epoll_ctl(epollfd, EPOLL_CTL_ADD, daefd, &e); + } + +#ifdef BGP + signal(SIGPIPE, SIG_IGN); + bgp_setup(config->as_number); + if (config->bind_address) + bgp_add_route(config->bind_address, 0xffffffff); + + for (i = 0; i < BGP_NUM_PEERS; i++) + { + if (config->neighbour[i].name[0]) + bgp_start(&bgp_peers[i], config->neighbour[i].name, + config->neighbour[i].as, config->neighbour[i].keepalive, + config->neighbour[i].hold, 0); /* 0 = routing disabled */ + } +#endif /* BGP */ + + while (!main_quit || still_busy()) + { + int more = 0; + int n; + + + if (main_reload) + { + main_reload = 0; + read_config_file(); + config->reload_config++; + } + + if (config->reload_config) + { + config->reload_config = 0; + update_config(); + } + +#ifdef BGP + bgp_set_poll(); +#endif /* BGP */ + + n = epoll_wait(epollfd, events, maxevent, 100); // timeout 100ms (1/10th sec) + STAT(select_called); + + TIME = now(NULL); + if (n < 0) + { + if (errno == EINTR || + errno == ECHILD) // EINTR was clobbered by sigchild_handler() + continue; + + LOG(0, 0, 0, "Error returned from select(): %s\n", strerror(errno)); + break; // exit + } + + if (n) + { + struct sockaddr_in addr; + struct in_addr local; + socklen_t alen; + int c, s; + int udp_ready = 0; + int tun_ready = 0; + int cluster_ready = 0; + int udp_pkts = 0; + int tun_pkts = 0; + int cluster_pkts = 0; +#ifdef BGP + uint32_t bgp_events[BGP_NUM_PEERS]; + memset(bgp_events, 0, sizeof(bgp_events)); +#endif /* BGP */ + + for (c = n, i = 0; i < c; i++) + { + struct event_data *d = events[i].data.ptr; + + switch (d->type) + { + case FD_TYPE_CLI: // CLI connections + { + int cli; + + alen = sizeof(addr); + if ((cli = accept(clifd, (struct sockaddr *)&addr, &alen)) >= 0) + { + cli_do(cli); + close(cli); + } + else + LOG(0, 0, 0, "accept error: %s\n", strerror(errno)); + + n--; + break; + } + + // these are handled below, with multiple interleaved reads + case FD_TYPE_CLUSTER: cluster_ready++; break; + case FD_TYPE_TUN: tun_ready++; break; + case FD_TYPE_UDP: udp_ready++; break; + + case FD_TYPE_CONTROL: // nsctl commands + alen = sizeof(addr); + s = recvfromto(controlfd, buf, sizeof(buf), MSG_WAITALL, (struct sockaddr *) &addr, &alen, &local); + if (s > 0) processcontrol(buf, s, &addr, alen, &local); + n--; + break; + + case FD_TYPE_DAE: // DAE requests + alen = sizeof(addr); + s = recvfromto(daefd, buf, sizeof(buf), MSG_WAITALL, (struct sockaddr *) &addr, &alen, &local); + if (s > 0) processdae(buf, s, &addr, alen, &local); + n--; + break; + + case FD_TYPE_RADIUS: // RADIUS response + alen = sizeof(addr); + s = recvfrom(radfds[d->index], buf, sizeof(buf), MSG_WAITALL, (struct sockaddr *) &addr, &alen); + if (s >= 0 && config->cluster_iam_master) + { + if (addr.sin_addr.s_addr == config->radiusserver[0] || + addr.sin_addr.s_addr == config->radiusserver[1]) + processrad(buf, s, d->index); + else + LOG(3, 0, 0, "Dropping RADIUS packet from unknown source %s\n", + fmtaddr(addr.sin_addr.s_addr, 0)); + } + + n--; + break; + +#ifdef BGP + case FD_TYPE_BGP: + bgp_events[d->index] = events[i].events; + n--; + break; +#endif /* BGP */ + + default: + LOG(0, 0, 0, "Unexpected fd type returned from epoll_wait: %d\n", d->type); + } + } + +#ifdef BGP + bgp_process(bgp_events); +#endif /* BGP */ + + for (c = 0; n && c < config->multi_read_count; c++) + { + // L2TP + if (udp_ready) + { + alen = sizeof(addr); + if ((s = recvfrom(udpfd, buf, sizeof(buf), 0, (void *) &addr, &alen)) > 0) + { + processudp(buf, s, &addr); + udp_pkts++; + } + else + { + udp_ready = 0; + n--; + } + } + + // incoming IP + if (tun_ready) + { + if ((s = read(tunfd, buf, sizeof(buf))) > 0) + { + processtun(buf, s); + tun_pkts++; + } + else + { + tun_ready = 0; + n--; + } + } + + // cluster + if (cluster_ready) + { + alen = sizeof(addr); + if ((s = recvfrom(cluster_sockfd, buf, sizeof(buf), MSG_WAITALL, (void *) &addr, &alen)) > 0) + { + processcluster(buf, s, addr.sin_addr.s_addr); + cluster_pkts++; + } + else + { + cluster_ready = 0; + n--; + } + } + } + + if (udp_pkts > 1 || tun_pkts > 1 || cluster_pkts > 1) + STAT(multi_read_used); + + if (c >= config->multi_read_count) + { + LOG(3, 0, 0, "Reached multi_read_count (%d); processed %d udp, %d tun and %d cluster packets\n", + config->multi_read_count, udp_pkts, tun_pkts, cluster_pkts); + + STAT(multi_read_exceeded); + more++; + } + } + + if (time_changed) + { + double Mbps = 1024.0 * 1024.0 / 8 * time_changed; + + // Log current traffic stats + snprintf(config->bandwidth, sizeof(config->bandwidth), + "UDP-ETH:%1.0f/%1.0f ETH-UDP:%1.0f/%1.0f TOTAL:%0.1f IN:%u OUT:%u", + (udp_rx / Mbps), (eth_tx / Mbps), (eth_rx / Mbps), (udp_tx / Mbps), + ((udp_tx + udp_rx + eth_tx + eth_rx) / Mbps), + udp_rx_pkt / time_changed, eth_rx_pkt / time_changed); + + udp_tx = udp_rx = 0; + udp_rx_pkt = eth_rx_pkt = 0; + eth_tx = eth_rx = 0; + time_changed = 0; + + if (config->dump_speed) + printf("%s\n", config->bandwidth); + + // Update the internal time counter + strftime(time_now_string, sizeof(time_now_string), "%Y-%m-%d %H:%M:%S", localtime(&time_now)); + + { + // Run timer hooks + struct param_timer p = { time_now }; + run_plugins(PLUGIN_TIMER, &p); + } + } + + // Runs on every machine (master and slaves). + if (next_cluster_ping <= TIME) + { + // Check to see which of the cluster is still alive.. + + cluster_send_ping(basetime); // Only does anything if we're a slave + cluster_check_master(); // ditto. + + cluster_heartbeat(); // Only does anything if we're a master. + cluster_check_slaves(); // ditto. + + master_update_counts(); // If we're a slave, send our byte counters to our master. + + if (config->cluster_iam_master && !config->cluster_iam_uptodate) + next_cluster_ping = TIME + 1; // out-of-date slaves, do fast updates + else + next_cluster_ping = TIME + config->cluster_hb_interval; + } + + if (!config->cluster_iam_master) + continue; + + // Run token bucket filtering queue.. + // Only run it every 1/10th of a second. + { + static clockt last_run = 0; + if (last_run != TIME) + { + last_run = TIME; + tbf_run_timer(); + } + } + + // Handle timeouts, retries etc. + { + static double last_clean = 0; + double this_clean; + double diff; + + TIME = now(&this_clean); + diff = this_clean - last_clean; + + // Run during idle time (after we've handled + // all incoming packets) or every 1/10th sec + if (!more || diff > 0.1) + { + regular_cleanups(diff); + last_clean = this_clean; + } + } + + if (*config->accounting_dir) + { + static clockt next_acct = 0; + static clockt next_shut_acct = 0; + + if (next_acct <= TIME) + { + // Dump accounting data + next_acct = TIME + ACCT_TIME; + next_shut_acct = TIME + ACCT_SHUT_TIME; + dump_acct_info(1); + } + else if (next_shut_acct <= TIME) + { + // Dump accounting data for shutdown sessions + next_shut_acct = TIME + ACCT_SHUT_TIME; + if (shut_acct_n) + dump_acct_info(0); + } + } + } + + // Are we the master and shutting down?? + if (config->cluster_iam_master) + cluster_heartbeat(); // Flush any queued changes.. + + // Ok. Notify everyone we're shutting down. If we're + // the master, this will force an election. + cluster_send_ping(0); + + // + // Important!!! We MUST not process any packets past this point! + LOG(1, 0, 0, "Shutdown complete\n"); +} + +static void stripdomain(char *host) +{ + char *p; + + if ((p = strchr(host, '.'))) + { + char *domain = 0; + char _domain[1024]; + + // strip off domain + FILE *resolv = fopen("/etc/resolv.conf", "r"); + if (resolv) + { + char buf[1024]; + char *b; + + while (fgets(buf, sizeof(buf), resolv)) + { + if (strncmp(buf, "domain", 6) && strncmp(buf, "search", 6)) + continue; + + if (!isspace(buf[6])) + continue; + + b = buf + 7; + while (isspace(*b)) b++; + + if (*b) + { + char *d = b; + while (*b && !isspace(*b)) b++; + *b = 0; + if (buf[0] == 'd') // domain is canonical + { + domain = d; + break; + } + + // first search line + if (!domain) + { + // hold, may be subsequent domain line + strncpy(_domain, d, sizeof(_domain))[sizeof(_domain)-1] = 0; + domain = _domain; + } + } + } + + fclose(resolv); + } + + if (domain) + { + int hl = strlen(host); + int dl = strlen(domain); + if (dl < hl && host[hl - dl - 1] == '.' && !strcmp(host + hl - dl, domain)) + host[hl -dl - 1] = 0; + } + else + { + *p = 0; // everything after first dot + } + } +} + +// Init data structures +static void initdata(int optdebug, char *optconfig) +{ + int i; + + if (!(config = shared_malloc(sizeof(configt)))) + { + fprintf(stderr, "Error doing malloc for configuration: %s\n", strerror(errno)); + exit(1); + } + + memset(config, 0, sizeof(configt)); + time(&config->start_time); + strncpy(config->config_file, optconfig, strlen(optconfig)); + config->debug = optdebug; + config->num_tbfs = MAXTBFS; + config->rl_rate = 28; // 28kbps + config->cluster_mcast_ttl = 1; + config->cluster_master_min_adv = 1; + config->ppp_restart_time = 3; + config->ppp_max_configure = 10; + config->ppp_max_failure = 5; + strcpy(config->random_device, RANDOMDEVICE); + + log_stream = stderr; + +#ifdef RINGBUFFER + if (!(ringbuffer = shared_malloc(sizeof(struct Tringbuffer)))) + { + LOG(0, 0, 0, "Error doing malloc for ringbuffer: %s\n", strerror(errno)); + exit(1); + } + memset(ringbuffer, 0, sizeof(struct Tringbuffer)); +#endif + + if (!(_statistics = shared_malloc(sizeof(struct Tstats)))) + { + LOG(0, 0, 0, "Error doing malloc for _statistics: %s\n", strerror(errno)); + exit(1); + } + if (!(tunnel = shared_malloc(sizeof(tunnelt) * MAXTUNNEL))) + { + LOG(0, 0, 0, "Error doing malloc for tunnels: %s\n", strerror(errno)); + exit(1); + } + if (!(session = shared_malloc(sizeof(sessiont) * MAXSESSION))) + { + LOG(0, 0, 0, "Error doing malloc for sessions: %s\n", strerror(errno)); + exit(1); + } + + if (!(sess_local = shared_malloc(sizeof(sessionlocalt) * MAXSESSION))) + { + LOG(0, 0, 0, "Error doing malloc for sess_local: %s\n", strerror(errno)); + exit(1); + } + + if (!(radius = shared_malloc(sizeof(radiust) * MAXRADIUS))) + { + LOG(0, 0, 0, "Error doing malloc for radius: %s\n", strerror(errno)); + exit(1); + } + + if (!(ip_address_pool = shared_malloc(sizeof(ippoolt) * MAXIPPOOL))) + { + LOG(0, 0, 0, "Error doing malloc for ip_address_pool: %s\n", strerror(errno)); + exit(1); + } + + if (!(ip_filters = shared_malloc(sizeof(ip_filtert) * MAXFILTER))) + { + LOG(0, 0, 0, "Error doing malloc for ip_filters: %s\n", strerror(errno)); + exit(1); + } + memset(ip_filters, 0, sizeof(ip_filtert) * MAXFILTER); + + if (!(cli_session_actions = shared_malloc(sizeof(struct cli_session_actions) * MAXSESSION))) + { + LOG(0, 0, 0, "Error doing malloc for cli session actions: %s\n", strerror(errno)); + exit(1); + } + memset(cli_session_actions, 0, sizeof(struct cli_session_actions) * MAXSESSION); + + if (!(cli_tunnel_actions = shared_malloc(sizeof(struct cli_tunnel_actions) * MAXSESSION))) + { + LOG(0, 0, 0, "Error doing malloc for cli tunnel actions: %s\n", strerror(errno)); + exit(1); + } + memset(cli_tunnel_actions, 0, sizeof(struct cli_tunnel_actions) * MAXSESSION); + + memset(tunnel, 0, sizeof(tunnelt) * MAXTUNNEL); + memset(session, 0, sizeof(sessiont) * MAXSESSION); + memset(radius, 0, sizeof(radiust) * MAXRADIUS); + memset(ip_address_pool, 0, sizeof(ippoolt) * MAXIPPOOL); + + // Put all the sessions on the free list marked as undefined. + for (i = 1; i < MAXSESSION; i++) + { + session[i].next = i + 1; + session[i].tunnel = T_UNDEF; // mark it as not filled in. + } + session[MAXSESSION - 1].next = 0; + sessionfree = 1; + + // Mark all the tunnels as undefined (waiting to be filled in by a download). + for (i = 1; i < MAXTUNNEL; i++) + tunnel[i].state = TUNNELUNDEF; // mark it as not filled in. + + if (!*hostname) + { + // Grab my hostname unless it's been specified + gethostname(hostname, sizeof(hostname)); + stripdomain(hostname); + } + + _statistics->start_time = _statistics->last_reset = time(NULL); + +#ifdef BGP + if (!(bgp_peers = shared_malloc(sizeof(struct bgp_peer) * BGP_NUM_PEERS))) + { + LOG(0, 0, 0, "Error doing malloc for bgp: %s\n", strerror(errno)); + exit(1); + } +#endif /* BGP */ +} + +static int assign_ip_address(sessionidt s) +{ + uint32_t i; + int best = -1; + time_t best_time = time_now; + char *u = session[s].user; + char reuse = 0; + + + CSTAT(assign_ip_address); + + for (i = 1; i < ip_pool_size; i++) + { + if (!ip_address_pool[i].address || ip_address_pool[i].assigned) + continue; + + if (!session[s].walled_garden && ip_address_pool[i].user[0] && !strcmp(u, ip_address_pool[i].user)) + { + best = i; + reuse = 1; + break; + } + + if (ip_address_pool[i].last < best_time) + { + best = i; + if (!(best_time = ip_address_pool[i].last)) + break; // never used, grab this one + } + } + + if (best < 0) + { + LOG(0, s, session[s].tunnel, "assign_ip_address(): out of addresses\n"); + return 0; + } + + session[s].ip = ip_address_pool[best].address; + session[s].ip_pool_index = best; + ip_address_pool[best].assigned = 1; + ip_address_pool[best].last = time_now; + ip_address_pool[best].session = s; + if (session[s].walled_garden) + /* Don't track addresses of users in walled garden (note: this + means that their address isn't "sticky" even if they get + un-gardened). */ + ip_address_pool[best].user[0] = 0; + else + strncpy(ip_address_pool[best].user, u, sizeof(ip_address_pool[best].user) - 1); + + STAT(ip_allocated); + LOG(4, s, session[s].tunnel, "assign_ip_address(): %s ip address %d from pool\n", + reuse ? "Reusing" : "Allocating", best); + + return 1; +} + +static void free_ip_address(sessionidt s) +{ + int i = session[s].ip_pool_index; + + + CSTAT(free_ip_address); + + if (!session[s].ip) + return; // what the? + + if (i < 0) // Is this actually part of the ip pool? + i = 0; + + STAT(ip_freed); + cache_ipmap(session[s].ip, -i); // Change the mapping to point back to the ip pool index. + session[s].ip = 0; + ip_address_pool[i].assigned = 0; + ip_address_pool[i].session = 0; + ip_address_pool[i].last = time_now; +} + +// +// Fsck the address pool against the session table. +// Normally only called when we become a master. +// +// This isn't perfect: We aren't keep tracking of which +// users used to have an IP address. +// +void rebuild_address_pool(void) +{ + int i; + + // + // Zero the IP pool allocation, and build + // a map from IP address to pool index. + for (i = 1; i < MAXIPPOOL; ++i) + { + ip_address_pool[i].assigned = 0; + ip_address_pool[i].session = 0; + if (!ip_address_pool[i].address) + continue; + + cache_ipmap(ip_address_pool[i].address, -i); // Map pool IP to pool index. + } + + for (i = 0; i < MAXSESSION; ++i) + { + int ipid; + if (!(session[i].opened && session[i].ip)) + continue; + + ipid = - lookup_ipmap(htonl(session[i].ip)); + + if (session[i].ip_pool_index < 0) + { + // Not allocated out of the pool. + if (ipid < 1) // Not found in the pool either? good. + continue; + + LOG(0, i, 0, "Session %d has an IP address (%s) that was marked static, but is in the pool (%d)!\n", + i, fmtaddr(session[i].ip, 0), ipid); + + // Fall through and process it as part of the pool. + } + + + if (ipid > MAXIPPOOL || ipid < 0) + { + LOG(0, i, 0, "Session %d has a pool IP that's not found in the pool! (%d)\n", i, ipid); + ipid = -1; + session[i].ip_pool_index = ipid; + continue; + } + + ip_address_pool[ipid].assigned = 1; + ip_address_pool[ipid].session = i; + ip_address_pool[ipid].last = time_now; + strncpy(ip_address_pool[ipid].user, session[i].user, sizeof(ip_address_pool[ipid].user) - 1); + session[i].ip_pool_index = ipid; + cache_ipmap(session[i].ip, i); // Fix the ip map. + } +} + +// +// Fix the address pool to match a changed session. +// (usually when the master sends us an update). +static void fix_address_pool(int sid) +{ + int ipid; + + ipid = session[sid].ip_pool_index; + + if (ipid > ip_pool_size) + return; // Ignore it. rebuild_address_pool will fix it up. + + if (ip_address_pool[ipid].address != session[sid].ip) + return; // Just ignore it. rebuild_address_pool will take care of it. + + ip_address_pool[ipid].assigned = 1; + ip_address_pool[ipid].session = sid; + ip_address_pool[ipid].last = time_now; + strncpy(ip_address_pool[ipid].user, session[sid].user, sizeof(ip_address_pool[ipid].user) - 1); +} + +// +// Add a block of addresses to the IP pool to hand out. +// +static void add_to_ip_pool(in_addr_t addr, in_addr_t mask) +{ + int i; + if (mask == 0) + mask = 0xffffffff; // Host route only. + + addr &= mask; + + if (ip_pool_size >= MAXIPPOOL) // Pool is full! + return ; + + for (i = addr ;(i & mask) == addr; ++i) + { + if ((i & 0xff) == 0 || (i&0xff) == 255) + continue; // Skip 0 and broadcast addresses. + + ip_address_pool[ip_pool_size].address = i; + ip_address_pool[ip_pool_size].assigned = 0; + ++ip_pool_size; + if (ip_pool_size >= MAXIPPOOL) + { + LOG(0, 0, 0, "Overflowed IP pool adding %s\n", fmtaddr(htonl(addr), 0)); + return; + } + } +} + +// Initialize the IP address pool +static void initippool() +{ + FILE *f; + char *p; + char buf[4096]; + memset(ip_address_pool, 0, sizeof(ip_address_pool)); + + if (!(f = fopen(IPPOOLFILE, "r"))) + { + LOG(0, 0, 0, "Can't load pool file " IPPOOLFILE ": %s\n", strerror(errno)); + exit(1); + } + + while (ip_pool_size < MAXIPPOOL && fgets(buf, 4096, f)) + { + char *pool = buf; + buf[4095] = 0; // Force it to be zero terminated/ + + if (*buf == '#' || *buf == '\n') + continue; // Skip comments / blank lines + if ((p = (char *)strrchr(buf, '\n'))) *p = 0; + if ((p = (char *)strchr(buf, ':'))) + { + in_addr_t src; + *p = '\0'; + src = inet_addr(buf); + if (src == INADDR_NONE) + { + LOG(0, 0, 0, "Invalid address pool IP %s\n", buf); + exit(1); + } + // This entry is for a specific IP only + if (src != config->bind_address) + continue; + *p = ':'; + pool = p+1; + } + if ((p = (char *)strchr(pool, '/'))) + { + // It's a range + int numbits = 0; + in_addr_t start = 0, mask = 0; + + LOG(2, 0, 0, "Adding IP address range %s\n", buf); + *p++ = 0; + if (!*p || !(numbits = atoi(p))) + { + LOG(0, 0, 0, "Invalid pool range %s\n", buf); + continue; + } + start = ntohl(inet_addr(pool)); + mask = (in_addr_t) (pow(2, numbits) - 1) << (32 - numbits); + + // Add a static route for this pool + LOG(5, 0, 0, "Adding route for address pool %s/%u\n", + fmtaddr(htonl(start), 0), 32 + mask); + + routeset(0, start, mask, 0, 1); + + add_to_ip_pool(start, mask); + } + else + { + // It's a single ip address + add_to_ip_pool(ntohl(inet_addr(pool)), 0); + } + } + fclose(f); + LOG(1, 0, 0, "IP address pool is %d addresses\n", ip_pool_size - 1); +} + +void snoop_send_packet(uint8_t *packet, uint16_t size, in_addr_t destination, uint16_t port) +{ + struct sockaddr_in snoop_addr = {0}; + if (!destination || !port || snoopfd <= 0 || size <= 0 || !packet) + return; + + snoop_addr.sin_family = AF_INET; + snoop_addr.sin_addr.s_addr = destination; + snoop_addr.sin_port = ntohs(port); + + LOG(5, 0, 0, "Snooping %d byte packet to %s:%d\n", size, + fmtaddr(snoop_addr.sin_addr.s_addr, 0), + htons(snoop_addr.sin_port)); + + if (sendto(snoopfd, packet, size, MSG_DONTWAIT | MSG_NOSIGNAL, (void *) &snoop_addr, sizeof(snoop_addr)) < 0) + LOG(0, 0, 0, "Error sending intercept packet: %s\n", strerror(errno)); + + STAT(packets_snooped); +} + +static int dump_session(FILE **f, sessiont *s) +{ + if (!s->opened || !s->ip || !(s->cin_delta || s->cout_delta) || !*s->user || s->walled_garden) + return 1; + + if (!*f) + { + char filename[1024]; + char timestr[64]; + time_t now = time(NULL); + + strftime(timestr, sizeof(timestr), "%Y%m%d%H%M%S", localtime(&now)); + snprintf(filename, sizeof(filename), "%s/%s", config->accounting_dir, timestr); + + if (!(*f = fopen(filename, "w"))) + { + LOG(0, 0, 0, "Can't write accounting info to %s: %s\n", filename, strerror(errno)); + return 0; + } + + LOG(3, 0, 0, "Dumping accounting information to %s\n", filename); + fprintf(*f, "# dslwatch.pl dump file V1.01\n" + "# host: %s\n" + "# endpoint: %s\n" + "# time: %ld\n" + "# uptime: %ld\n" + "# format: username ip qos uptxoctets downrxoctets\n", + hostname, + fmtaddr(config->bind_address ? config->bind_address : my_address, 0), + now, + now - basetime); + } + + LOG(4, 0, 0, "Dumping accounting information for %s\n", s->user); + fprintf(*f, "%s %s %d %u %u\n", + s->user, // username + fmtaddr(htonl(s->ip), 0), // ip + (s->throttle_in || s->throttle_out) ? 2 : 1, // qos + (uint32_t) s->cin_delta, // uptxoctets + (uint32_t) s->cout_delta); // downrxoctets + + s->cin_delta = s->cout_delta = 0; + + return 1; +} + +static void dump_acct_info(int all) +{ + int i; + FILE *f = NULL; + + + CSTAT(dump_acct_info); + + if (shut_acct_n) + { + for (i = 0; i < shut_acct_n; i++) + dump_session(&f, &shut_acct[i]); + + shut_acct_n = 0; + } + + if (all) + for (i = 1; i <= config->cluster_highest_sessionid; i++) + dump_session(&f, &session[i]); + + if (f) + fclose(f); +} + +// Main program +int main(int argc, char *argv[]) +{ + int i; + int optdebug = 0; + char *optconfig = CONFIGFILE; + + time(&basetime); // start clock + + // scan args + while ((i = getopt(argc, argv, "dvc:h:")) >= 0) + { + switch (i) + { + case 'd': + if (fork()) exit(0); + setsid(); + freopen("/dev/null", "r", stdin); + freopen("/dev/null", "w", stdout); + freopen("/dev/null", "w", stderr); + break; + case 'v': + optdebug++; + break; + case 'c': + optconfig = optarg; + break; + case 'h': + snprintf(hostname, sizeof(hostname), "%s", optarg); + break; + default: + printf("Args are:\n" + "\t-d\t\tDetach from terminal\n" + "\t-c \tConfig file\n" + "\t-h \tForce hostname\n" + "\t-v\t\tDebug\n"); + + return (0); + break; + } + } + + // Start the timer routine off + time(&time_now); + strftime(time_now_string, sizeof(time_now_string), "%Y-%m-%d %H:%M:%S", localtime(&time_now)); + + initplugins(); + initdata(optdebug, optconfig); + + init_cli(hostname); + read_config_file(); + update_config(); + init_tbf(config->num_tbfs); + + LOG(0, 0, 0, "L2TPNS version " VERSION "\n"); + LOG(0, 0, 0, "Copyright (c) 2003, 2004, 2005 Optus Internet Engineering\n"); + LOG(0, 0, 0, "Copyright (c) 2002 FireBrick (Andrews & Arnold Ltd / Watchfront Ltd) - GPL licenced\n"); + { + struct rlimit rlim; + rlim.rlim_cur = RLIM_INFINITY; + rlim.rlim_max = RLIM_INFINITY; + // Remove the maximum core size + if (setrlimit(RLIMIT_CORE, &rlim) < 0) + LOG(0, 0, 0, "Can't set ulimit: %s\n", strerror(errno)); + + // Make core dumps go to /tmp + chdir("/tmp"); + } + + if (config->scheduler_fifo) + { + int ret; + struct sched_param params = {0}; + params.sched_priority = 1; + + if (get_nprocs() < 2) + { + LOG(0, 0, 0, "Not using FIFO scheduler, there is only 1 processor in the system.\n"); + config->scheduler_fifo = 0; + } + else + { + if ((ret = sched_setscheduler(0, SCHED_FIFO, ¶ms)) == 0) + { + LOG(1, 0, 0, "Using FIFO scheduler. Say goodbye to any other processes running\n"); + } + else + { + LOG(0, 0, 0, "Error setting scheduler to FIFO: %s\n", strerror(errno)); + config->scheduler_fifo = 0; + } + } + } + + /* Set up the cluster communications port. */ + if (cluster_init() < 0) + exit(1); + + inittun(); + LOG(1, 0, 0, "Set up on interface %s\n", config->tundevice); + + initudp(); + initrad(); + initippool(); + + // seed prng + { + unsigned seed = time_now ^ getpid(); + LOG(4, 0, 0, "Seeding the pseudo random generator: %u\n", seed); + srand(seed); + } + + signal(SIGHUP, sighup_handler); + signal(SIGCHLD, sigchild_handler); + signal(SIGTERM, shutdown_handler); + signal(SIGINT, shutdown_handler); + signal(SIGQUIT, shutdown_handler); + + // Prevent us from getting paged out + if (config->lock_pages) + { + if (!mlockall(MCL_CURRENT)) + LOG(1, 0, 0, "Locking pages into memory\n"); + else + LOG(0, 0, 0, "Can't lock pages: %s\n", strerror(errno)); + } + + // Drop privileges here + if (config->target_uid > 0 && geteuid() == 0) + setuid(config->target_uid); + + mainloop(); + + /* remove plugins (so cleanup code gets run) */ + plugins_done(); + + // Remove the PID file if we wrote it + if (config->wrote_pid && *config->pid_file == '/') + unlink(config->pid_file); + + /* kill CLI children */ + signal(SIGTERM, SIG_IGN); + kill(0, SIGTERM); + return 0; +} + +static void sighup_handler(int sig) +{ + main_reload++; +} + +static void shutdown_handler(int sig) +{ + main_quit = (sig == SIGQUIT) ? QUIT_SHUTDOWN : QUIT_FAILOVER; +} + +static void sigchild_handler(int sig) +{ + while (waitpid(-1, NULL, WNOHANG) > 0) + ; +} + +static void build_chap_response(uint8_t *challenge, uint8_t id, uint16_t challenge_length, uint8_t **challenge_response) +{ + MD5_CTX ctx; + *challenge_response = NULL; + + if (!*config->l2tp_secret) + { + LOG(0, 0, 0, "LNS requested CHAP authentication, but no l2tp secret is defined\n"); + return; + } + + LOG(4, 0, 0, " Building challenge response for CHAP request\n"); + + *challenge_response = calloc(17, 1); + + MD5_Init(&ctx); + MD5_Update(&ctx, &id, 1); + MD5_Update(&ctx, config->l2tp_secret, strlen(config->l2tp_secret)); + MD5_Update(&ctx, challenge, challenge_length); + MD5_Final(*challenge_response, &ctx); + + return; +} + +static int facility_value(char *name) +{ + int i; + for (i = 0; facilitynames[i].c_name; i++) + { + if (strcmp(facilitynames[i].c_name, name) == 0) + return facilitynames[i].c_val; + } + return 0; +} + +static void update_config() +{ + int i; + char *p; + static int timeout = 0; + static int interval = 0; + + // Update logging + closelog(); + syslog_log = 0; + if (log_stream) + { + if (log_stream != stderr) + fclose(log_stream); + + log_stream = NULL; + } + + if (*config->log_filename) + { + if (strstr(config->log_filename, "syslog:") == config->log_filename) + { + char *p = config->log_filename + 7; + if (*p) + { + openlog("l2tpns", LOG_PID, facility_value(p)); + syslog_log = 1; + } + } + else if (strchr(config->log_filename, '/') == config->log_filename) + { + if ((log_stream = fopen((char *)(config->log_filename), "a"))) + { + fseek(log_stream, 0, SEEK_END); + setbuf(log_stream, NULL); + } + else + { + log_stream = stderr; + setbuf(log_stream, NULL); + } + } + } + else + { + log_stream = stderr; + setbuf(log_stream, NULL); + } + +#define L2TP_HDRS (20+8+6+4) // L2TP data encaptulation: ip + udp + l2tp (data) + ppp (inc hdlc) +#define TCP_HDRS (20+20) // TCP encapsulation: ip + tcp + + if (config->l2tp_mtu <= 0) config->l2tp_mtu = 1500; // ethernet default + else if (config->l2tp_mtu < MINMTU) config->l2tp_mtu = MINMTU; + else if (config->l2tp_mtu > MAXMTU) config->l2tp_mtu = MAXMTU; + + // reset MRU/MSS globals + MRU = config->l2tp_mtu - L2TP_HDRS; + if (MRU > PPPoE_MRU) + MRU = PPPoE_MRU; + + MSS = MRU - TCP_HDRS; + + // Update radius + config->numradiusservers = 0; + for (i = 0; i < MAXRADSERVER; i++) + if (config->radiusserver[i]) + { + config->numradiusservers++; + // Set radius port: if not set, take the port from the + // first radius server. For the first radius server, + // take the #defined default value from l2tpns.h + + // test twice, In case someone works with + // a secondary radius server without defining + // a primary one, this will work even then. + if (i > 0 && !config->radiusport[i]) + config->radiusport[i] = config->radiusport[i-1]; + if (!config->radiusport[i]) + config->radiusport[i] = RADPORT; + } + + if (!config->numradiusservers) + LOG(0, 0, 0, "No RADIUS servers defined!\n"); + + // parse radius_authtypes_s + config->radius_authtypes = config->radius_authprefer = 0; + p = config->radius_authtypes_s; + while (p && *p) + { + char *s = strpbrk(p, " \t,"); + int type = 0; + + if (s) + { + *s++ = 0; + while (*s == ' ' || *s == '\t') + s++; + + if (!*s) + s = 0; + } + + if (!strncasecmp("chap", p, strlen(p))) + type = AUTHCHAP; + else if (!strncasecmp("pap", p, strlen(p))) + type = AUTHPAP; + else + LOG(0, 0, 0, "Invalid RADIUS authentication type \"%s\"\n", p); + + config->radius_authtypes |= type; + if (!config->radius_authprefer) + config->radius_authprefer = type; + + p = s; + } + + if (!config->radius_authtypes) + { + LOG(0, 0, 0, "Defaulting to PAP authentication\n"); + config->radius_authtypes = config->radius_authprefer = AUTHPAP; + } + + // normalise radius_authtypes_s + if (config->radius_authprefer == AUTHPAP) + { + strcpy(config->radius_authtypes_s, "pap"); + if (config->radius_authtypes & AUTHCHAP) + strcat(config->radius_authtypes_s, ", chap"); + } + else + { + strcpy(config->radius_authtypes_s, "chap"); + if (config->radius_authtypes & AUTHPAP) + strcat(config->radius_authtypes_s, ", pap"); + } + + if (!config->radius_dae_port) + config->radius_dae_port = DAEPORT; + + // re-initialise the random number source + initrandom(config->random_device); + + // Update plugins + for (i = 0; i < MAXPLUGINS; i++) + { + if (strcmp(config->plugins[i], config->old_plugins[i]) == 0) + continue; + + if (*config->plugins[i]) + { + // Plugin added + add_plugin(config->plugins[i]); + } + else if (*config->old_plugins[i]) + { + // Plugin removed + remove_plugin(config->old_plugins[i]); + } + } + + memcpy(config->old_plugins, config->plugins, sizeof(config->plugins)); + if (!config->multi_read_count) config->multi_read_count = 10; + if (!config->cluster_address) config->cluster_address = inet_addr(DEFAULT_MCAST_ADDR); + if (!*config->cluster_interface) + strncpy(config->cluster_interface, DEFAULT_MCAST_INTERFACE, sizeof(config->cluster_interface) - 1); + + if (!config->cluster_hb_interval) + config->cluster_hb_interval = PING_INTERVAL; // Heartbeat every 0.5 seconds. + + if (!config->cluster_hb_timeout) + config->cluster_hb_timeout = HB_TIMEOUT; // 10 missed heartbeat triggers an election. + + if (interval != config->cluster_hb_interval || timeout != config->cluster_hb_timeout) + { + // Paranoia: cluster_check_master() treats 2 x interval + 1 sec as + // late, ensure we're sufficiently larger than that + int t = 4 * config->cluster_hb_interval + 11; + + if (config->cluster_hb_timeout < t) + { + LOG(0, 0, 0, "Heartbeat timeout %d too low, adjusting to %d\n", config->cluster_hb_timeout, t); + config->cluster_hb_timeout = t; + } + + // Push timing changes to the slaves immediately if we're the master + if (config->cluster_iam_master) + cluster_heartbeat(); + + interval = config->cluster_hb_interval; + timeout = config->cluster_hb_timeout; + } + + // Write PID file + if (*config->pid_file == '/' && !config->wrote_pid) + { + FILE *f; + if ((f = fopen(config->pid_file, "w"))) + { + fprintf(f, "%d\n", getpid()); + fclose(f); + config->wrote_pid = 1; + } + else + { + LOG(0, 0, 0, "Can't write to PID file %s: %s\n", config->pid_file, strerror(errno)); + } + } +} + +static void read_config_file() +{ + FILE *f; + + if (!config->config_file) return; + if (!(f = fopen(config->config_file, "r"))) + { + fprintf(stderr, "Can't open config file %s: %s\n", config->config_file, strerror(errno)); + return; + } + + LOG(3, 0, 0, "Reading config file %s\n", config->config_file); + cli_do_file(f); + LOG(3, 0, 0, "Done reading config file\n"); + fclose(f); +} + +int sessionsetup(sessionidt s, tunnelidt t) +{ + // A session now exists, set it up + in_addr_t ip; + char *user; + sessionidt i; + int r; + + CSTAT(sessionsetup); + + LOG(3, s, t, "Doing session setup for session\n"); + + if (!session[s].ip) + { + assign_ip_address(s); + if (!session[s].ip) + { + LOG(0, s, t, " No IP allocated. The IP address pool is FULL!\n"); + sessionshutdown(s, "No IP addresses available.", CDN_TRY_ANOTHER, TERM_SERVICE_UNAVAILABLE); + return 0; + } + LOG(3, s, t, " No IP allocated. Assigned %s from pool\n", + fmtaddr(htonl(session[s].ip), 0)); + } + + + // Make sure this is right + session[s].tunnel = t; + + // zap old sessions with same IP and/or username + // Don't kill gardened sessions - doing so leads to a DoS + // from someone who doesn't need to know the password + { + ip = session[s].ip; + user = session[s].user; + for (i = 1; i <= config->cluster_highest_sessionid; i++) + { + if (i == s) continue; + if (!session[s].opened) continue; + if (ip == session[i].ip) + { + sessionkill(i, "Duplicate IP address"); + continue; + } + + if (config->allow_duplicate_users) continue; + if (session[s].walled_garden || session[i].walled_garden) continue; + if (!strcasecmp(user, session[i].user)) + sessionkill(i, "Duplicate session for users"); + } + } + + { + int routed = 0; + + // Add the route for this session. + for (r = 0; r < MAXROUTE && session[s].route[r].ip; r++) + { + if ((session[s].ip & session[s].route[r].mask) == + (session[s].route[r].ip & session[s].route[r].mask)) + routed++; + + routeset(s, session[s].route[r].ip, session[s].route[r].mask, 0, 1); + } + + // Static IPs need to be routed if not already + // convered by a Framed-Route. Anything else is part + // of the IP address pool and is already routed, it + // just needs to be added to the IP cache. + // IPv6 route setup is done in ppp.c, when IPV6CP is acked. + if (session[s].ip_pool_index == -1) // static ip + { + if (!routed) routeset(s, session[s].ip, 0, 0, 1); + } + else + cache_ipmap(session[s].ip, s); + } + + sess_local[s].lcp_authtype = 0; // RADIUS authentication complete + lcp_open(s, t); // transition to Network phase and send initial IPCP + + // Run the plugin's against this new session. + { + struct param_new_session data = { &tunnel[t], &session[s] }; + run_plugins(PLUGIN_NEW_SESSION, &data); + } + + // Allocate TBFs if throttled + if (session[s].throttle_in || session[s].throttle_out) + throttle_session(s, session[s].throttle_in, session[s].throttle_out); + + session[s].last_packet = time_now; + + LOG(2, s, t, "Login by %s at %s from %s (%s)\n", session[s].user, + fmtaddr(htonl(session[s].ip), 0), + fmtaddr(htonl(tunnel[t].ip), 1), tunnel[t].hostname); + + cluster_send_session(s); // Mark it as dirty, and needing to the flooded to the cluster. + + return 1; // RADIUS OK and IP allocated, done... +} + +// +// This session just got dropped on us by the master or something. +// Make sure our tables up up to date... +// +int load_session(sessionidt s, sessiont *new) +{ + int i; + int newip = 0; + + // Sanity checks. + if (new->ip_pool_index >= MAXIPPOOL || + new->tunnel >= MAXTUNNEL) + { + LOG(0, s, 0, "Strange session update received!\n"); + // FIXME! What to do here? + return 0; + } + + // + // Ok. All sanity checks passed. Now we're committed to + // loading the new session. + // + + session[s].tunnel = new->tunnel; // For logging in cache_ipmap + + // See if routes/ip cache need updating + if (new->ip != session[s].ip) + newip++; + + for (i = 0; !newip && i < MAXROUTE && (session[s].route[i].ip || new->route[i].ip); i++) + if (new->route[i].ip != session[s].route[i].ip || + new->route[i].mask != session[s].route[i].mask) + newip++; + + // needs update + if (newip) + { + int routed = 0; + + // remove old routes... + for (i = 0; i < MAXROUTE && session[s].route[i].ip; i++) + { + if ((session[s].ip & session[s].route[i].mask) == + (session[s].route[i].ip & session[s].route[i].mask)) + routed++; + + routeset(s, session[s].route[i].ip, session[s].route[i].mask, 0, 0); + } + + // ...ip + if (session[s].ip) + { + if (session[s].ip_pool_index == -1) // static IP + { + if (!routed) routeset(s, session[s].ip, 0, 0, 0); + } + else // It's part of the IP pool, remove it manually. + uncache_ipmap(session[s].ip); + } + + routed = 0; + + // add new routes... + for (i = 0; i < MAXROUTE && new->route[i].ip; i++) + { + if ((new->ip & new->route[i].mask) == + (new->route[i].ip & new->route[i].mask)) + routed++; + + routeset(s, new->route[i].ip, new->route[i].mask, 0, 1); + } + + // ...ip + if (new->ip) + { + // If there's a new one, add it. + if (new->ip_pool_index == -1) + { + if (!routed) routeset(s, new->ip, 0, 0, 1); + } + else + cache_ipmap(new->ip, s); + } + } + + // check v6 routing + if (new->ipv6prefixlen && new->ppp.ipv6cp == Opened && session[s].ppp.ipv6cp != Opened) + route6set(s, new->ipv6route, new->ipv6prefixlen, 1); + + // check filters + if (new->filter_in && (new->filter_in > MAXFILTER || !ip_filters[new->filter_in - 1].name[0])) + { + LOG(2, s, session[s].tunnel, "Dropping invalid input filter %d\n", (int) new->filter_in); + new->filter_in = 0; + } + + if (new->filter_out && (new->filter_out > MAXFILTER || !ip_filters[new->filter_out - 1].name[0])) + { + LOG(2, s, session[s].tunnel, "Dropping invalid output filter %d\n", (int) new->filter_out); + new->filter_out = 0; + } + + if (new->filter_in != session[s].filter_in) + { + if (session[s].filter_in) ip_filters[session[s].filter_in - 1].used--; + if (new->filter_in) ip_filters[new->filter_in - 1].used++; + } + + if (new->filter_out != session[s].filter_out) + { + if (session[s].filter_out) ip_filters[session[s].filter_out - 1].used--; + if (new->filter_out) ip_filters[new->filter_out - 1].used++; + } + + if (new->tunnel && s > config->cluster_highest_sessionid) // Maintain this in the slave. It's used + // for walking the sessions to forward byte counts to the master. + config->cluster_highest_sessionid = s; + + memcpy(&session[s], new, sizeof(session[s])); // Copy over.. + + // Do fixups into address pool. + if (new->ip_pool_index != -1) + fix_address_pool(s); + + return 1; +} + +static void initplugins() +{ + int i; + + loaded_plugins = ll_init(); + // Initialize the plugins to nothing + for (i = 0; i < MAX_PLUGIN_TYPES; i++) + plugins[i] = ll_init(); +} + +static void *open_plugin(char *plugin_name, int load) +{ + char path[256] = ""; + + snprintf(path, 256, PLUGINDIR "/%s.so", plugin_name); + LOG(2, 0, 0, "%soading plugin from %s\n", load ? "L" : "Un-l", path); + return dlopen(path, RTLD_NOW); +} + +// plugin callback to get a config value +static void *getconfig(char *key, enum config_typet type) +{ + int i; + + for (i = 0; config_values[i].key; i++) + { + if (!strcmp(config_values[i].key, key)) + { + if (config_values[i].type == type) + return ((void *) config) + config_values[i].offset; + + LOG(1, 0, 0, "plugin requested config item \"%s\" expecting type %d, have type %d\n", + key, type, config_values[i].type); + + return 0; + } + } + + LOG(1, 0, 0, "plugin requested unknown config item \"%s\"\n", key); + return 0; +} + +static int add_plugin(char *plugin_name) +{ + static struct pluginfuncs funcs = { + _log, + _log_hex, + fmtaddr, + sessionbyuser, + sessiontbysessionidt, + sessionidtbysessiont, + radiusnew, + radiussend, + getconfig, + sessionshutdown, + sessionkill, + throttle_session, + cluster_send_session, + }; + + void *p = open_plugin(plugin_name, 1); + int (*initfunc)(struct pluginfuncs *); + int i; + + if (!p) + { + LOG(1, 0, 0, " Plugin load failed: %s\n", dlerror()); + return -1; + } + + if (ll_contains(loaded_plugins, p)) + { + dlclose(p); + return 0; // already loaded + } + + { + int *v = dlsym(p, "plugin_api_version"); + if (!v || *v != PLUGIN_API_VERSION) + { + LOG(1, 0, 0, " Plugin load failed: API version mismatch: %s\n", dlerror()); + dlclose(p); + return -1; + } + } + + if ((initfunc = dlsym(p, "plugin_init"))) + { + if (!initfunc(&funcs)) + { + LOG(1, 0, 0, " Plugin load failed: plugin_init() returned FALSE: %s\n", dlerror()); + dlclose(p); + return -1; + } + } + + ll_push(loaded_plugins, p); + + for (i = 0; i < max_plugin_functions; i++) + { + void *x; + if (plugin_functions[i] && (x = dlsym(p, plugin_functions[i]))) + { + LOG(3, 0, 0, " Supports function \"%s\"\n", plugin_functions[i]); + ll_push(plugins[i], x); + } + } + + LOG(2, 0, 0, " Loaded plugin %s\n", plugin_name); + return 1; +} + +static void run_plugin_done(void *plugin) +{ + int (*donefunc)(void) = dlsym(plugin, "plugin_done"); + + if (donefunc) + donefunc(); +} + +static int remove_plugin(char *plugin_name) +{ + void *p = open_plugin(plugin_name, 0); + int loaded = 0; + + if (!p) + return -1; + + if (ll_contains(loaded_plugins, p)) + { + int i; + for (i = 0; i < max_plugin_functions; i++) + { + void *x; + if (plugin_functions[i] && (x = dlsym(p, plugin_functions[i]))) + ll_delete(plugins[i], x); + } + + ll_delete(loaded_plugins, p); + run_plugin_done(p); + loaded = 1; + } + + dlclose(p); + LOG(2, 0, 0, "Removed plugin %s\n", plugin_name); + return loaded; +} + +int run_plugins(int plugin_type, void *data) +{ + int (*func)(void *data); + + if (!plugins[plugin_type] || plugin_type > max_plugin_functions) + return PLUGIN_RET_ERROR; + + ll_reset(plugins[plugin_type]); + while ((func = ll_next(plugins[plugin_type]))) + { + int r = func(data); + + if (r != PLUGIN_RET_OK) + return r; // stop here + } + + return PLUGIN_RET_OK; +} + +static void plugins_done() +{ + void *p; + + ll_reset(loaded_plugins); + while ((p = ll_next(loaded_plugins))) + run_plugin_done(p); +} + +static void processcontrol(uint8_t *buf, int len, struct sockaddr_in *addr, int alen, struct in_addr *local) +{ + struct nsctl request; + struct nsctl response; + int type = unpack_control(&request, buf, len); + int r; + void *p; + + if (log_stream && config->debug >= 4) + { + if (type < 0) + { + LOG(4, 0, 0, "Bogus control message from %s (%d)\n", + fmtaddr(addr->sin_addr.s_addr, 0), type); + } + else + { + LOG(4, 0, 0, "Received [%s] ", fmtaddr(addr->sin_addr.s_addr, 0)); + dump_control(&request, log_stream); + } + } + + switch (type) + { + case NSCTL_REQ_LOAD: + if (request.argc != 1) + { + response.type = NSCTL_RES_ERR; + response.argc = 1; + response.argv[0] = "name of plugin required"; + } + else if ((r = add_plugin(request.argv[0])) < 1) + { + response.type = NSCTL_RES_ERR; + response.argc = 1; + response.argv[0] = !r + ? "plugin already loaded" + : "error loading plugin"; + } + else + { + response.type = NSCTL_RES_OK; + response.argc = 0; + } + + break; + + case NSCTL_REQ_UNLOAD: + if (request.argc != 1) + { + response.type = NSCTL_RES_ERR; + response.argc = 1; + response.argv[0] = "name of plugin required"; + } + else if ((r = remove_plugin(request.argv[0])) < 1) + { + response.type = NSCTL_RES_ERR; + response.argc = 1; + response.argv[0] = !r + ? "plugin not loaded" + : "plugin not found"; + } + else + { + response.type = NSCTL_RES_OK; + response.argc = 0; + } + + break; + + case NSCTL_REQ_HELP: + response.type = NSCTL_RES_OK; + response.argc = 0; + + ll_reset(loaded_plugins); + while ((p = ll_next(loaded_plugins))) + { + char **help = dlsym(p, "plugin_control_help"); + while (response.argc < 0xff && help && *help) + response.argv[response.argc++] = *help++; + } + + break; + + case NSCTL_REQ_CONTROL: + { + struct param_control param = { + config->cluster_iam_master, + request.argc, + request.argv, + 0, + NULL, + }; + + int r = run_plugins(PLUGIN_CONTROL, ¶m); + + if (r == PLUGIN_RET_ERROR) + { + response.type = NSCTL_RES_ERR; + response.argc = 1; + response.argv[0] = param.additional + ? param.additional + : "error returned by plugin"; + } + else if (r == PLUGIN_RET_NOTMASTER) + { + static char msg[] = "must be run on master: 000.000.000.000"; + + response.type = NSCTL_RES_ERR; + response.argc = 1; + if (config->cluster_master_address) + { + strcpy(msg + 23, fmtaddr(config->cluster_master_address, 0)); + response.argv[0] = msg; + } + else + { + response.argv[0] = "must be run on master: none elected"; + } + } + else if (!(param.response & NSCTL_RESPONSE)) + { + response.type = NSCTL_RES_ERR; + response.argc = 1; + response.argv[0] = param.response + ? "unrecognised response value from plugin" + : "unhandled action"; + } + else + { + response.type = param.response; + response.argc = 0; + if (param.additional) + { + response.argc = 1; + response.argv[0] = param.additional; + } + } + } + + break; + + default: + response.type = NSCTL_RES_ERR; + response.argc = 1; + response.argv[0] = "error unpacking control packet"; + } + + buf = calloc(NSCTL_MAX_PKT_SZ, 1); + if (!buf) + { + LOG(2, 0, 0, "Failed to allocate nsctl response\n"); + return; + } + + r = pack_control(buf, NSCTL_MAX_PKT_SZ, response.type, response.argc, response.argv); + if (r > 0) + { + sendtofrom(controlfd, buf, r, 0, (const struct sockaddr *) addr, alen, local); + if (log_stream && config->debug >= 4) + { + LOG(4, 0, 0, "Sent [%s] ", fmtaddr(addr->sin_addr.s_addr, 0)); + dump_control(&response, log_stream); + } + } + else + LOG(2, 0, 0, "Failed to pack nsctl response for %s (%d)\n", + fmtaddr(addr->sin_addr.s_addr, 0), r); + + free(buf); +} + +static tunnelidt new_tunnel() +{ + tunnelidt i; + for (i = 1; i < MAXTUNNEL; i++) + { + if (tunnel[i].state == TUNNELFREE) + { + LOG(4, 0, i, "Assigning tunnel ID %d\n", i); + if (i > config->cluster_highest_tunnelid) + config->cluster_highest_tunnelid = i; + return i; + } + } + LOG(0, 0, 0, "Can't find a free tunnel! There shouldn't be this many in use!\n"); + return 0; +} + +// +// We're becoming the master. Do any required setup.. +// +// This is principally telling all the plugins that we're +// now a master, and telling them about all the sessions +// that are active too.. +// +void become_master(void) +{ + int s, i; + static struct event_data d[RADIUS_FDS]; + struct epoll_event e; + + run_plugins(PLUGIN_BECOME_MASTER, NULL); + + // running a bunch of iptables commands is slow and can cause + // the master to drop tunnels on takeover--kludge around the + // problem by forking for the moment (note: race) + if (!fork_and_close()) + { + for (s = 1; s <= config->cluster_highest_sessionid ; ++s) + { + if (!session[s].opened) // Not an in-use session. + continue; + + run_plugins(PLUGIN_NEW_SESSION_MASTER, &session[s]); + } + exit(0); + } + + // add radius fds + e.events = EPOLLIN; + for (i = 0; i < RADIUS_FDS; i++) + { + d[i].type = FD_TYPE_RADIUS; + d[i].index = i; + e.data.ptr = &d[i]; + + epoll_ctl(epollfd, EPOLL_CTL_ADD, radfds[i], &e); + } +} + +int cmd_show_hist_idle(struct cli_def *cli, char *command, char **argv, int argc) +{ + int s, i; + int count = 0; + int buckets[64]; + + if (CLI_HELP_REQUESTED) + return CLI_HELP_NO_ARGS; + + time(&time_now); + for (i = 0; i < 64;++i) buckets[i] = 0; + + for (s = 1; s <= config->cluster_highest_sessionid ; ++s) + { + int idle; + if (!session[s].opened) + continue; + + idle = time_now - session[s].last_packet; + idle /= 5 ; // In multiples of 5 seconds. + if (idle < 0) + idle = 0; + if (idle > 63) + idle = 63; + + ++count; + ++buckets[idle]; + } + + for (i = 0; i < 63; ++i) + { + cli_print(cli, "%3d seconds : %7.2f%% (%6d)", i * 5, (double) buckets[i] * 100.0 / count , buckets[i]); + } + cli_print(cli, "lots of secs : %7.2f%% (%6d)", (double) buckets[63] * 100.0 / count , buckets[i]); + cli_print(cli, "%d total sessions open.", count); + return CLI_OK; +} + +int cmd_show_hist_open(struct cli_def *cli, char *command, char **argv, int argc) +{ + int s, i; + int count = 0; + int buckets[64]; + + if (CLI_HELP_REQUESTED) + return CLI_HELP_NO_ARGS; + + time(&time_now); + for (i = 0; i < 64;++i) buckets[i] = 0; + + for (s = 1; s <= config->cluster_highest_sessionid ; ++s) + { + int open = 0, d; + if (!session[s].opened) + continue; + + d = time_now - session[s].opened; + if (d < 0) + d = 0; + while (d > 1 && open < 32) + { + ++open; + d >>= 1; // half. + } + ++count; + ++buckets[open]; + } + + s = 1; + for (i = 0; i < 30; ++i) + { + cli_print(cli, " < %8d seconds : %7.2f%% (%6d)", s, (double) buckets[i] * 100.0 / count , buckets[i]); + s <<= 1; + } + cli_print(cli, "%d total sessions open.", count); + return CLI_OK; +} + +/* Unhide an avp. + * + * This unencodes the AVP using the L2TP secret and the previously + * stored random vector. It overwrites the hidden data with the + * unhidden AVP subformat. + */ +static void unhide_value(uint8_t *value, size_t len, uint16_t type, uint8_t *vector, size_t vec_len) +{ + MD5_CTX ctx; + uint8_t digest[16]; + uint8_t *last; + size_t d = 0; + uint16_t m = htons(type); + + // Compute initial pad + MD5_Init(&ctx); + MD5_Update(&ctx, (unsigned char *) &m, 2); + MD5_Update(&ctx, config->l2tp_secret, strlen(config->l2tp_secret)); + MD5_Update(&ctx, vector, vec_len); + MD5_Final(digest, &ctx); + + // pointer to last decoded 16 octets + last = value; + + while (len > 0) + { + // calculate a new pad based on the last decoded block + if (d >= sizeof(digest)) + { + MD5_Init(&ctx); + MD5_Update(&ctx, config->l2tp_secret, strlen(config->l2tp_secret)); + MD5_Update(&ctx, last, sizeof(digest)); + MD5_Final(digest, &ctx); + + d = 0; + last = value; + } + + *value++ ^= digest[d++]; + len--; + } +} + +int find_filter(char const *name, size_t len) +{ + int free = -1; + int i; + + for (i = 0; i < MAXFILTER; i++) + { + if (!*ip_filters[i].name) + { + if (free < 0) + free = i; + + continue; + } + + if (strlen(ip_filters[i].name) != len) + continue; + + if (!strncmp(ip_filters[i].name, name, len)) + return i; + } + + return free; +} + +static int ip_filter_port(ip_filter_portt *p, uint16_t port) +{ + switch (p->op) + { + case FILTER_PORT_OP_EQ: return port == p->port; + case FILTER_PORT_OP_NEQ: return port != p->port; + case FILTER_PORT_OP_GT: return port > p->port; + case FILTER_PORT_OP_LT: return port < p->port; + case FILTER_PORT_OP_RANGE: return port >= p->port && port <= p->port2; + } + + return 0; +} + +static int ip_filter_flag(uint8_t op, uint8_t sflags, uint8_t cflags, uint8_t flags) +{ + switch (op) + { + case FILTER_FLAG_OP_ANY: + return (flags & sflags) || (~flags & cflags); + + case FILTER_FLAG_OP_ALL: + return (flags & sflags) == sflags && (~flags & cflags) == cflags; + + case FILTER_FLAG_OP_EST: + return (flags & (TCP_FLAG_ACK|TCP_FLAG_RST)) && (~flags & TCP_FLAG_SYN); + } + + return 0; +} + +int ip_filter(uint8_t *buf, int len, uint8_t filter) +{ + uint16_t frag_offset; + uint8_t proto; + in_addr_t src_ip; + in_addr_t dst_ip; + uint16_t src_port = 0; + uint16_t dst_port = 0; + uint8_t flags = 0; + ip_filter_rulet *rule; + + if (len < 20) // up to end of destination address + return 0; + + if ((*buf >> 4) != 4) // IPv4 + return 0; + + frag_offset = ntohs(*(uint16_t *) (buf + 6)) & 0x1fff; + proto = buf[9]; + src_ip = *(in_addr_t *) (buf + 12); + dst_ip = *(in_addr_t *) (buf + 16); + + if (frag_offset == 0 && (proto == IPPROTO_TCP || proto == IPPROTO_UDP)) + { + int l = (buf[0] & 0xf) * 4; // length of IP header + if (len < l + 4) // ports + return 0; + + src_port = ntohs(*(uint16_t *) (buf + l)); + dst_port = ntohs(*(uint16_t *) (buf + l + 2)); + if (proto == IPPROTO_TCP) + { + if (len < l + 14) // flags + return 0; + + flags = buf[l + 13] & 0x3f; + } + } + + for (rule = ip_filters[filter].rules; rule->action; rule++) + { + if (rule->proto != IPPROTO_IP && proto != rule->proto) + continue; + + if (rule->src_wild != INADDR_BROADCAST && + (src_ip & ~rule->src_wild) != (rule->src_ip & ~rule->src_wild)) + continue; + + if (rule->dst_wild != INADDR_BROADCAST && + (dst_ip & ~rule->dst_wild) != (rule->dst_ip & ~rule->dst_wild)) + continue; + + if (frag_offset) + { + // layer 4 deny rules are skipped + if (rule->action == FILTER_ACTION_DENY && + (rule->src_ports.op || rule->dst_ports.op || rule->tcp_flag_op)) + continue; + } + else + { + if (rule->frag) + continue; + + if (proto == IPPROTO_TCP || proto == IPPROTO_UDP) + { + if (rule->src_ports.op && !ip_filter_port(&rule->src_ports, src_port)) + continue; + + if (rule->dst_ports.op && !ip_filter_port(&rule->dst_ports, dst_port)) + continue; + + if (proto == IPPROTO_TCP && rule->tcp_flag_op && + !ip_filter_flag(rule->tcp_flag_op, rule->tcp_sflags, rule->tcp_cflags, flags)) + continue; + } + } + + // matched + rule->counter++; + return rule->action == FILTER_ACTION_PERMIT; + } + + // default deny + return 0; +} diff --git a/l2tpns.h b/l2tpns.h new file mode 100644 index 0000000..6c38bbf --- /dev/null +++ b/l2tpns.h @@ -0,0 +1,859 @@ +// L2TPNS Global Stuff +// $Id: l2tpns.h,v 1.113.2.3 2006/12/02 14:09:14 bodea Exp $ + +#ifndef __L2TPNS_H__ +#define __L2TPNS_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VERSION "2.1.21" + +// Limits +#define MAXTUNNEL 500 // could be up to 65535 +#define MAXSESSION 60000 // could be up to 65535 +#define MAXTBFS 6000 // Maximum token bucket filters. Might need up to 2 * session. + +#define RADIUS_SHIFT 6 +#define RADIUS_FDS (1 << RADIUS_SHIFT) +#define RADIUS_MASK ((1 << RADIUS_SHIFT) - 1) +#define MAXRADIUS (1 << (8 + RADIUS_SHIFT)) + +#define T_UNDEF (0xffff) // A tunnel ID that won't ever be used. Mark session as undefined. +#define T_FREE (0) // A tunnel ID that won't ever be used. Mark session as free. + +#define MAXCONTROL 1000 // max length control message we ever send... +#define MINMTU 576 // minimum recommended MTU (rfc1063) +#define MAXMTU 2600 // arbitrary maximum MTU +#define PPPoE_MRU 1492 // maximum PPPoE MRU (rfc2516: 1500 less PPPoE header (6) and PPP protocol ID (2)) +#define MAXETHER (MAXMTU+18) // max packet we try sending to tun +#define MAXTEL 96 // telephone number +#define MAXUSER 128 // username +#define MAXPASS 128 // password +#define MAXPLUGINS 20 // maximum number of plugins to load +#define MAXRADSERVER 10 // max radius servers +#define MAXROUTE 10 // max static routes per session +#define MAXIPPOOL 131072 // max number of ip addresses in pool +#define RINGBUFFER_SIZE 10000 // Number of ringbuffer entries to allocate +#define MAX_LOG_LENGTH 512 // Maximum size of log message +#define ECHO_TIMEOUT 60 // Time between last packet sent and LCP ECHO generation +#define IDLE_TIMEOUT 240 // Time between last packet sent and LCP ECHO generation +#define BUSY_WAIT_TIME 3000 // 5 minutes in 1/10th seconds to wait for radius to cleanup on shutdown + +// Constants +#ifndef ETCDIR +#define ETCDIR "/etc/l2tpns" +#endif + +#ifndef LIBDIR +#define LIBDIR "/usr/lib/l2tpns" +#endif + +#ifndef PLUGINDIR +#define PLUGINDIR LIBDIR // Plugins +#endif + +#ifndef PLUGINCONF +#define PLUGINCONF ETCDIR // Plugin config dir +#endif + +#ifndef FLASHDIR +#define FLASHDIR ETCDIR +#endif + +#define TUNDEVICE "/dev/net/tun" +#define RANDOMDEVICE "/dev/urandom" // default, not as secure as /dev/random but non-blocking +#define CONFIGFILE FLASHDIR "/startup-config" // Configuration file +#define CLIUSERS FLASHDIR "/users" // CLI Users file +#define IPPOOLFILE FLASHDIR "/ip_pool" // Address pool configuration +#define ACCT_TIME 3000 // 5 minute accounting interval +#define ACCT_SHUT_TIME 600 // 1 minute for counters of shutdown sessions +#define L2TPPORT 1701 // L2TP port +#define RADPORT 1645 // old radius port... +#define DAEPORT 3799 // DAE port +#define PKTARP 0x0806 // ARP packet type +#define PKTIP 0x0800 // IPv4 packet type +#define PKTIPV6 0x86DD // IPv6 packet type +#define PPPPAP 0xC023 +#define PPPCHAP 0xC223 +#define PPPLCP 0xC021 +#define PPPIPCP 0x8021 +#define PPPIPV6CP 0x8057 +#define PPPCCP 0x80FD +#define PPPIP 0x0021 +#define PPPIPV6 0x0057 +#define PPPMP 0x003D +#define MIN_IP_SIZE 0x19 + +enum { + ConfigReq = 1, + ConfigAck, + ConfigNak, + ConfigRej, + TerminateReq, + TerminateAck, + CodeRej, + ProtocolRej, + EchoReq, + EchoReply, + DiscardRequest, + IdentRequest +}; + +enum { + AccessRequest = 1, + AccessAccept, + AccessReject, + AccountingRequest, + AccountingResponse, + AccessChallenge = 11, + DisconnectRequest = 40, + DisconnectACK, + DisconnectNAK, + CoARequest, + CoAACK, + CoANAK +}; + +// PPP phases +enum { + Dead, + Establish, + Authenticate, + Network, + Terminate +}; + +// PPP states +enum { + Initial, + Starting, + Closed, + Stopped, + Closing, + Stopping, + RequestSent, + AckReceived, + AckSent, + Opened +}; + +// reset state machine counters +#define initialise_restart_count(_s, _fsm) \ + sess_local[_s]._fsm.conf_sent = \ + sess_local[_s]._fsm.nak_sent = 0 + +// no more attempts +#define zero_restart_count(_s, _fsm) ({ \ + sess_local[_s]._fsm.conf_sent = \ + config->ppp_max_configure; \ + sess_local[_s]._fsm.restart = \ + time_now + config->ppp_restart_time; \ +}) + +// increment ConfReq counter and reset timer +#define restart_timer(_s, _fsm) ({ \ + sess_local[_s]._fsm.conf_sent++; \ + sess_local[_s]._fsm.restart = \ + time_now + config->ppp_restart_time; \ +}) + +// stop timer on change to state where timer does not run +#define change_state(_s, _fsm, _new) ({ \ + if (_new != session[_s].ppp._fsm) \ + { \ + switch (_new) \ + { \ + case Initial: \ + case Starting: \ + case Closed: \ + case Stopped: \ + case Opened: \ + sess_local[_s]._fsm.restart = 0; \ + initialise_restart_count(_s, _fsm); \ + } \ + session[_s].ppp._fsm = _new; \ + cluster_send_session(_s); \ + } \ +}) + +// Types +typedef uint16_t sessionidt; +typedef uint16_t tunnelidt; +typedef uint32_t clockt; +typedef uint8_t hasht[16]; + +// CLI actions +struct cli_session_actions { + char action; + in_addr_t snoop_ip; + uint16_t snoop_port; + int throttle_in; + int throttle_out; + int filter_in; + int filter_out; +}; + +#define CLI_SESS_KILL 0x01 +#define CLI_SESS_SNOOP 0x02 +#define CLI_SESS_NOSNOOP 0x04 +#define CLI_SESS_THROTTLE 0x08 +#define CLI_SESS_NOTHROTTLE 0x10 +#define CLI_SESS_FILTER 0x20 +#define CLI_SESS_NOFILTER 0x40 + +struct cli_tunnel_actions { + char action; +}; + +#define CLI_TUN_KILL 0x01 + +// structures +typedef struct // route +{ + in_addr_t ip; + in_addr_t mask; +} +routet; + +typedef struct controls // control message +{ + struct controls *next; // next in queue + uint16_t length; // length + uint8_t buf[MAXCONTROL]; +} +controlt; + +typedef struct +{ + sessionidt next; // next session in linked list + sessionidt far; // far end session ID + tunnelidt tunnel; // near end tunnel ID + uint8_t flags; // session flags: see SESSION_* + struct { + uint8_t phase; // PPP phase + uint8_t lcp:4; // LCP state + uint8_t ipcp:4; // IPCP state + uint8_t ipv6cp:4; // IPV6CP state + uint8_t ccp:4; // CCP state + } ppp; + char reserved_1[2]; // unused: padding + in_addr_t ip; // IP of session set by RADIUS response (host byte order). + int ip_pool_index; // index to IP pool + uint32_t unique_id; // unique session id + char reserved_2[4]; // unused: was ns/nr + uint32_t magic; // ppp magic number + uint32_t pin, pout; // packet counts + uint32_t cin, cout; // byte counts + uint32_t cin_wrap, cout_wrap; // byte counter wrap count (RADIUS accounting giagawords) + uint32_t cin_delta, cout_delta; // byte count changes (for dump_session()) + uint16_t throttle_in; // upstream throttle rate (kbps) + uint16_t throttle_out; // downstream throttle rate + uint8_t filter_in; // input filter index (to ip_filters[N-1]; 0 if none) + uint8_t filter_out; // output filter index + uint16_t mru; // maximum receive unit + clockt opened; // when started + clockt die; // being closed, when to finally free + time_t last_packet; // Last packet from the user (used for idle timeouts) + in_addr_t dns1, dns2; // DNS servers + routet route[MAXROUTE]; // static routes + uint16_t tbf_in; // filter bucket for throttling in from the user. + uint16_t tbf_out; // filter bucket for throttling out to the user. + int random_vector_length; + uint8_t random_vector[MAXTEL]; + char user[MAXUSER]; // user (needed in seesion for radius stop messages) + char called[MAXTEL]; // called number + char calling[MAXTEL]; // calling number + uint32_t tx_connect_speed; + uint32_t rx_connect_speed; + in_addr_t snoop_ip; // Interception destination IP + uint16_t snoop_port; // Interception destination port + uint8_t walled_garden; // is this session gardened? + uint8_t ipv6prefixlen; // IPv6 route prefix length + struct in6_addr ipv6route; // Static IPv6 route + char reserved_3[11]; // Space to expand structure without changing HB_VERSION +} +sessiont; + +#define AUTHPAP 1 // allow PAP +#define AUTHCHAP 2 // allow CHAP + +typedef struct +{ + // packet counters + uint32_t pin; + uint32_t pout; + + // byte counters + uint32_t cin; + uint32_t cout; + + // PPP restart timer/counters + struct { + time_t restart; + int conf_sent; + int nak_sent; + } lcp, ipcp, ipv6cp, ccp; + + // identifier for Protocol-Reject, Code-Reject + uint8_t lcp_ident; + + // authentication to use + int lcp_authtype; + + // our MRU + uint16_t ppp_mru; + + // DoS prevention + clockt last_packet_out; + uint32_t packets_out; + uint32_t packets_dropped; + + // RADIUS session in use + uint16_t radius; + + // interim RADIUS + time_t last_interim; + + // last LCP Echo + time_t last_echo; +} sessionlocalt; + +// session flags +#define SESSION_PFC (1 << 0) // use Protocol-Field-Compression +#define SESSION_ACFC (1 << 1) // use Address-and-Control-Field-Compression +#define SESSION_STARTED (1 << 2) // RADIUS Start record sent + +// 168 bytes per tunnel +typedef struct +{ + tunnelidt far; // far end tunnel ID + in_addr_t ip; // Ip for far end + uint16_t port; // port for far end + uint16_t window; // Rx window + uint16_t nr; // next receive + uint16_t ns; // next send + int state; // current state (tunnelstate enum) + clockt last; // when last control message sent (used for resend timeout) + clockt retry; // when to try resending pending control + clockt die; // being closed, when to finally free + clockt lastrec; // when the last control message was received + char hostname[128]; // tunnel hostname + char vendor[128]; // LAC vendor + uint8_t try; // number of retrys on a control message + uint16_t controlc; // outstaind messages in queue + controlt *controls; // oldest message + controlt *controle; // newest message +} +tunnelt; + +// 164 bytes per radius session +typedef struct // outstanding RADIUS requests +{ + sessionidt session; // which session this applies to + hasht auth; // request authenticator + clockt retry; // when to try next + char pass[129]; // password + uint8_t id; // ID for PPP response + uint8_t try; // which try we are on + uint8_t state; // state of radius requests + uint8_t chap; // set if CHAP used (is CHAP identifier) + uint8_t term_cause; // Stop record: Acct-Terminate-Cause + char const *term_msg; // terminate reason +} +radiust; + +typedef struct +{ + in_addr_t address; // Host byte order.. + char assigned; // 1 if assigned, 0 if free + sessionidt session; + clockt last; // last used + char user[129]; // user (try to have ip addresses persistent) +} +ippoolt; + +#ifdef RINGBUFFER +struct Tringbuffer +{ + struct { + char level; + sessionidt session; + tunnelidt tunnel; + char message[MAX_LOG_LENGTH]; + } buffer[RINGBUFFER_SIZE]; + int head; + int tail; +}; +#endif + +/* + * Possible tunnel states + * TUNNELFREE -> TUNNELOPEN -> TUNNELDIE -> TUNNELFREE + */ +enum +{ + TUNNELFREE, // Not in use + TUNNELOPEN, // Active tunnel + TUNNELDIE, // Currently closing + TUNNELOPENING, // Busy opening + TUNNELUNDEF, // Undefined +}; + +enum +{ + RADIUSNULL, // Not in use + RADIUSCHAP, // sending CHAP down PPP + RADIUSAUTH, // sending auth to RADIUS server + RADIUSSTART, // sending start accounting to RADIUS server + RADIUSSTOP, // sending stop accounting to RADIUS server + RADIUSINTERIM, // sending interim accounting to RADIUS server + RADIUSWAIT, // waiting timeout before available, in case delayed replies +}; + +struct Tstats +{ + time_t start_time; + time_t last_reset; + + uint32_t tun_rx_packets; + uint32_t tun_tx_packets; + uint32_t tun_rx_bytes; + uint32_t tun_tx_bytes; + uint32_t tun_rx_errors; + uint32_t tun_tx_errors; + uint32_t tun_rx_dropped; + + uint32_t tunnel_rx_packets; + uint32_t tunnel_tx_packets; + uint32_t tunnel_rx_bytes; + uint32_t tunnel_tx_bytes; + uint32_t tunnel_rx_errors; + uint32_t tunnel_tx_errors; + + uint32_t tunnel_retries; + uint32_t radius_retries; + + uint32_t arp_sent; + + uint32_t packets_snooped; + + uint32_t tunnel_created; + uint32_t session_created; + uint32_t tunnel_timeout; + uint32_t session_timeout; + uint32_t radius_timeout; + uint32_t radius_overflow; + uint32_t tunnel_overflow; + uint32_t session_overflow; + + uint32_t ip_allocated; + uint32_t ip_freed; + + uint32_t c_forwarded; + uint32_t recv_forward; + + uint32_t select_called; + uint32_t multi_read_used; + uint32_t multi_read_exceeded; + +#ifdef STATISTICS + uint32_t call_processtun; + uint32_t call_processipout; + uint32_t call_processipv6out; + uint32_t call_processudp; + uint32_t call_sessionbyip; + uint32_t call_sessionbyipv6; + uint32_t call_sessionbyuser; + uint32_t call_sendarp; + uint32_t call_sendipcp; + uint32_t call_sendipv6cp; + uint32_t call_processipv6cp; + uint32_t call_tunnelsend; + uint32_t call_sessionkill; + uint32_t call_sessionshutdown; + uint32_t call_tunnelkill; + uint32_t call_tunnelshutdown; + uint32_t call_assign_ip_address; + uint32_t call_free_ip_address; + uint32_t call_dump_acct_info; + uint32_t call_sessionsetup; + uint32_t call_processpap; + uint32_t call_processchap; + uint32_t call_processlcp; + uint32_t call_processipcp; + uint32_t call_processipin; + uint32_t call_processipv6in; + uint32_t call_processccp; + uint32_t call_sendchap; + uint32_t call_processrad; + uint32_t call_radiussend; + uint32_t call_radiusretry; + uint32_t call_random_data; +#endif +}; + +#ifdef STATISTICS + +#ifdef STAT_CALLS +#define CSTAT(x) STAT(call_ ## x) +#else +#define CSTAT(x) +#endif + +#define STAT(x) (_statistics->x++) +#define INC_STAT(x,y) (_statistics->x += (y)) +#define GET_STAT(x) (_statistics->x) +#define SET_STAT(x, y) (_statistics->x = (y)) +#else +#define CSTAT(x) +#define STAT(x) +#define INC_STAT(x,y) +#define GET_STAT(x) 0 +#define SET_STAT(x, y) +#endif + +typedef struct +{ + int debug; // debugging level + time_t start_time; // time when l2tpns was started + char bandwidth[256]; // current bandwidth + char pid_file[256]; // file to write PID to on startup + int wrote_pid; + clockt current_time; // 1/10ths of a second since the process started. + // means that we can only run a given process + // for 13 years without re-starting! + + char config_file[128]; + int reload_config; // flag to re-read config (set by cli) + int multi_read_count; // amount of packets to read per fd in processing loop + + char tundevice[10]; // tun device name + char log_filename[128]; + + char l2tp_secret[64]; // L2TP shared secret + int l2tp_mtu; // MTU of interface used for L2TP + + char random_device[256]; // random device path, defaults to RANDOMDEVICE + + int ppp_restart_time; // timeout for PPP restart + int ppp_max_configure; // max lcp configure requests to send + int ppp_max_failure; // max lcp configure naks to send + + char radiussecret[64]; + int radius_accounting; + int radius_interim; + in_addr_t radiusserver[MAXRADSERVER]; // radius servers + uint16_t radiusport[MAXRADSERVER]; // radius base ports + uint8_t numradiusservers; // radius server count + + uint16_t radius_dae_port; // local port for radius dae + + char radius_authtypes_s[32]; // list of valid authentication types (chap, pap) in order of preference + int radius_authtypes; + int radius_authprefer; + + int allow_duplicate_users; // allow multiple logins with the same username + + in_addr_t default_dns1, default_dns2; + + unsigned long rl_rate; // default throttle rate + int num_tbfs; // number of throttle buckets + + char accounting_dir[128]; + in_addr_t bind_address; + in_addr_t peer_address; + int send_garp; // Set to true to garp for vip address on startup + + int target_uid; + int dump_speed; + char plugins[64][MAXPLUGINS]; + char old_plugins[64][MAXPLUGINS]; + + int next_tbf; // Next HTB id available to use + int scheduler_fifo; // If the system has multiple CPUs, use FIFO scheduling + // policy for this process. + int lock_pages; // Lock pages into memory. + int icmp_rate; // Max number of ICMP unreachable per second to send + int max_packets; // DoS prevention: per session limit of packets/0.1s + + in_addr_t cluster_address; // Multicast address of cluster. + // Send to this address to have everyone hear. + char cluster_interface[64]; // Which interface to listen for multicast on. + int cluster_iam_master; // Are we the cluster master??? + int cluster_iam_uptodate; // Set if we've got a full set of state from the master. + in_addr_t cluster_master_address; // The network address of the cluster master. + // Zero if i am the cluster master. + int cluster_seq_number; // Sequence number of the next heartbeat we'll send out + // (or the seq number we're next expecting if we're a slave). + int cluster_undefined_sessions; // How many sessions we're yet to receive from the master. + int cluster_undefined_tunnels; // How many tunnels we're yet to receive from the master. + int cluster_highest_sessionid; + int cluster_highest_tunnelid; + clockt cluster_last_hb; // Last time we saw a heartbeat from the master. + int cluster_last_hb_ver; // Heartbeat version last seen from master + int cluster_num_changes; // Number of changes queued. + + int cluster_mcast_ttl; // TTL for multicast packets + int cluster_hb_interval; // How often to send a heartbeat. + int cluster_hb_timeout; // How many missed heartbeats trigger an election. + uint64_t cluster_table_version; // # state changes processed by cluster + + struct in6_addr ipv6_prefix; // Our IPv6 network pool. + + + int cluster_master_min_adv; // Master advertises routes while the number of up to date + // slaves is less than this value. + +#ifdef BGP +#define BGP_NUM_PEERS 2 + uint16_t as_number; + struct { + char name[64]; + uint16_t as; + int keepalive; + int hold; + } neighbour[BGP_NUM_PEERS]; +#endif +} configt; + +enum config_typet { INT, STRING, UNSIGNED_LONG, SHORT, BOOL, IPv4, IPv6 }; +typedef struct +{ + char *key; + int offset; + int size; + enum config_typet type; +} config_descriptt; + +typedef struct +{ + uint8_t op; // operation +#define FILTER_PORT_OP_NONE 0 // all ports match +#define FILTER_PORT_OP_EQ 1 +#define FILTER_PORT_OP_NEQ 2 +#define FILTER_PORT_OP_GT 3 +#define FILTER_PORT_OP_LT 4 +#define FILTER_PORT_OP_RANGE 5 + uint16_t port; // port (host byte order) + uint16_t port2; // range +} ip_filter_portt; + +typedef struct +{ + int action; // permit/deny +#define FILTER_ACTION_DENY 1 +#define FILTER_ACTION_PERMIT 2 + uint8_t proto; // protocol: IPPROTO_* (netinet/in.h) + in_addr_t src_ip; // source ip (network byte order) + in_addr_t src_wild; + ip_filter_portt src_ports; + in_addr_t dst_ip; // dest ip + in_addr_t dst_wild; + ip_filter_portt dst_ports; + uint8_t frag; // apply to non-initial fragments + uint8_t tcp_flag_op; // match type: any, all, established +#define FILTER_FLAG_OP_ANY 1 +#define FILTER_FLAG_OP_ALL 2 +#define FILTER_FLAG_OP_EST 3 + uint8_t tcp_sflags; // flags set + uint8_t tcp_cflags; // flags clear + uint32_t counter; // match count +} ip_filter_rulet; + +#define TCP_FLAG_FIN 0x01 +#define TCP_FLAG_SYN 0x02 +#define TCP_FLAG_RST 0x04 +#define TCP_FLAG_PSH 0x08 +#define TCP_FLAG_ACK 0x10 +#define TCP_FLAG_URG 0x20 + +#define MAXFILTER 32 +#define MAXFILTER_RULES 32 +typedef struct +{ + char name[32]; // ACL name + int extended; // type: 0 = standard, 1 = extended + ip_filter_rulet rules[MAXFILTER_RULES]; + int used; // session ref count +} ip_filtert; + +// CDN result/error codes +#define CDN_NONE 0, 0 +#define CDN_TRY_ANOTHER 2, 7 +#define CDN_ADMIN_DISC 3, 0 +#define CDN_UNAVAILABLE 4, 0 + +// RADIUS Acct-Terminate-Cause values +#define TERM_USER_REQUEST 1 +#define TERM_LOST_CARRIER 2 +#define TERM_LOST_SERVICE 3 +#define TERM_IDLE_TIMEOUT 4 +#define TERM_SESSION_TIMEOUT 5 +#define TERM_ADMIN_RESET 6 +#define TERM_ADMIN_REBOOT 7 +#define TERM_PORT_ERROR 8 +#define TERM_NAS_ERROR 9 +#define TERM_NAS_REQUEST 10 +#define TERM_NAS_REBOOT 11 +#define TERM_PORT_UNNEEDED 12 +#define TERM_PORT_PREEMPTED 13 +#define TERM_PORT_SUSPENDED 14 +#define TERM_SERVICE_UNAVAILABLE 15 +#define TERM_CALLBACK 16 +#define TERM_USER_ERROR 17 +#define TERM_HOST_REQUEST 18 +#define TERM_SUPPLICANT_RESTART 19 +#define TERM_REAUTHENTICATION_FAILURE 20 +#define TERM_PORT_REINIT 21 +#define TERM_PORT_DISABLED 22 + +// arp.c +void sendarp(int ifr_idx, const unsigned char* mac, in_addr_t ip); + + +// ppp.c +void processpap(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l); +void processchap(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l); +void lcp_open(sessionidt s, tunnelidt t); +void processlcp(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l); +void processipcp(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l); +void processipv6cp(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l); +void processipin(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l); +void processipv6in(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l); +void processccp(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l); +void sendchap(sessionidt s, tunnelidt t); +uint8_t *makeppp(uint8_t *b, int size, uint8_t *p, int l, sessionidt s, tunnelidt t, uint16_t mtype); +void sendlcp(sessionidt s, tunnelidt t); +void send_ipin(sessionidt s, uint8_t *buf, int len); +void sendccp(sessionidt s, tunnelidt t); +void protoreject(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l, uint16_t proto); + + +// radius.c +void initrad(void); +void radiussend(uint16_t r, uint8_t state); +void processrad(uint8_t *buf, int len, char socket_index); +void radiusretry(uint16_t r); +uint16_t radiusnew(sessionidt s); +void radiusclear(uint16_t r, sessionidt s); +void processdae(uint8_t *buf, int len, struct sockaddr_in *addr, int alen, struct in_addr *local); + + +// l2tpns.c +clockt backoff(uint8_t try); +void send_ipv6_ra(sessionidt s, tunnelidt t, struct in6_addr *ip); +void route6set(sessionidt s, struct in6_addr ip, int prefixlen, int add); +sessionidt sessionbyip(in_addr_t ip); +sessionidt sessionbyipv6(struct in6_addr ip); +sessionidt sessionbyuser(char *username); +void increment_counter(uint32_t *counter, uint32_t *wrap, uint32_t delta); +void random_data(uint8_t *buf, int len); +void sessionkill(sessionidt s, char *reason); +void sessionshutdown(sessionidt s, char const *reason, int cdn_result, int cdn_error, int term_cause); +void filter_session(sessionidt s, int filter_in, int filter_out); +void send_garp(in_addr_t ip); +void tunnelsend(uint8_t *buf, uint16_t l, tunnelidt t); +int tun_write(uint8_t *data, int size); +void adjust_tcp_mss(sessionidt s, tunnelidt t, uint8_t *buf, int len, uint8_t *tcp); +void sendipcp(sessionidt s, tunnelidt t); +void sendipv6cp(sessionidt s, tunnelidt t); +void processudp(uint8_t *buf, int len, struct sockaddr_in *addr); +void snoop_send_packet(uint8_t *packet, uint16_t size, in_addr_t destination, uint16_t port); +int find_filter(char const *name, size_t len); +int ip_filter(uint8_t *buf, int len, uint8_t filter); +int cmd_show_ipcache(struct cli_def *cli, char *command, char **argv, int argc); +int cmd_show_hist_idle(struct cli_def *cli, char *command, char **argv, int argc); +int cmd_show_hist_open(struct cli_def *cli, char *command, char **argv, int argc); + +#undef LOG +#undef LOG_HEX +#define LOG(D, s, t, f, ...) ({ if (D <= config->debug) _log(D, s, t, f, ## __VA_ARGS__); }) +#define LOG_HEX(D, t, d, s) ({ if (D <= config->debug) _log_hex(D, t, d, s); }) + +void _log(int level, sessionidt s, tunnelidt t, const char *format, ...) __attribute__((format (printf, 4, 5))); +void _log_hex(int level, const char *title, const uint8_t *data, int maxsize); + + +int sessionsetup(sessionidt s, tunnelidt t); +int run_plugins(int plugin_type, void *data); +void rebuild_address_pool(void); +void throttle_session(sessionidt s, int rate_in, int rate_out); +int load_session(sessionidt, sessiont *); +void become_master(void); // We're the master; kick off any required master initializations. + + +// cli.c +void init_cli(char *hostname); +void cli_do_file(FILE *fh); +void cli_do(int sockfd); +int cli_arg_help(struct cli_def *cli, int cr_ok, char *entry, ...); + + +// icmp.c +void host_unreachable(in_addr_t destination, uint16_t id, in_addr_t source, uint8_t *packet, int packet_len); + + +extern tunnelt *tunnel; +extern sessiont *session; +extern sessionlocalt *sess_local; +extern ippoolt *ip_address_pool; +#define sessionfree (session[0].next) + + +extern configt *config; +extern time_t basetime; // Time when this process started. +extern time_t time_now; // Seconds since EPOCH. +extern char main_quit; +extern uint32_t last_id; +extern struct Tstats *_statistics; +extern in_addr_t my_address; +extern int clifd; +extern int epollfd; + +struct event_data { + enum { + FD_TYPE_CLI, + FD_TYPE_CLUSTER, + FD_TYPE_TUN, + FD_TYPE_UDP, + FD_TYPE_CONTROL, + FD_TYPE_DAE, + FD_TYPE_RADIUS, + FD_TYPE_BGP, + } type; + int index; // for RADIUS, BGP +}; + +#define TIME (config->current_time) + +extern uint16_t MRU; +extern uint16_t MSS; + +// macros for handling help in cli commands +#define CLI_HELP_REQUESTED (argc > 0 && argv[argc-1][strlen(argv[argc-1])-1] == '?') +#define CLI_HELP_NO_ARGS (argc > 1 || argv[0][1]) ? CLI_OK : cli_arg_help(cli, 1, NULL) + +// CVS identifiers (for "show version file") +extern char const *cvs_id_arp; +extern char const *cvs_id_cli; +extern char const *cvs_id_cluster; +extern char const *cvs_id_constants; +extern char const *cvs_id_control; +extern char const *cvs_id_icmp; +extern char const *cvs_id_l2tpns; +extern char const *cvs_id_ll; +extern char const *cvs_id_md5; +extern char const *cvs_id_ppp; +extern char const *cvs_id_radius; +extern char const *cvs_id_tbf; +extern char const *cvs_id_util; + +#endif /* __L2TPNS_H__ */ diff --git a/l2tpns.spec b/l2tpns.spec new file mode 100644 index 0000000..4cd9ca8 --- /dev/null +++ b/l2tpns.spec @@ -0,0 +1,47 @@ +Summary: A high-speed clustered L2TP LNS +Name: l2tpns +Version: 2.1.21 +Release: 1 +License: GPL +Group: System Environment/Daemons +Source: http://optusnet.dl.sourceforge.net/sourceforge/l2tpns/l2tpns-%{version}.tar.gz +URL: http://sourceforge.net/projects/l2tpns +BuildRoot: %{_tmppath}/%{name}-%{version}-%(%__id -un) +Prereq: /sbin/chkconfig +BuildRequires: libcli >= 1.8.5 +Requires: libcli >= 1.8.5 + +%description +l2tpns is a layer 2 tunneling protocol network server (LNS). It +supports up to 65535 concurrent sessions per server/cluster plus ISP +features such as rate limiting, walled garden, usage accounting, and +more. + +%prep +%setup -q + +%build +make + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot} +make install DESTDIR=%{buildroot} + +%clean +rm -rf %{buildroot} + +%files +%defattr(-,root,root) +%doc Changes INSTALL INTERNALS COPYING THANKS Docs/manual.html +%dir /etc/l2tpns +%config(noreplace) /etc/l2tpns/users +%config(noreplace) /etc/l2tpns/startup-config +%config(noreplace) /etc/l2tpns/ip_pool +%attr(755,root,root) /usr/sbin/* +%attr(755,root,root) /usr/lib/l2tpns +%attr(644,root,root) /usr/share/man/man[58]/* + +%changelog +* Fri Dec 1 2006 Brendan O'Dea 2.1.21-1 +- 2.1.21 release, see /usr/share/doc/l2tpns-2.1.21/Changes diff --git a/ll.c b/ll.c new file mode 100644 index 0000000..f59d73a --- /dev/null +++ b/ll.c @@ -0,0 +1,141 @@ +// L2TPNS Linked List Stuff + +char const *cvs_id_ll = "$Id: ll.c,v 1.6 2004/11/18 08:12:55 bodea Exp $"; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ll.h" + +linked_list *ll_init() +{ + return (linked_list *)calloc(sizeof(linked_list), 1); +} + +void ll_done(linked_list *l) +{ + li *i = l->head, *n; + + while (i) + { + n = i->next; + free(i); + i = n; + } + + free(l); +} + +li *ll_push(linked_list *l, void *data) +{ + li *i; + + if (!l) return NULL; + if (!(i = (li *)calloc(sizeof(li), 1))) return NULL; + + i->data = data; + i->next = NULL; + if (l->end) + l->end->next = i; + else + l->head = i; + l->end = i; + + return i; +} + +void *ll_pop(linked_list *l) +{ + li *i; + void *data; + + if (!l) return NULL; + if (!l->head) + return NULL; + + data = l->head->data; + i = l->head->next; + free(l->head); + l->head = i; + return data; +} + +void ll_iterate(linked_list *l, int(*func)(void *)) +{ + li *i; + if (!l || !func) return; + + for (i = l->head; i; i = i->next) + { + if (i->data && !func(i->data)) + break; + } +} + +void ll_reset(linked_list *l) +{ + if (!l) return; + l->current = NULL; +} + +void *ll_next(linked_list *l) +{ + if (!l) return NULL; + if (!l->current) + l->current = l->head; + else + l->current = l->current->next; + if (!l->current) + return NULL; + return l->current->data; +} + +void ll_delete(linked_list *l, void *data) +{ + li *i = l->head, *p = NULL; + + while (i) + { + if (i->data == data) + { + if (l->head == i) l->head = i->next; + if (l->end == i) l->end = p; + if (p) p->next = i->next; + free(i); + l->current = NULL; + return; + } + p = i; + i = i->next; + } +} + +int ll_size(linked_list *l) +{ + int count = 0; + li *i; + + if (!l) return 0; + + for (i = l->head; i; i = i->next) + if (i->data) count++; + + return count; +} + +int ll_contains(linked_list *l, void *search) +{ + li *i; + for (i = l->head; i; i = i->next) + if (i->data == search) + return 1; + return 0; +} + diff --git a/ll.h b/ll.h new file mode 100644 index 0000000..f4d2d88 --- /dev/null +++ b/ll.h @@ -0,0 +1,28 @@ +#ifndef __LL_H__ +#define __LL_H__ + +typedef struct s_li +{ + void *data; + struct s_li *next; +} li; + +typedef struct s_ll +{ + li *head; + li *end; + li *current; +} linked_list; + +linked_list *ll_init(); +void ll_done(linked_list *l); +li *ll_push(linked_list *l, void *data); +void ll_delete(linked_list *l, void *data); +void *ll_pop(linked_list *l); +void ll_iterate(linked_list *l, int(*func)(void *)); +void ll_reset(linked_list *l); +void *ll_next(linked_list *l); +int ll_size(linked_list *l); +int ll_contains(linked_list *l, void *search); + +#endif /* __LL_H__ */ diff --git a/md5.c b/md5.c new file mode 100644 index 0000000..9abecfb --- /dev/null +++ b/md5.c @@ -0,0 +1,274 @@ +/* + * This is an OpenSSL-compatible implementation of the RSA Data Security, + * Inc. MD5 Message-Digest Algorithm. + * + * Written by Solar Designer in 2001, and placed + * in the public domain. There's absolutely no warranty. + * + * This differs from Colin Plumb's older public domain implementation in + * that no 32-bit integer data type is required, there's no compile-time + * endianness configuration, and the function prototypes match OpenSSL's. + * The primary goals are portability and ease of use. + * + * This implementation is meant to be fast, but not as fast as possible. + * Some known optimizations are not included to reduce source code size + * and avoid compile-time configuration. + */ + +#ifndef HAVE_OPENSSL + +#include + +#include "md5.h" + +/* + * The basic MD5 functions. + * + * F is optimized compared to its RFC 1321 definition just like in Colin + * Plumb's implementation. + */ +#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | ~(z))) + +/* + * The MD5 transformation for all four rounds. + */ +#define STEP(f, a, b, c, d, x, t, s) \ + (a) += f((b), (c), (d)) + (x) + (t); \ + (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ + (a) += (b); + +/* + * SET reads 4 input bytes in little-endian byte order and stores them + * in a properly aligned word in host byte order. + * + * The check for little-endian architectures which tolerate unaligned + * memory accesses is just an optimization. Nothing will break if it + * doesn't work. + */ +#if defined(__i386__) || defined(__vax__) +#define SET(n) \ + (*(MD5_u32plus *)&ptr[(n) * 4]) +#define GET(n) \ + SET(n) +#else +#define SET(n) \ + (ctx->block[(n)] = \ + (MD5_u32plus)ptr[(n) * 4] | \ + ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \ + ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \ + ((MD5_u32plus)ptr[(n) * 4 + 3] << 24)) +#define GET(n) \ + (ctx->block[(n)]) +#endif + +/* + * This processes one or more 64-byte data blocks, but does NOT update + * the bit counters. There're no alignment requirements. + */ +static void *body(MD5_CTX *ctx, void *data, unsigned long size) +{ + unsigned char *ptr; + MD5_u32plus a, b, c, d; + MD5_u32plus saved_a, saved_b, saved_c, saved_d; + + ptr = data; + + a = ctx->a; + b = ctx->b; + c = ctx->c; + d = ctx->d; + + do { + saved_a = a; + saved_b = b; + saved_c = c; + saved_d = d; + +/* Round 1 */ + STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) + STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) + STEP(F, c, d, a, b, SET(2), 0x242070db, 17) + STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) + STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) + STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) + STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) + STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) + STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) + STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) + STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) + STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) + STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) + STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) + STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) + STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) + +/* Round 2 */ + STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) + STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) + STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) + STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) + STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) + STEP(G, d, a, b, c, GET(10), 0x02441453, 9) + STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) + STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) + STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) + STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) + STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) + STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) + STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) + STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) + STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) + STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) + +/* Round 3 */ + STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) + STEP(H, d, a, b, c, GET(8), 0x8771f681, 11) + STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) + STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23) + STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) + STEP(H, d, a, b, c, GET(4), 0x4bdecfa9, 11) + STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) + STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23) + STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) + STEP(H, d, a, b, c, GET(0), 0xeaa127fa, 11) + STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) + STEP(H, b, c, d, a, GET(6), 0x04881d05, 23) + STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) + STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11) + STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) + STEP(H, b, c, d, a, GET(2), 0xc4ac5665, 23) + +/* Round 4 */ + STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) + STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) + STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) + STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) + STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) + STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) + STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) + STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) + STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) + STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) + STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) + STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) + STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) + STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) + STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) + STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) + + a += saved_a; + b += saved_b; + c += saved_c; + d += saved_d; + + ptr += 64; + } while (size -= 64); + + ctx->a = a; + ctx->b = b; + ctx->c = c; + ctx->d = d; + + return ptr; +} + +void MD5_Init(MD5_CTX *ctx) +{ + ctx->a = 0x67452301; + ctx->b = 0xefcdab89; + ctx->c = 0x98badcfe; + ctx->d = 0x10325476; + + ctx->lo = 0; + ctx->hi = 0; +} + +void MD5_Update(MD5_CTX *ctx, void *data, unsigned long size) +{ + MD5_u32plus saved_lo; + unsigned long used, free; + + saved_lo = ctx->lo; + if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) + ctx->hi++; + ctx->hi += size >> 29; + + used = saved_lo & 0x3f; + + if (used) { + free = 64 - used; + + if (size < free) { + memcpy(&ctx->buffer[used], data, size); + return; + } + + memcpy(&ctx->buffer[used], data, free); + data = (unsigned char *)data + free; + size -= free; + body(ctx, ctx->buffer, 64); + } + + if (size >= 64) { + data = body(ctx, data, size & ~(unsigned long)0x3f); + size &= 0x3f; + } + + memcpy(ctx->buffer, data, size); +} + +void MD5_Final(unsigned char *result, MD5_CTX *ctx) +{ + unsigned long used, free; + + used = ctx->lo & 0x3f; + + ctx->buffer[used++] = 0x80; + + free = 64 - used; + + if (free < 8) { + memset(&ctx->buffer[used], 0, free); + body(ctx, ctx->buffer, 64); + used = 0; + free = 64; + } + + memset(&ctx->buffer[used], 0, free - 8); + + ctx->lo <<= 3; + ctx->buffer[56] = ctx->lo; + ctx->buffer[57] = ctx->lo >> 8; + ctx->buffer[58] = ctx->lo >> 16; + ctx->buffer[59] = ctx->lo >> 24; + ctx->buffer[60] = ctx->hi; + ctx->buffer[61] = ctx->hi >> 8; + ctx->buffer[62] = ctx->hi >> 16; + ctx->buffer[63] = ctx->hi >> 24; + + body(ctx, ctx->buffer, 64); + + result[0] = ctx->a; + result[1] = ctx->a >> 8; + result[2] = ctx->a >> 16; + result[3] = ctx->a >> 24; + result[4] = ctx->b; + result[5] = ctx->b >> 8; + result[6] = ctx->b >> 16; + result[7] = ctx->b >> 24; + result[8] = ctx->c; + result[9] = ctx->c >> 8; + result[10] = ctx->c >> 16; + result[11] = ctx->c >> 24; + result[12] = ctx->d; + result[13] = ctx->d >> 8; + result[14] = ctx->d >> 16; + result[15] = ctx->d >> 24; + + memset(ctx, 0, sizeof(*ctx)); +} + +#endif diff --git a/md5.h b/md5.h new file mode 100644 index 0000000..1ff5045 --- /dev/null +++ b/md5.h @@ -0,0 +1,28 @@ +/* + * This is an OpenSSL-compatible implementation of the RSA Data Security, + * Inc. MD5 Message-Digest Algorithm. + * + * Written by Solar Designer in 2001, and placed + * in the public domain. See md5.c for more information. + */ + +#ifdef HAVE_OPENSSL +#include +#elif !defined(_MD5_H) +#define _MD5_H + +/* Any 32-bit or wider unsigned integer data type will do */ +typedef unsigned long MD5_u32plus; + +typedef struct { + MD5_u32plus lo, hi; + MD5_u32plus a, b, c, d; + unsigned char buffer[64]; + MD5_u32plus block[16]; +} MD5_CTX; + +extern void MD5_Init(MD5_CTX *ctx); +extern void MD5_Update(MD5_CTX *ctx, void *data, unsigned long size); +extern void MD5_Final(unsigned char *result, MD5_CTX *ctx); + +#endif diff --git a/nsctl.c b/nsctl.c new file mode 100644 index 0000000..59ad0c6 --- /dev/null +++ b/nsctl.c @@ -0,0 +1,238 @@ +/* l2tpns plugin control */ + +#include +#include +#include +#include +#include +#include +#include + +#include "l2tpns.h" +#include "control.h" + +struct { + char *command; + char *usage; + int action; +} builtins[] = { + { "load_plugin", " PLUGIN Load named plugin", NSCTL_REQ_LOAD }, + { "unload_plugin", " PLUGIN Unload named plugin", NSCTL_REQ_UNLOAD }, + { "help", " List available commands", NSCTL_REQ_HELP }, + { 0 } +}; + +static int debug = 0; +static int timeout = 2; // 2 seconds +static char *me; + +#define USAGE() fprintf(stderr, "Usage: %s [-d] [-h HOST[:PORT]] [-t TIMEOUT] COMMAND [ARG ...]\n", me) + +static struct nsctl *request(char *host, int port, int type, int argc, char *argv[]); + +int main(int argc, char *argv[]) +{ + int req_type = 0; + char *host = 0; + int port; + int i; + char *p; + struct nsctl *res; + + if ((p = strrchr((me = argv[0]), '/'))) + me = p + 1; + + opterr = 0; + while ((i = getopt(argc, argv, "dh:t:")) != -1) + switch (i) + { + case 'd': + debug++; + break; + + case 'h': + host = optarg; + break; + + case 't': + timeout = atoi(optarg); + break; + + default: + USAGE(); + return EXIT_FAILURE; + } + + argc -= optind; + argv += optind; + + if (argc < 1 || !argv[0][0]) + { + USAGE(); + return EXIT_FAILURE; + } + + if (!host) + host = "127.0.0.1"; + + if ((p = strchr(host, ':'))) + { + port = atoi(p + 1); + if (!port) + { + fprintf(stderr, "%s: invalid port `%s'\n", me, p + 1); + return EXIT_FAILURE; + } + + *p = 0; + } + else + { + port = NSCTL_PORT; + } + + for (i = 0; !req_type && builtins[i].command; i++) + if (!strcmp(argv[0], builtins[i].command)) + req_type = builtins[i].action; + + if (req_type == NSCTL_REQ_HELP) + { + printf("Available commands:\n"); + for (i = 0; builtins[i].command; i++) + printf(" %s%s\n", builtins[i].command, builtins[i].usage); + } + + if (req_type) + { + argc--; + argv++; + } + else + { + req_type = NSCTL_REQ_CONTROL; + } + + if ((res = request(host, port, req_type, argc, argv))) + { + FILE *stream = stderr; + int status = EXIT_FAILURE; + + if (res->type == NSCTL_RES_OK) + { + stream = stdout; + status = EXIT_SUCCESS; + } + + for (i = 0; i < res->argc; i++) + fprintf(stream, "%s\n", res->argv[i]); + + return status; + } + + return EXIT_FAILURE; +} + +static void sigalrm_handler(int sig) { } + +static struct nsctl *request(char *host, int port, int type, int argc, char *argv[]) +{ + static struct nsctl res; + struct sockaddr_in peer; + socklen_t len = sizeof(peer); + struct hostent *h = gethostbyname(host); + int fd; + uint8_t buf[NSCTL_MAX_PKT_SZ]; + int sz; + char *err; + + if (!h || h->h_addrtype != AF_INET) + { + fprintf(stderr, "%s: invalid host `%s'\n", me, host); + return 0; + } + + if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) + { + fprintf(stderr, "%s: can't create udp socket (%s)\n", me, strerror(errno)); + return 0; + } + + memset(&peer, 0, len); + peer.sin_family = AF_INET; + peer.sin_port = htons(port); + memcpy(&peer.sin_addr.s_addr, h->h_addr, sizeof(peer.sin_addr.s_addr)); + + if (connect(fd, (struct sockaddr *) &peer, sizeof(peer)) < 0) + { + fprintf(stderr, "%s: udp connect failed (%s)\n", me, strerror(errno)); + return 0; + } + + if ((sz = pack_control(buf, sizeof(buf), type, argc, argv)) < 0) + { + fprintf(stderr, "%s: error packing request\n", me); + return 0; + } + + if (debug) + { + struct nsctl req; + if (unpack_control(&req, buf, sz) == type) + { + fprintf(stderr, "Sending "); + dump_control(&req, stderr); + } + } + + if (send(fd, buf, sz, 0) < 0) + { + fprintf(stderr, "%s: error sending request (%s)\n", me, strerror(errno)); + return 0; + } + + /* set timer */ + if (timeout) + { + struct sigaction alrm; + alrm.sa_handler = sigalrm_handler; + sigemptyset(&alrm.sa_mask); + alrm.sa_flags = 0; + + sigaction(SIGALRM, &alrm, 0); + alarm(timeout); + } + + sz = recv(fd, buf, sizeof(buf), 0); + alarm(0); + + if (sz < 0) + { + fprintf(stderr, "%s: error receiving response (%s)\n", me, + errno == EINTR ? "timed out" : strerror(errno)); + + return 0; + } + + if ((type = unpack_control(&res, buf, sz)) > 0 && type & NSCTL_RESPONSE) + { + if (debug) + { + fprintf(stderr, "Received "); + dump_control(&res, stderr); + } + + return &res; + } + + err = "unknown error"; + switch (type) + { + case NSCTL_ERR_SHORT: err = "short packet"; break; + case NSCTL_ERR_LONG: err = "extra data"; break; + case NSCTL_ERR_MAGIC: err = "bad magic"; break; + case NSCTL_ERR_TYPE: err = "invalid type"; break; + } + + fprintf(stderr, "%s: %s\n", me, err); + return 0; +} diff --git a/plugin.h b/plugin.h new file mode 100644 index 0000000..9a4b67e --- /dev/null +++ b/plugin.h @@ -0,0 +1,129 @@ +#ifndef __PLUGIN_H__ +#define __PLUGIN_H__ + +#define PLUGIN_API_VERSION 7 +#define MAX_PLUGIN_TYPES 30 + +enum +{ + PLUGIN_PRE_AUTH = 1, + PLUGIN_POST_AUTH, + PLUGIN_PACKET_RX, + PLUGIN_PACKET_TX, + PLUGIN_TIMER, + PLUGIN_NEW_SESSION, + PLUGIN_KILL_SESSION, + PLUGIN_CONTROL, + PLUGIN_RADIUS_RESPONSE, + PLUGIN_RADIUS_RESET, + PLUGIN_RADIUS_ACCOUNT, + PLUGIN_BECOME_MASTER, + PLUGIN_NEW_SESSION_MASTER, +}; + +#define PLUGIN_RET_ERROR 0 +#define PLUGIN_RET_OK 1 +#define PLUGIN_RET_STOP 2 +#define PLUGIN_RET_NOTMASTER 3 + +struct pluginfuncs +{ + void (*log)(int level, sessionidt s, tunnelidt t, const char *format, ...); + void (*log_hex)(int level, const char *title, const uint8_t *data, int maxsize); + char *(*fmtaddr)(in_addr_t addr, int n); + sessionidt (*get_session_by_username)(char *username); + sessiont *(*get_session_by_id)(sessionidt s); + sessionidt (*get_id_by_session)(sessiont *s); + uint16_t (*radiusnew)(sessionidt s); + void (*radiussend)(uint16_t r, uint8_t state); + void *(*getconfig)(char *key, enum config_typet type); + void (*sessionshutdown)(sessionidt s, char const *reason, int result, int error, int term_cause); + void (*sessionkill)(sessionidt s, char *reason); + void (*throttle)(sessionidt s, int rate_in, int rate_out); + int (*session_changed)(int sid); +}; + +struct param_pre_auth +{ + tunnelt *t; + sessiont *s; + char *username; + char *password; + int protocol; + int continue_auth; +}; + +struct param_post_auth +{ + tunnelt *t; + sessiont *s; + char *username; + short auth_allowed; + int protocol; +}; + +struct param_packet_rx +{ + tunnelt *t; + sessiont *s; + char *buf; + int len; +}; + +struct param_packet_tx +{ + tunnelt *t; + sessiont *s; + char *buf; + int len; +}; + +struct param_timer +{ + time_t time_now; +}; + +struct param_control +{ + int iam_master; + int argc; + char **argv; + // output + int response; + char *additional; +}; + +struct param_new_session +{ + tunnelt *t; + sessiont *s; +}; + +struct param_kill_session +{ + tunnelt *t; + sessiont *s; +}; + +struct param_radius_response +{ + tunnelt *t; + sessiont *s; + char *key; + char *value; +}; + +struct param_radius_reset +{ + tunnelt *t; + sessiont *s; +}; + +struct param_radius_account +{ + tunnelt *t; + sessiont *s; + uint8_t **packet; +}; + +#endif /* __PLUGIN_H__ */ diff --git a/ppp.c b/ppp.c new file mode 100644 index 0000000..d903496 --- /dev/null +++ b/ppp.c @@ -0,0 +1,2000 @@ +// L2TPNS PPP Stuff + +char const *cvs_id_ppp = "$Id: ppp.c,v 1.99.2.1 2006/05/26 07:33:52 bodea Exp $"; + +#include +#include +#include +#include +#include +#include "l2tpns.h" +#include "constants.h" +#include "plugin.h" +#include "util.h" +#include "tbf.h" +#include "cluster.h" + +extern tunnelt *tunnel; +extern sessiont *session; +extern radiust *radius; +extern int tunfd; +extern char hostname[]; +extern uint32_t eth_tx; +extern time_t time_now; +extern configt *config; + +static int add_lcp_auth(uint8_t *b, int size, int authtype); + +// Process PAP messages +void processpap(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l) +{ + char user[MAXUSER]; + char pass[MAXPASS]; + uint16_t hl; + uint16_t r; + + CSTAT(processpap); + + LOG_HEX(5, "PAP", p, l); + if (l < 4) + { + LOG(1, s, t, "Short PAP %u bytes\n", l); + STAT(tunnel_rx_errors); + sessionshutdown(s, "Short PAP packet.", CDN_ADMIN_DISC, TERM_USER_ERROR); + return; + } + + if ((hl = ntohs(*(uint16_t *) (p + 2))) > l) + { + LOG(1, s, t, "Length mismatch PAP %u/%u\n", hl, l); + STAT(tunnel_rx_errors); + sessionshutdown(s, "PAP length mismatch.", CDN_ADMIN_DISC, TERM_USER_ERROR); + return; + } + l = hl; + + if (*p != 1) + { + LOG(1, s, t, "Unexpected PAP code %d\n", *p); + STAT(tunnel_rx_errors); + sessionshutdown(s, "Unexpected PAP code.", CDN_ADMIN_DISC, TERM_USER_ERROR); + return; + } + + if (session[s].ppp.phase != Authenticate) + { + LOG(2, s, t, "PAP ignored in %s phase\n", ppp_phase(session[s].ppp.phase)); + return; + } + + { + uint8_t *b = p; + b += 4; + user[0] = pass[0] = 0; + if (*b && *b < sizeof(user)) + { + memcpy(user, b + 1, *b); + user[*b] = 0; + b += 1 + *b; + if (*b && *b < sizeof(pass)) + { + memcpy(pass, b + 1, *b); + pass[*b] = 0; + } + } + LOG(3, s, t, "PAP login %s/%s\n", user, pass); + } + + if (session[s].ip || !(r = radiusnew(s))) + { + // respond now, either no RADIUS available or already authenticated + uint8_t b[MAXETHER]; + uint8_t id = p[1]; + uint8_t *p = makeppp(b, sizeof(b), 0, 0, s, t, PPPPAP); + if (!p) return; + + if (session[s].ip) + *p = 2; // ACK + else + *p = 3; // cant authorise + p[1] = id; + *(uint16_t *) (p + 2) = htons(5); // length + p[4] = 0; // no message + tunnelsend(b, 5 + (p - b), t); // send it + + if (session[s].ip) + { + LOG(3, s, t, "Already an IP allocated: %s (%d)\n", + fmtaddr(htonl(session[s].ip), 0), session[s].ip_pool_index); + } + else + { + LOG(1, s, t, "No RADIUS session available to authenticate session...\n"); + sessionshutdown(s, "No free RADIUS sessions.", CDN_UNAVAILABLE, TERM_SERVICE_UNAVAILABLE); + } + } + else + { + // Run PRE_AUTH plugins + struct param_pre_auth packet = { &tunnel[t], &session[s], strdup(user), strdup(pass), PPPPAP, 1 }; + run_plugins(PLUGIN_PRE_AUTH, &packet); + if (!packet.continue_auth) + { + LOG(3, s, t, "A plugin rejected PRE_AUTH\n"); + if (packet.username) free(packet.username); + if (packet.password) free(packet.password); + return; + } + + strncpy(session[s].user, packet.username, sizeof(session[s].user) - 1); + strncpy(radius[r].pass, packet.password, sizeof(radius[r].pass) - 1); + + free(packet.username); + free(packet.password); + + radius[r].id = p[1]; + LOG(3, s, t, "Sending login for %s/%s to RADIUS\n", user, pass); + radiussend(r, RADIUSAUTH); + } +} + +// Process CHAP messages +void processchap(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l) +{ + uint16_t r; + uint16_t hl; + + CSTAT(processchap); + + LOG_HEX(5, "CHAP", p, l); + + if (l < 4) + { + LOG(1, s, t, "Short CHAP %u bytes\n", l); + STAT(tunnel_rx_errors); + sessionshutdown(s, "Short CHAP packet.", CDN_ADMIN_DISC, TERM_USER_ERROR); + return; + } + + if ((hl = ntohs(*(uint16_t *) (p + 2))) > l) + { + LOG(1, s, t, "Length mismatch CHAP %u/%u\n", hl, l); + STAT(tunnel_rx_errors); + sessionshutdown(s, "CHAP length mismatch.", CDN_ADMIN_DISC, TERM_USER_ERROR); + return; + } + l = hl; + + if (*p != 2) + { + LOG(1, s, t, "Unexpected CHAP response code %d\n", *p); + STAT(tunnel_rx_errors); + sessionshutdown(s, "CHAP length mismatch.", CDN_ADMIN_DISC, TERM_USER_ERROR); + return; + } + + if (session[s].ppp.phase != Authenticate) + { + LOG(2, s, t, "CHAP ignored in %s phase\n", ppp_phase(session[s].ppp.phase)); + return; + } + + r = sess_local[s].radius; + if (!r) + { + LOG(3, s, t, "Unexpected CHAP message\n"); + + // Some modems (Netgear DM602, possibly others) persist in using CHAP even + // after ACKing our ConfigReq for PAP. + if (sess_local[s].lcp_authtype == AUTHPAP && config->radius_authtypes & AUTHCHAP) + { + sess_local[s].lcp_authtype = AUTHCHAP; + sendchap(s, t); + } + return; + } + + if (p[1] != radius[r].id) + { + LOG(1, s, t, "Wrong CHAP response ID %d (should be %d) (%d)\n", p[1], radius[r].id, r); + STAT(tunnel_rx_errors); + sessionshutdown(s, "Unexpected CHAP response ID.", CDN_ADMIN_DISC, TERM_USER_ERROR); + return; + } + + if (l < 5 || p[4] != 16) + { + LOG(1, s, t, "Bad CHAP response length %d\n", l < 5 ? -1 : p[4]); + STAT(tunnel_rx_errors); + sessionshutdown(s, "Bad CHAP response length.", CDN_ADMIN_DISC, TERM_USER_ERROR); + return; + } + + l -= 5; + p += 5; + if (l < 16 || l - 16 >= sizeof(session[s].user)) + { + LOG(1, s, t, "CHAP user too long %d\n", l - 16); + STAT(tunnel_rx_errors); + sessionshutdown(s, "CHAP username too long.", CDN_ADMIN_DISC, TERM_USER_ERROR); + return; + } + + // Run PRE_AUTH plugins + { + struct param_pre_auth packet = { &tunnel[t], &session[s], NULL, NULL, PPPCHAP, 1 }; + + packet.password = calloc(17, 1); + memcpy(packet.password, p, 16); + + p += 16; + l -= 16; + + packet.username = calloc(l + 1, 1); + memcpy(packet.username, p, l); + + run_plugins(PLUGIN_PRE_AUTH, &packet); + if (!packet.continue_auth) + { + LOG(3, s, t, "A plugin rejected PRE_AUTH\n"); + if (packet.username) free(packet.username); + if (packet.password) free(packet.password); + return; + } + + strncpy(session[s].user, packet.username, sizeof(session[s].user) - 1); + memcpy(radius[r].pass, packet.password, 16); + + free(packet.username); + free(packet.password); + } + + radius[r].chap = 1; + LOG(3, s, t, "CHAP login %s\n", session[s].user); + radiussend(r, RADIUSAUTH); +} + +static void dumplcp(uint8_t *p, int l) +{ + int x = l - 4; + uint8_t *o = (p + 4); + + LOG_HEX(5, "PPP LCP Packet", p, l); + LOG(4, 0, 0, "PPP LCP Packet type %d (%s len %d)\n", *p, ppp_code((int)*p), ntohs( ((uint16_t *) p)[1]) ); + LOG(4, 0, 0, "Length: %d\n", l); + if (*p != ConfigReq && *p != ConfigRej && *p != ConfigAck) + return; + + while (x > 2) + { + int type = o[0]; + int length = o[1]; + if (length < 2) + { + LOG(4, 0, 0, " Option length is %d...\n", length); + break; + } + if (type == 0) + { + LOG(4, 0, 0, " Option type is 0...\n"); + x -= length; + o += length; + continue; + } + switch (type) + { + case 1: // Maximum-Receive-Unit + if (length == 4) + LOG(4, 0, 0, " %s %d\n", ppp_lcp_option(type), ntohs(*(uint16_t *)(o + 2))); + else + LOG(4, 0, 0, " %s odd length %d\n", ppp_lcp_option(type), length); + break; + case 2: // Async-Control-Character-Map + if (length == 6) + { + uint32_t asyncmap = ntohl(*(uint32_t *)(o + 2)); + LOG(4, 0, 0, " %s %x\n", ppp_lcp_option(type), asyncmap); + } + else + LOG(4, 0, 0, " %s odd length %d\n", ppp_lcp_option(type), length); + break; + case 3: // Authentication-Protocol + if (length == 4) + { + int proto = ntohs(*(uint16_t *)(o + 2)); + LOG(4, 0, 0, " %s 0x%x (%s)\n", ppp_lcp_option(type), proto, + proto == PPPPAP ? "PAP" : "UNSUPPORTED"); + } + else if (length == 5) + { + int proto = ntohs(*(uint16_t *)(o + 2)); + int algo = *(o + 4); + LOG(4, 0, 0, " %s 0x%x 0x%x (%s)\n", ppp_lcp_option(type), proto, algo, + (proto == PPPCHAP && algo == 5) ? "CHAP MD5" : "UNSUPPORTED"); + } + else + LOG(4, 0, 0, " %s odd length %d\n", ppp_lcp_option(type), length); + break; + case 4: // Quality-Protocol + { + uint32_t qp = ntohl(*(uint32_t *)(o + 2)); + LOG(4, 0, 0, " %s %x\n", ppp_lcp_option(type), qp); + } + break; + case 5: // Magic-Number + if (length == 6) + { + uint32_t magicno = ntohl(*(uint32_t *)(o + 2)); + LOG(4, 0, 0, " %s %x\n", ppp_lcp_option(type), magicno); + } + else + LOG(4, 0, 0, " %s odd length %d\n", ppp_lcp_option(type), length); + break; + case 7: // Protocol-Field-Compression + case 8: // Address-And-Control-Field-Compression + LOG(4, 0, 0, " %s\n", ppp_lcp_option(type)); + break; + default: + LOG(2, 0, 0, " Unknown PPP LCP Option type %d\n", type); + break; + } + x -= length; + o += length; + } +} + +void lcp_open(sessionidt s, tunnelidt t) +{ + // transition to Authentication or Network phase: + session[s].ppp.phase = sess_local[s].lcp_authtype ? Authenticate : Network; + + LOG(3, s, t, "LCP: Opened, phase %s\n", ppp_phase(session[s].ppp.phase)); + + // LCP now Opened + change_state(s, lcp, Opened); + + if (session[s].ppp.phase == Authenticate) + { + if (sess_local[s].lcp_authtype == AUTHCHAP) + sendchap(s, t); + } + else + { + // This-Layer-Up + sendipcp(s, t); + change_state(s, ipcp, RequestSent); + // move to passive state for IPv6 (if configured), CCP + if (config->ipv6_prefix.s6_addr[0]) + change_state(s, ipv6cp, Stopped); + else + change_state(s, ipv6cp, Closed); + + change_state(s, ccp, Stopped); + } +} + +static void lcp_restart(sessionidt s) +{ + session[s].ppp.phase = Establish; + // This-Layer-Down + change_state(s, ipcp, Dead); + change_state(s, ipv6cp, Dead); + change_state(s, ccp, Dead); +} + +static uint8_t *ppp_conf_rej(sessionidt s, uint8_t *buf, size_t blen, uint16_t mtype, + uint8_t **response, uint8_t *queued, uint8_t *packet, uint8_t *option) +{ + if (!*response || **response != ConfigRej) + { + queued = *response = makeppp(buf, blen, packet, 2, s, session[s].tunnel, mtype); + if (!queued) + return 0; + + *queued = ConfigRej; + queued += 4; + } + + if ((queued - buf + option[1]) > blen) + { + LOG(2, s, session[s].tunnel, "PPP overflow for ConfigRej (proto %u, option %u).\n", mtype, *option); + return 0; + } + + memcpy(queued, option, option[1]); + return queued + option[1]; +} + +static uint8_t *ppp_conf_nak(sessionidt s, uint8_t *buf, size_t blen, uint16_t mtype, + uint8_t **response, uint8_t *queued, uint8_t *packet, uint8_t *option, + uint8_t *value, size_t vlen) +{ + int *nak_sent; + switch (mtype) + { + case PPPLCP: nak_sent = &sess_local[s].lcp.nak_sent; break; + case PPPIPCP: nak_sent = &sess_local[s].ipcp.nak_sent; break; + case PPPIPV6CP: nak_sent = &sess_local[s].ipv6cp.nak_sent; break; + default: return 0; // ? + } + + if (*response && **response != ConfigNak) + { + if (*nak_sent < config->ppp_max_failure) // reject queued + return queued; + + return ppp_conf_rej(s, buf, blen, mtype, response, 0, packet, option); + } + + if (!*response) + { + if (*nak_sent >= config->ppp_max_failure) + return ppp_conf_rej(s, buf, blen, mtype, response, 0, packet, option); + + queued = *response = makeppp(buf, blen, packet, 2, s, session[s].tunnel, mtype); + if (!queued) + return 0; + + (*nak_sent)++; + *queued = ConfigNak; + queued += 4; + } + + if ((queued - buf + vlen + 2) > blen) + { + LOG(2, s, session[s].tunnel, "PPP overflow for ConfigNak (proto %u, option %u).\n", mtype, *option); + return 0; + } + + *queued++ = *option; + *queued++ = vlen + 2; + memcpy(queued, value, vlen); + return queued + vlen; +} + +static void ppp_code_rej(sessionidt s, tunnelidt t, uint16_t proto, + char *pname, uint8_t *p, uint16_t l, uint8_t *buf, size_t size) +{ + uint8_t *q; + int mru = session[s].mru; + if (mru < MINMTU) mru = MINMTU; + if (mru > size) mru = size; + + l += 4; + if (l > mru) l = mru; + + q = makeppp(buf, size, 0, 0, s, t, proto); + if (!q) return; + + *q = CodeRej; + *(q + 1) = ++sess_local[s].lcp_ident; + *(uint16_t *)(q + 2) = htons(l); + memcpy(q + 4, p, l - 4); + + LOG(2, s, t, "Unexpected %s code %s\n", pname, ppp_code(*p)); + LOG(3, s, t, "%s: send %s\n", pname, ppp_code(*q)); + if (config->debug > 3) dumplcp(q, l); + + tunnelsend(buf, l + (q - buf), t); +} + +// Process LCP messages +void processlcp(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l) +{ + uint8_t b[MAXETHER]; + uint8_t *q = NULL; + uint16_t hl; + + CSTAT(processlcp); + + LOG_HEX(5, "LCP", p, l); + if (l < 4) + { + LOG(1, s, t, "Short LCP %d bytes\n", l); + STAT(tunnel_rx_errors); + return ; + } + + if ((hl = ntohs(*(uint16_t *) (p + 2))) > l) + { + LOG(1, s, t, "Length mismatch LCP %u/%u\n", hl, l); + STAT(tunnel_rx_errors); + return ; + } + l = hl; + + if (session[s].die) // going down... + return; + + LOG((*p == EchoReq || *p == EchoReply) ? 4 : 3, s, t, + "LCP: recv %s\n", ppp_code(*p)); + + if (config->debug > 3) dumplcp(p, l); + + if (*p == ConfigAck) + { + int x = l - 4; + uint8_t *o = (p + 4); + int authtype = 0; + + while (x > 2) + { + int type = o[0]; + int length = o[1]; + + if (length == 0 || type == 0 || x < length) break; + switch (type) + { + case 3: // Authentication-Protocol + { + int proto = ntohs(*(uint16_t *)(o + 2)); + if (proto == PPPPAP) + authtype = AUTHPAP; + else if (proto == PPPCHAP && *(o + 4) == 5) + authtype = AUTHCHAP; + } + + break; + } + x -= length; + o += length; + } + + if (!session[s].ip && authtype) + sess_local[s].lcp_authtype = authtype; + + switch (session[s].ppp.lcp) + { + case RequestSent: + initialise_restart_count(s, lcp); + change_state(s, lcp, AckReceived); + break; + + case AckReceived: + case Opened: + LOG(2, s, t, "LCP: ConfigAck in state %s? Sending ConfigReq\n", ppp_state(session[s].ppp.lcp)); + if (session[s].ppp.lcp == Opened) + lcp_restart(s); + + sendlcp(s, t); + change_state(s, lcp, RequestSent); + break; + + case AckSent: + lcp_open(s, t); + break; + + default: + LOG(2, s, t, "LCP: ignoring %s in state %s\n", ppp_code(*p), ppp_state(session[s].ppp.lcp)); + } + } + else if (*p == ConfigReq) + { + int x = l - 4; + uint8_t *o = (p + 4); + uint8_t *response = 0; + static uint8_t asyncmap[4] = { 0, 0, 0, 0 }; // all zero + static uint8_t authproto[5]; + + while (x > 2) + { + int type = o[0]; + int length = o[1]; + + if (length == 0 || type == 0 || x < length) break; + switch (type) + { + case 1: // Maximum-Receive-Unit + { + uint16_t mru = ntohs(*(uint16_t *)(o + 2)); + if (mru >= MINMTU) + { + session[s].mru = mru; + cluster_send_session(s); + break; + } + + LOG(3, s, t, " Remote requesting MRU of %u. Rejecting.\n", mru); + mru = htons(MRU); + q = ppp_conf_nak(s, b, sizeof(b), PPPLCP, &response, q, p, o, (uint8_t *) &mru, sizeof(mru)); + } + break; + + case 2: // Async-Control-Character-Map + if (!ntohl(*(uint32_t *)(o + 2))) // all bits zero is OK + break; + + LOG(3, s, t, " Remote requesting asyncmap. Rejecting.\n"); + q = ppp_conf_nak(s, b, sizeof(b), PPPLCP, &response, q, p, o, asyncmap, sizeof(asyncmap)); + break; + + case 3: // Authentication-Protocol + { + int proto = ntohs(*(uint16_t *)(o + 2)); + char proto_name[] = "0x0000"; + int alen; + + if (proto == PPPPAP) + { + if (config->radius_authtypes & AUTHPAP) + { + sess_local[s].lcp_authtype = AUTHPAP; + break; + } + + strcpy(proto_name, "PAP"); + } + else if (proto == PPPCHAP) + { + if (config->radius_authtypes & AUTHCHAP + && *(o + 4) == 5) // MD5 + { + sess_local[s].lcp_authtype = AUTHCHAP; + break; + } + + strcpy(proto_name, "CHAP"); + } + else + sprintf(proto_name, "%#4.4x", proto); + + LOG(3, s, t, " Remote requesting %s authentication. Rejecting.\n", proto_name); + + alen = add_lcp_auth(authproto, sizeof(authproto), config->radius_authprefer); + if (alen < 2) break; // paranoia + + q = ppp_conf_nak(s, b, sizeof(b), PPPLCP, &response, q, p, o, authproto + 2, alen - 2); + if (q && *response == ConfigNak && + config->radius_authtypes != config->radius_authprefer) + { + // alternate type + alen = add_lcp_auth(authproto, sizeof(authproto), config->radius_authtypes & ~config->radius_authprefer); + if (alen < 2) break; + q = ppp_conf_nak(s, b, sizeof(b), PPPLCP, &response, q, p, o, authproto + 2, alen - 2); + } + + break; + } + break; + + case 4: // Quality-Protocol + case 5: // Magic-Number + case 7: // Protocol-Field-Compression + case 8: // Address-And-Control-Field-Compression + break; + + default: // Reject any unknown options + LOG(3, s, t, " Rejecting unknown PPP LCP option %d\n", type); + q = ppp_conf_rej(s, b, sizeof(b), PPPLCP, &response, q, p, o); + } + x -= length; + o += length; + } + + if (response) + { + l = q - response; // LCP packet length + *((uint16_t *) (response + 2)) = htons(l); // update header + } + else + { + // Send packet back as ConfigAck + response = makeppp(b, sizeof(b), p, l, s, t, PPPLCP); + if (!response) return; + *response = ConfigAck; + } + + switch (session[s].ppp.lcp) + { + case Closed: + response = makeppp(b, sizeof(b), p, 2, s, t, PPPLCP); + if (!response) return; + *response = TerminateAck; + *((uint16_t *) (response + 2)) = htons(l = 4); + break; + + case Stopped: + initialise_restart_count(s, lcp); + sendlcp(s, t); + if (*response == ConfigAck) + change_state(s, lcp, AckSent); + else + change_state(s, lcp, RequestSent); + + break; + + case RequestSent: + if (*response == ConfigAck) + change_state(s, lcp, AckSent); + + break; + + case AckReceived: + if (*response == ConfigAck) + lcp_open(s, t); + + break; + + case Opened: + lcp_restart(s); + sendlcp(s, t); + /* fallthrough */ + + case AckSent: + if (*response == ConfigAck) + change_state(s, lcp, AckSent); + else + change_state(s, lcp, RequestSent); + + break; + + default: + LOG(2, s, t, "LCP: ignoring %s in state %s\n", ppp_code(*p), ppp_state(session[s].ppp.lcp)); + return; + } + + LOG(3, s, t, "LCP: send %s\n", ppp_code(*response)); + if (config->debug > 3) dumplcp(response, l); + + tunnelsend(b, l + (response - b), t); + } + else if (*p == ConfigNak || *p == ConfigRej) + { + int x = l - 4; + uint8_t *o = (p + 4); + int authtype = -1; + + while (x > 2) + { + int type = o[0]; + int length = o[1]; + + if (length == 0 || type == 0 || x < length) break; + switch (type) + { + case 1: // Maximum-Receive-Unit + if (*p == ConfigNak) + { + if (length < 4) break; + sess_local[s].ppp_mru = ntohs(*(uint16_t *)(o + 2)); + LOG(3, s, t, " Remote requested MRU of %u\n", sess_local[s].ppp_mru); + } + else + { + sess_local[s].ppp_mru = 0; + LOG(3, s, t, " Remote rejected MRU negotiation\n"); + } + + break; + + case 3: // Authentication-Protocol + if (authtype > 0) + break; + + if (*p == ConfigNak) + { + int proto; + + if (length < 4) break; + proto = ntohs(*(uint16_t *)(o + 2)); + + if (proto == PPPPAP) + { + authtype = config->radius_authtypes & AUTHPAP; + LOG(3, s, t, " Remote requested PAP authentication...%sing\n", + authtype ? "accept" : "reject"); + } + else if (proto == PPPCHAP && length > 4 && *(o + 4) == 5) + { + authtype = config->radius_authtypes & AUTHCHAP; + LOG(3, s, t, " Remote requested CHAP authentication...%sing\n", + authtype ? "accept" : "reject"); + } + else + { + LOG(3, s, t, " Rejecting unsupported authentication %#4x\n", + proto); + } + } + else + { + LOG(2, s, t, "LCP: remote rejected auth negotiation\n"); + authtype = 0; // shutdown + } + + break; + + case 5: // Magic-Number + session[s].magic = 0; + if (*p == ConfigNak) + { + if (length < 6) break; + session[s].magic = ntohl(*(uint32_t *)(o + 2)); + } + + if (session[s].magic) + LOG(3, s, t, " Remote requested magic-no %x\n", session[s].magic); + else + LOG(3, s, t, " Remote rejected magic-no\n"); + + cluster_send_session(s); + break; + + default: + LOG(2, s, t, "LCP: remote sent %s for type %u?\n", ppp_code(*p), type); + sessionshutdown(s, "Unable to negotiate LCP.", CDN_ADMIN_DISC, TERM_USER_ERROR); + return; + } + x -= length; + o += length; + } + + if (!authtype) + { + sessionshutdown(s, "Unsupported authentication.", CDN_ADMIN_DISC, TERM_USER_ERROR); + return; + } + + if (authtype > 0) + sess_local[s].lcp_authtype = authtype; + + switch (session[s].ppp.lcp) + { + case Closed: + case Stopped: + { + uint8_t *response = makeppp(b, sizeof(b), p, 2, s, t, PPPLCP); + if (!response) return; + *response = TerminateAck; + *((uint16_t *) (response + 2)) = htons(l = 4); + + LOG(3, s, t, "LCP: send %s\n", ppp_code(*response)); + if (config->debug > 3) dumplcp(response, l); + + tunnelsend(b, l + (response - b), t); + } + break; + + case RequestSent: + case AckSent: + initialise_restart_count(s, lcp); + sendlcp(s, t); + break; + + case AckReceived: + LOG(2, s, t, "LCP: ConfigNak in state %s? Sending ConfigReq\n", ppp_state(session[s].ppp.lcp)); + sendlcp(s, t); + break; + + case Opened: + lcp_restart(s); + sendlcp(s, t); + break; + + default: + LOG(2, s, t, "LCP: ignoring %s in state %s\n", ppp_code(*p), ppp_state(session[s].ppp.lcp)); + return; + } + } + else if (*p == TerminateReq) + { + switch (session[s].ppp.lcp) + { + case Closed: + case Stopped: + case Closing: + case Stopping: + case RequestSent: + case AckReceived: + case AckSent: + break; + + case Opened: + lcp_restart(s); + zero_restart_count(s, lcp); + change_state(s, lcp, Closing); + break; + + default: + LOG(2, s, t, "LCP: ignoring %s in state %s\n", ppp_code(*p), ppp_state(session[s].ppp.lcp)); + return; + } + + *p = TerminateAck; // send ack + q = makeppp(b, sizeof(b), p, l, s, t, PPPLCP); + if (!q) return; + + LOG(3, s, t, "LCP: send %s\n", ppp_code(*q)); + if (config->debug > 3) dumplcp(q, l); + + tunnelsend(b, l + (q - b), t); // send it + } + else if (*p == ProtocolRej) + { + uint16_t proto = 0; + + if (l > 4) + { + proto = *(p+4); + if (l > 5 && !(proto & 1)) + { + proto <<= 8; + proto |= *(p+5); + } + } + + if (proto == PPPIPV6CP) + { + LOG(3, s, t, "IPv6 rejected\n"); + change_state(s, ipv6cp, Closed); + } + else + { + LOG(3, s, t, "LCP protocol reject: 0x%04X\n", proto); + } + } + else if (*p == EchoReq) + { + *p = EchoReply; // reply + *(uint32_t *) (p + 4) = htonl(session[s].magic); // our magic number + q = makeppp(b, sizeof(b), p, l, s, t, PPPLCP); + if (!q) return; + + LOG(4, s, t, "LCP: send %s\n", ppp_code(*q)); + if (config->debug > 3) dumplcp(q, l); + + tunnelsend(b, l + (q - b), t); // send it + } + else if (*p == EchoReply) + { + // Ignore it, last_packet time is set earlier than this. + } + else if (*p != CodeRej) + { + ppp_code_rej(s, t, PPPLCP, "LCP", p, l, b, sizeof(b)); + } +} + +static void ipcp_open(sessionidt s, tunnelidt t) +{ + LOG(3, s, t, "IPCP: Opened, session is now active\n"); + + change_state(s, ipcp, Opened); + + if (!(session[s].walled_garden || session[s].flags & SESSION_STARTED)) + { + uint16_t r = radiusnew(s); + if (r) + { + radiussend(r, RADIUSSTART); // send radius start + + // don't send further Start records if IPCP is restarted + session[s].flags |= SESSION_STARTED; + cluster_send_session(s); + } + } + + // start IPv6 if configured and still in passive state + if (session[s].ppp.ipv6cp == Stopped) + { + sendipv6cp(s, t); + change_state(s, ipv6cp, RequestSent); + } +} + +// Process IPCP messages +void processipcp(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l) +{ + uint8_t b[MAXETHER]; + uint8_t *q = 0; + uint16_t hl; + + CSTAT(processipcp); + + LOG_HEX(5, "IPCP", p, l); + if (l < 4) + { + LOG(1, s, t, "Short IPCP %d bytes\n", l); + STAT(tunnel_rx_errors); + return ; + } + + if ((hl = ntohs(*(uint16_t *) (p + 2))) > l) + { + LOG(1, s, t, "Length mismatch IPCP %u/%u\n", hl, l); + STAT(tunnel_rx_errors); + return ; + } + l = hl; + + if (session[s].ppp.phase < Network) + { + LOG(2, s, t, "IPCP %s ignored in %s phase\n", ppp_code(*p), ppp_phase(session[s].ppp.phase)); + return; + } + + LOG(3, s, t, "IPCP: recv %s\n", ppp_code(*p)); + + if (*p == ConfigAck) + { + switch (session[s].ppp.ipcp) + { + case RequestSent: + initialise_restart_count(s, ipcp); + change_state(s, ipcp, AckReceived); + break; + + case AckReceived: + case Opened: + LOG(2, s, t, "IPCP: ConfigAck in state %s? Sending ConfigReq\n", ppp_state(session[s].ppp.ipcp)); + sendipcp(s, t); + change_state(s, ipcp, RequestSent); + break; + + case AckSent: + ipcp_open(s, t); + break; + + default: + LOG(2, s, t, "IPCP: ignoring %s in state %s\n", ppp_code(*p), ppp_state(session[s].ppp.ipcp)); + } + } + else if (*p == ConfigReq) + { + uint8_t *response = 0; + uint8_t *o = p + 4; + int length = l - 4; + int gotip = 0; + in_addr_t addr; + + while (length > 2) + { + if (!o[1] || o[1] > length) return; + + switch (*o) + { + case 3: // ip address + gotip++; // seen address + if (o[1] != 6) return; + + addr = htonl(session[s].ip); + if (memcmp(o + 2, &addr, (sizeof addr))) + { + uint8_t *oq = q; + q = ppp_conf_nak(s, b, sizeof(b), PPPIPCP, &response, q, p, o, (uint8_t *) &addr, sizeof(addr)); + if (!q || (q != oq && *response == ConfigRej)) + { + sessionshutdown(s, "Can't negotiate IPCP.", CDN_ADMIN_DISC, TERM_USER_ERROR); + return; + } + } + + break; + + case 129: // primary DNS + if (o[1] != 6) return; + + addr = htonl(session[s].dns1); + if (memcmp(o + 2, &addr, (sizeof addr))) + { + q = ppp_conf_nak(s, b, sizeof(b), PPPIPCP, &response, q, p, o, (uint8_t *) &addr, sizeof(addr)); + if (!q) return; + } + + break; + + case 131: // secondary DNS + if (o[1] != 6) return; + + addr = htonl(session[s].dns2); + if (memcmp(o + 2, &addr, sizeof(addr))) + { + q = ppp_conf_nak(s, b, sizeof(b), PPPIPCP, &response, q, p, o, (uint8_t *) &addr, sizeof(addr)); + if (!q) return; + } + + break; + + default: + LOG(2, s, t, " Rejecting PPP IPCP Option type %d\n", *o); + q = ppp_conf_rej(s, b, sizeof(b), PPPIPCP, &response, q, p, o); + if (!q) return; + } + + length -= o[1]; + o += o[1]; + } + + if (response) + { + l = q - response; // IPCP packet length + *((uint16_t *) (response + 2)) = htons(l); // update header + } + else if (gotip) + { + // Send packet back as ConfigAck + response = makeppp(b, sizeof(b), p, l, s, t, PPPIPCP); + if (!response) return; + *response = ConfigAck; + } + else + { + LOG(1, s, t, "No IP in IPCP request\n"); + STAT(tunnel_rx_errors); + return; + } + + switch (session[s].ppp.ipcp) + { + case Closed: + response = makeppp(b, sizeof(b), p, 2, s, t, PPPIPCP); + if (!response) return; + *response = TerminateAck; + *((uint16_t *) (response + 2)) = htons(l = 4); + break; + + case Stopped: + initialise_restart_count(s, ipcp); + sendipcp(s, t); + if (*response == ConfigAck) + change_state(s, ipcp, AckSent); + else + change_state(s, ipcp, RequestSent); + + break; + + case RequestSent: + if (*response == ConfigAck) + change_state(s, ipcp, AckSent); + + break; + + case AckReceived: + if (*response == ConfigAck) + ipcp_open(s, t); + + break; + + case Opened: + initialise_restart_count(s, ipcp); + sendipcp(s, t); + /* fallthrough */ + + case AckSent: + if (*response == ConfigAck) + change_state(s, ipcp, AckSent); + else + change_state(s, ipcp, RequestSent); + + break; + + default: + LOG(2, s, t, "IPCP: ignoring %s in state %s\n", ppp_code(*p), ppp_state(session[s].ppp.ipcp)); + return; + } + + LOG(3, s, t, "IPCP: send %s\n", ppp_code(*response)); + tunnelsend(b, l + (response - b), t); + } + else if (*p == TerminateReq) + { + switch (session[s].ppp.ipcp) + { + case Closed: + case Stopped: + case Closing: + case Stopping: + case RequestSent: + case AckReceived: + case AckSent: + break; + + case Opened: + zero_restart_count(s, ipcp); + change_state(s, ipcp, Closing); + break; + + default: + LOG(2, s, t, "IPCP: ignoring %s in state %s\n", ppp_code(*p), ppp_state(session[s].ppp.ipcp)); + return; + } + + *p = TerminateAck; // send ack + q = makeppp(b, sizeof(b), p, l, s, t, PPPIPCP); + if (!q) return; + + LOG(3, s, t, "IPCP: send %s\n", ppp_code(*q)); + tunnelsend(b, l + (q - b), t); // send it + } + else if (*p != CodeRej) + { + ppp_code_rej(s, t, PPPIPCP, "IPCP", p, l, b, sizeof(b)); + } +} + +static void ipv6cp_open(sessionidt s, tunnelidt t) +{ + LOG(3, s, t, "IPV6CP: Opened\n"); + + change_state(s, ipv6cp, Opened); + if (session[s].ipv6prefixlen) + route6set(s, session[s].ipv6route, session[s].ipv6prefixlen, 1); + + // Send an initial RA (TODO: Should we send these regularly?) + send_ipv6_ra(s, t, NULL); +} + +// Process IPV6CP messages +void processipv6cp(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l) +{ + uint8_t b[MAXETHER]; + uint8_t *q = 0; + uint16_t hl; + + CSTAT(processipv6cp); + + LOG_HEX(5, "IPV6CP", p, l); + if (l < 4) + { + LOG(1, s, t, "Short IPV6CP %d bytes\n", l); + STAT(tunnel_rx_errors); + return ; + } + + if ((hl = ntohs(*(uint16_t *) (p + 2))) > l) + { + LOG(1, s, t, "Length mismatch IPV6CP %u/%u\n", hl, l); + STAT(tunnel_rx_errors); + return ; + } + l = hl; + + if (session[s].ppp.phase < Network) + { + LOG(2, s, t, "IPV6CP %s ignored in %s phase\n", ppp_code(*p), ppp_phase(session[s].ppp.phase)); + return; + } + + LOG(3, s, t, "IPV6CP: recv %s\n", ppp_code(*p)); + + if (!session[s].ip) + { + LOG(3, s, t, "IPV6CP: no IPv4 address (IPCP in state %s)\n", ppp_state(session[s].ppp.ipcp)); + return; // need IPCP to complete... + } + + if (*p == ConfigAck) + { + switch (session[s].ppp.ipv6cp) + { + case RequestSent: + initialise_restart_count(s, ipv6cp); + change_state(s, ipv6cp, AckReceived); + break; + + case AckReceived: + case Opened: + LOG(2, s, t, "IPV6CP: ConfigAck in state %s? Sending ConfigReq\n", ppp_state(session[s].ppp.ipv6cp)); + sendipv6cp(s, t); + change_state(s, ipv6cp, RequestSent); + break; + + case AckSent: + ipv6cp_open(s, t); + break; + + default: + LOG(2, s, t, "IPV6CP: ignoring %s in state %s\n", ppp_code(*p), ppp_state(session[s].ppp.ipv6cp)); + } + } + else if (*p == ConfigReq) + { + uint8_t *response = 0; + uint8_t *o = p + 4; + int length = l - 4; + int gotip = 0; + uint8_t ident[8]; + + while (length > 2) + { + if (!o[1] || o[1] > length) return; + + switch (*o) + { + case 1: // interface identifier + gotip++; // seen address + if (o[1] != 10) return; + + *(uint32_t *) ident = htonl(session[s].ip); + *(uint32_t *) (ident + 4) = 0; + + if (memcmp(o + 2, ident, sizeof(ident))) + { + q = ppp_conf_nak(s, b, sizeof(b), PPPIPV6CP, &response, q, p, o, ident, sizeof(ident)); + if (!q) return; + } + + break; + + default: + LOG(2, s, t, " Rejecting PPP IPV6CP Option type %d\n", *o); + q = ppp_conf_rej(s, b, sizeof(b), PPPIPV6CP, &response, q, p, o); + if (!q) return; + } + + length -= o[1]; + o += o[1]; + } + + if (response) + { + l = q - response; // IPV6CP packet length + *((uint16_t *) (response + 2)) = htons(l); // update header + } + else if (gotip) + { + // Send packet back as ConfigAck + response = makeppp(b, sizeof(b), p, l, s, t, PPPIPV6CP); + if (!response) return; + *response = ConfigAck; + } + else + { + LOG(1, s, t, "No interface identifier in IPV6CP request\n"); + STAT(tunnel_rx_errors); + return; + } + + switch (session[s].ppp.ipv6cp) + { + case Closed: + response = makeppp(b, sizeof(b), p, 2, s, t, PPPIPV6CP); + if (!response) return; + *response = TerminateAck; + *((uint16_t *) (response + 2)) = htons(l = 4); + break; + + case Stopped: + initialise_restart_count(s, ipv6cp); + sendipv6cp(s, t); + if (*response == ConfigAck) + change_state(s, ipv6cp, AckSent); + else + change_state(s, ipv6cp, RequestSent); + + break; + + case RequestSent: + if (*response == ConfigAck) + change_state(s, ipv6cp, AckSent); + + break; + + case AckReceived: + if (*response == ConfigAck) + ipv6cp_open(s, t); + + break; + + case Opened: + initialise_restart_count(s, ipv6cp); + sendipv6cp(s, t); + /* fallthrough */ + + case AckSent: + if (*response == ConfigAck) + change_state(s, ipv6cp, AckSent); + else + change_state(s, ipv6cp, RequestSent); + + break; + + default: + LOG(2, s, t, "IPV6CP: ignoring %s in state %s\n", ppp_code(*p), ppp_state(session[s].ppp.ipv6cp)); + return; + } + + LOG(3, s, t, "IPV6CP: send %s\n", ppp_code(*response)); + tunnelsend(b, l + (response - b), t); + } + else if (*p == TerminateReq) + { + switch (session[s].ppp.ipv6cp) + { + case Closed: + case Stopped: + case Closing: + case Stopping: + case RequestSent: + case AckReceived: + case AckSent: + break; + + case Opened: + zero_restart_count(s, ipv6cp); + change_state(s, ipv6cp, Closing); + break; + + default: + LOG(2, s, t, "IPV6CP: ignoring %s in state %s\n", ppp_code(*p), ppp_state(session[s].ppp.ipv6cp)); + return; + } + + *p = TerminateAck; // send ack + q = makeppp(b, sizeof(b), p, l, s, t, PPPIPV6CP); + if (!q) return; + + LOG(3, s, t, "IPV6CP: send %s\n", ppp_code(*q)); + tunnelsend(b, l + (q - b), t); // send it + } + else if (*p != CodeRej) + { + ppp_code_rej(s, t, PPPIPV6CP, "IPV6CP", p, l, b, sizeof(b)); + } +} + +// process IP packet received +// +// This MUST be called with at least 4 byte behind 'p'. +// (i.e. this routine writes to p[-4]). +void processipin(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l) +{ + in_addr_t ip; + + CSTAT(processipin); + + LOG_HEX(5, "IP", p, l); + + if (l < 20) + { + LOG(1, s, t, "IP packet too short %d\n", l); + STAT(tunnel_rx_errors); + return ; + } + + ip = ntohl(*(uint32_t *)(p + 12)); + + if (l > MAXETHER) + { + LOG(1, s, t, "IP packet too long %d\n", l); + STAT(tunnel_rx_errors); + return ; + } + + if (session[s].ppp.phase != Network || session[s].ppp.ipcp != Opened) + return; + + // no spoof (do sessionbyip to handled statically routed subnets) + if (ip != session[s].ip && sessionbyip(htonl(ip)) != s) + { + LOG(5, s, t, "Dropping packet with spoofed IP %s\n", fmtaddr(htonl(ip), 0)); + return; + } + + // run access-list if any + if (session[s].filter_in && !ip_filter(p, l, session[s].filter_in - 1)) + return; + + // adjust MSS on SYN and SYN,ACK packets with options + if ((ntohs(*(uint16_t *) (p + 6)) & 0x1fff) == 0 && p[9] == IPPROTO_TCP) // first tcp fragment + { + int ihl = (p[0] & 0xf) * 4; // length of IP header + if (l >= ihl + 20 && (p[ihl + 13] & TCP_FLAG_SYN) && ((p[ihl + 12] >> 4) > 5)) + adjust_tcp_mss(s, t, p, l, p + ihl); + } + + // Add on the tun header + p -= 4; + *(uint32_t *) p = htonl(PKTIP); + l += 4; + + // Are we throttled and a slave? + if (session[s].tbf_in && !config->cluster_iam_master) { + // Pass it to the master for handling. + master_throttle_packet(session[s].tbf_in, p, l); + return; + } + + // Are we throttled and a master?? + if (session[s].tbf_in && config->cluster_iam_master) { + // Actually handle the throttled packets. + tbf_queue_packet(session[s].tbf_in, p, l); + return; + } + + // send to ethernet + if (tun_write(p, l) < 0) + { + STAT(tun_tx_errors); + LOG(0, s, t, "Error writing %d bytes to TUN device: %s (tunfd=%d, p=%p)\n", + l, strerror(errno), tunfd, p); + + return; + } + + p += 4; + l -= 4; + + if (session[s].snoop_ip && session[s].snoop_port) + { + // Snooping this session + snoop_send_packet(p, l, session[s].snoop_ip, session[s].snoop_port); + } + + increment_counter(&session[s].cin, &session[s].cin_wrap, l); + session[s].cin_delta += l; + session[s].pin++; + + sess_local[s].cin += l; + sess_local[s].pin++; + + eth_tx += l; + + STAT(tun_tx_packets); + INC_STAT(tun_tx_bytes, l); +} + +// process IPv6 packet received +// +// This MUST be called with at least 4 byte behind 'p'. +// (i.e. this routine writes to p[-4]). +void processipv6in(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l) +{ + struct in6_addr ip; + in_addr_t ipv4; + + CSTAT(processipv6in); + + LOG_HEX(5, "IPv6", p, l); + + ip = *(struct in6_addr *) (p + 8); + ipv4 = ntohl(*(uint32_t *)(p + 16)); + + if (l > MAXETHER) + { + LOG(1, s, t, "IP packet too long %d\n", l); + STAT(tunnel_rx_errors); + return ; + } + + if (session[s].ppp.phase != Network || session[s].ppp.ipv6cp != Opened) + return; + + // no spoof + if (ipv4 != session[s].ip && memcmp(&config->ipv6_prefix, &ip, 8) && sessionbyipv6(ip) != s) + { + char str[INET6_ADDRSTRLEN]; + LOG(5, s, t, "Dropping packet with spoofed IP %s\n", + inet_ntop(AF_INET6, &ip, str, INET6_ADDRSTRLEN)); + return; + } + + // Check if it's a Router Solicition message. + if (*(p + 6) == 58 && *(p + 7) == 255 && *(p + 24) == 0xFF && *(p + 25) == 2 && + *(uint32_t *)(p + 26) == 0 && *(uint32_t *)(p + 30) == 0 && + *(uint32_t *)(p + 34) == 0 && + *(p + 38) == 0 && *(p + 39) == 2 && *(p + 40) == 133) { + LOG(3, s, t, "Got IPv6 RS\n"); + send_ipv6_ra(s, t, &ip); + return; + } + + // Add on the tun header + p -= 4; + *(uint32_t *) p = htonl(PKTIPV6); + l += 4; + + // Are we throttled and a slave? + if (session[s].tbf_in && !config->cluster_iam_master) { + // Pass it to the master for handling. + master_throttle_packet(session[s].tbf_in, p, l); + return; + } + + // Are we throttled and a master?? + if (session[s].tbf_in && config->cluster_iam_master) { + // Actually handle the throttled packets. + tbf_queue_packet(session[s].tbf_in, p, l); + return; + } + + // send to ethernet + if (tun_write(p, l) < 0) + { + STAT(tun_tx_errors); + LOG(0, s, t, "Error writing %d bytes to TUN device: %s (tunfd=%d, p=%p)\n", + l, strerror(errno), tunfd, p); + + return; + } + + p += 4; + l -= 4; + + if (session[s].snoop_ip && session[s].snoop_port) + { + // Snooping this session + snoop_send_packet(p, l, session[s].snoop_ip, session[s].snoop_port); + } + + increment_counter(&session[s].cin, &session[s].cin_wrap, l); + session[s].cin_delta += l; + session[s].pin++; + + sess_local[s].cin += l; + sess_local[s].pin++; + + eth_tx += l; + + STAT(tun_tx_packets); + INC_STAT(tun_tx_bytes, l); +} + +// +// Helper routine for the TBF filters. +// Used to send queued data in from the user. +// +void send_ipin(sessionidt s, uint8_t *buf, int len) +{ + LOG_HEX(5, "IP in throttled", buf, len); + + if (write(tunfd, buf, len) < 0) + { + STAT(tun_tx_errors); + LOG(0, 0, 0, "Error writing %d bytes to TUN device: %s (tunfd=%d, p=%p)\n", + len, strerror(errno), tunfd, buf); + + return; + } + + buf += 4; + len -= 4; + + if (session[s].snoop_ip && session[s].snoop_port) + { + // Snooping this session + snoop_send_packet(buf, len, session[s].snoop_ip, session[s].snoop_port); + } + + // Increment packet counters + increment_counter(&session[s].cin, &session[s].cin_wrap, len); + session[s].cin_delta += len; + session[s].pin++; + + sess_local[s].cin += len; + sess_local[s].pin++; + + eth_tx += len; + + STAT(tun_tx_packets); + INC_STAT(tun_tx_bytes, len - 4); +} + + +// Process CCP messages +void processccp(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l) +{ + uint8_t b[MAXETHER]; + uint8_t *q; + + CSTAT(processccp); + + LOG_HEX(5, "CCP", p, l); + + if (session[s].ppp.phase < Network) + { + LOG(2, s, t, "CCP %s ignored in %s phase\n", ppp_code(*p), ppp_phase(session[s].ppp.phase)); + return; + } + + if (l < 1) + { + LOG(1, s, t, "Short CCP packet\n"); + STAT(tunnel_rx_errors); + } + + LOG(3, s, t, "CCP: recv %s\n", ppp_code(*p)); + if (*p == ConfigAck) + { + switch (session[s].ppp.ccp) + { + case RequestSent: + initialise_restart_count(s, ccp); + change_state(s, ccp, AckReceived); + break; + + case AckReceived: + case Opened: + LOG(2, s, t, "CCP: ConfigAck in state %s? Sending ConfigReq\n", ppp_state(session[s].ppp.ccp)); + sendccp(s, t); + change_state(s, ccp, RequestSent); + break; + + case AckSent: + LOG(3, s, t, "CCP: Opened\n"); + change_state(s, ccp, Opened); + break; + + default: + LOG(2, s, t, "CCP: ignoring %s in state %s\n", ppp_code(*p), ppp_state(session[s].ppp.ccp)); + } + } + else if (*p == ConfigReq) + { + if (l < 6) // accept no compression + *p = ConfigAck; + else // compression requested--reject + *p = ConfigRej; + + q = makeppp(b, sizeof(b), p, l, s, t, PPPCCP); + if (!q) return; + + switch (session[s].ppp.ccp) + { + case Closed: + q = makeppp(b, sizeof(b), p, 2, s, t, PPPCCP); + if (!q) return; + *q = TerminateAck; + *((uint16_t *) (q + 2)) = htons(l = 4); + break; + + case Stopped: + initialise_restart_count(s, ccp); + sendccp(s, t); + if (*q == ConfigAck) + change_state(s, ccp, AckSent); + else + change_state(s, ccp, RequestSent); + + break; + + case RequestSent: + if (*q == ConfigAck) + change_state(s, ccp, AckSent); + + break; + + case AckReceived: + if (*q == ConfigAck) + change_state(s, ccp, Opened); + + break; + + case Opened: + initialise_restart_count(s, ccp); + sendccp(s, t); + /* fallthrough */ + + case AckSent: + if (*q == ConfigAck) + change_state(s, ccp, AckSent); + else + change_state(s, ccp, RequestSent); + + break; + + default: + LOG(2, s, t, "CCP: ignoring %s in state %s\n", ppp_code(*p), ppp_state(session[s].ppp.ccp)); + return; + } + + LOG(3, s, t, "CCP: send %s\n", ppp_code(*q)); + tunnelsend(b, l + (q - b), t); + } + else if (*p == TerminateReq) + { + *p = TerminateAck; + q = makeppp(b, sizeof(b), p, l, s, t, PPPCCP); + if (!q) return; + LOG(3, s, t, "CCP: send %s\n", ppp_code(*q)); + tunnelsend(b, l + (q - b), t); + change_state(s, ccp, Stopped); + } + else if (*p != CodeRej) + { + ppp_code_rej(s, t, PPPCCP, "CCP", p, l, b, sizeof(b)); + } +} + +// send a CHAP challenge +void sendchap(sessionidt s, tunnelidt t) +{ + uint8_t b[MAXETHER]; + uint16_t r; + uint8_t *q; + + CSTAT(sendchap); + + r = radiusnew(s); + if (!r) + { + LOG(1, s, t, "No RADIUS to send challenge\n"); + STAT(tunnel_tx_errors); + return; + } + + LOG(1, s, t, "Send CHAP challenge\n"); + + radius[r].chap = 1; // CHAP not PAP + radius[r].id++; + if (radius[r].state != RADIUSCHAP) + radius[r].try = 0; + + radius[r].state = RADIUSCHAP; + radius[r].retry = backoff(radius[r].try++); + if (radius[r].try > 5) + { + sessionshutdown(s, "CHAP timeout.", CDN_ADMIN_DISC, TERM_REAUTHENTICATION_FAILURE); + STAT(tunnel_tx_errors); + return ; + } + q = makeppp(b, sizeof(b), 0, 0, s, t, PPPCHAP); + if (!q) return; + + *q = 1; // challenge + q[1] = radius[r].id; // ID + q[4] = 16; // value size (size of challenge) + memcpy(q + 5, radius[r].auth, 16); // challenge + strcpy((char *) q + 21, hostname); // our name + *(uint16_t *) (q + 2) = htons(strlen(hostname) + 21); // length + tunnelsend(b, strlen(hostname) + 21 + (q - b), t); // send it +} + +// fill in a L2TP message with a PPP frame, +// returns start of PPP frame +uint8_t *makeppp(uint8_t *b, int size, uint8_t *p, int l, sessionidt s, tunnelidt t, uint16_t mtype) +{ + if (size < 12) // Need more space than this!! + { + LOG(0, s, t, "makeppp buffer too small for L2TP header (size=%d)\n", size); + return NULL; + } + + *(uint16_t *) (b + 0) = htons(0x0002); // L2TP with no options + *(uint16_t *) (b + 2) = htons(tunnel[t].far); // tunnel + *(uint16_t *) (b + 4) = htons(session[s].far); // session + b += 6; + if (mtype == PPPLCP || !(session[s].flags & SESSION_ACFC)) + { + *(uint16_t *) b = htons(0xFF03); // HDLC header + b += 2; + } + if (mtype < 0x100 && session[s].flags & SESSION_PFC) + *b++ = mtype; + else + { + *(uint16_t *) b = htons(mtype); + b += 2; + } + + if (l + 12 > size) + { + LOG(2, s, t, "makeppp would overflow buffer (size=%d, header+payload=%d)\n", size, l + 12); + return NULL; + } + + if (p && l) + memcpy(b, p, l); + + return b; +} + +static int add_lcp_auth(uint8_t *b, int size, int authtype) +{ + int len = 0; + if ((authtype == AUTHCHAP && size < 5) || size < 4) + return 0; + + *b++ = 3; // Authentication-Protocol + if (authtype == AUTHCHAP) + { + len = *b++ = 5; // length + *(uint16_t *) b = htons(PPPCHAP); b += 2; + *b++ = 5; // MD5 + } + else if (authtype == AUTHPAP) + { + len = *b++ = 4; // length + *(uint16_t *) b = htons(PPPPAP); b += 2; + } + else + { + LOG(0, 0, 0, "add_lcp_auth called with unsupported auth type %d\n", authtype); + } + + return len; +} + +// Send LCP ConfigReq for MRU, authentication type and magic no +void sendlcp(sessionidt s, tunnelidt t) +{ + uint8_t b[500], *q, *l; + int authtype = sess_local[s].lcp_authtype; + + if (!(q = makeppp(b, sizeof(b), NULL, 0, s, t, PPPLCP))) + return; + + LOG(3, s, t, "LCP: send ConfigReq%s%s%s\n", + authtype ? " (" : "", + authtype ? (authtype == AUTHCHAP ? "CHAP" : "PAP") : "", + authtype ? ")" : ""); + + l = q; + *l++ = ConfigReq; + *l++ = ++sess_local[s].lcp_ident; // ID + + l += 2; //Save space for length + + if (sess_local[s].ppp_mru) + { + *l++ = 1; *l++ = 4; // Maximum-Receive-Unit (length 4) + *(uint16_t *) l = htons(sess_local[s].ppp_mru); l += 2; + } + + if (authtype) + l += add_lcp_auth(l, sizeof(b) - (l - b), authtype); + + if (session[s].magic) + { + *l++ = 5; *l++ = 6; // Magic-Number (length 6) + *(uint32_t *) l = htonl(session[s].magic); + l += 4; + } + + *(uint16_t *)(q + 2) = htons(l - q); // Length + + LOG_HEX(5, "PPPLCP", q, l - q); + if (config->debug > 3) dumplcp(q, l - q); + + tunnelsend(b, (l - b), t); + restart_timer(s, lcp); +} + +// Send CCP request for no compression +void sendccp(sessionidt s, tunnelidt t) +{ + uint8_t b[500], *q; + + if (!(q = makeppp(b, sizeof(b), NULL, 0, s, t, PPPCCP))) + return; + + LOG(3, s, t, "CCP: send ConfigReq (no compression)\n"); + + *q = ConfigReq; + *(q + 1) = ++sess_local[s].lcp_ident; // ID + *(uint16_t *)(q + 2) = htons(4); // Length + + LOG_HEX(5, "PPPCCP", q, 4); + tunnelsend(b, (q - b) + 4 , t); + restart_timer(s, ccp); +} + +// Reject unknown/unconfigured protocols +void protoreject(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l, uint16_t proto) +{ + + uint8_t buf[MAXETHER]; + uint8_t *q; + int mru = session[s].mru; + if (mru < MINMTU) mru = MINMTU; + if (mru > sizeof(buf)) mru = sizeof(buf); + + l += 6; + if (l > mru) l = mru; + + q = makeppp(buf, sizeof(buf), 0, 0, s, t, PPPLCP); + if (!q) return; + + *q = ProtocolRej; + *(q + 1) = ++sess_local[s].lcp_ident; + *(uint16_t *)(q + 2) = htons(l); + *(uint16_t *)(q + 4) = htons(proto); + memcpy(q + 6, p, l - 6); + + if (proto == PPPIPV6CP) + LOG(3, s, t, "LCP: send ProtocolRej (IPV6CP: not configured)\n"); + else + LOG(2, s, t, "LCP: sent ProtocolRej (0x%04X: unsupported)\n", proto); + + tunnelsend(buf, l + (q - buf), t); +} diff --git a/radius.c b/radius.c new file mode 100644 index 0000000..8a39894 --- /dev/null +++ b/radius.c @@ -0,0 +1,1093 @@ +// L2TPNS Radius Stuff + +char const *cvs_id_radius = "$Id: radius.c,v 1.49.2.2 2006/08/02 14:17:20 bodea Exp $"; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "md5.h" +#include "constants.h" +#include "l2tpns.h" +#include "plugin.h" +#include "util.h" +#include "cluster.h" + +extern radiust *radius; +extern sessiont *session; +extern tunnelt *tunnel; +extern configt *config; +extern int *radfds; +extern ip_filtert *ip_filters; + +static const hasht zero; + +static void calc_auth(const void *buf, size_t len, const uint8_t *in, uint8_t *out) +{ + MD5_CTX ctx; + + MD5_Init(&ctx); + MD5_Update(&ctx, (void *)buf, 4); // code, id, length + MD5_Update(&ctx, (void *)in, 16); // auth + MD5_Update(&ctx, (void *)(buf + 20), len - 20); + MD5_Update(&ctx, config->radiussecret, strlen(config->radiussecret)); + MD5_Final(out, &ctx); +} + +// Set up socket for radius requests +void initrad(void) +{ + int i; + LOG(3, 0, 0, "Creating %d sockets for RADIUS queries\n", RADIUS_FDS); + radfds = calloc(sizeof(int), RADIUS_FDS); + for (i = 0; i < RADIUS_FDS; i++) + { + int flags; + radfds[i] = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + flags = fcntl(radfds[i], F_GETFL, 0); + fcntl(radfds[i], F_SETFL, flags | O_NONBLOCK); + } +} + +void radiusclear(uint16_t r, sessionidt s) +{ + if (s) sess_local[s].radius = 0; + memset(&radius[r], 0, sizeof(radius[r])); // radius[r].state = RADIUSNULL; +} + +static uint16_t get_free_radius() +{ + int count; + static uint32_t next_radius_id = 0; + + for (count = MAXRADIUS; count > 0; --count) + { + ++next_radius_id; // Find the next ID to check. + if (next_radius_id >= MAXRADIUS) + next_radius_id = 1; + + if (radius[next_radius_id].state == RADIUSNULL) + { + return next_radius_id; + } + } + + LOG(0, 0, 0, "Can't find a free radius session! This is very bad!\n"); + return 0; +} + +uint16_t radiusnew(sessionidt s) +{ + uint16_t r = sess_local[s].radius; + + /* re-use */ + if (r) + { + LOG(3, s, session[s].tunnel, "Re-used radius %d\n", r); + return r; + } + + if (!(r = get_free_radius())) + { + LOG(1, s, session[s].tunnel, "No free RADIUS sessions\n"); + STAT(radius_overflow); + return 0; + }; + + memset(&radius[r], 0, sizeof(radius[r])); + sess_local[s].radius = r; + radius[r].session = s; + radius[r].state = RADIUSWAIT; + radius[r].retry = TIME + 1200; // Wait at least 120 seconds to re-claim this. + + random_data(radius[r].auth, sizeof(radius[r].auth)); + + LOG(3, s, session[s].tunnel, "Allocated radius %d\n", r); + return r; +} + +// Send a RADIUS request +void radiussend(uint16_t r, uint8_t state) +{ + struct sockaddr_in addr; + uint8_t b[4096]; // RADIUS packet + char pass[129]; + int pl; + uint8_t *p; + sessionidt s; + + CSTAT(radiussend); + + s = radius[r].session; + if (!config->numradiusservers) + { + LOG(0, s, session[s].tunnel, "No RADIUS servers\n"); + return; + } + if (!*config->radiussecret) + { + LOG(0, s, session[s].tunnel, "No RADIUS secret\n"); + return; + } + + if (state != RADIUSAUTH && !config->radius_accounting) + { + // Radius accounting is turned off + radiusclear(r, s); + return; + } + + if (radius[r].state != state) + radius[r].try = 0; + + radius[r].state = state; + radius[r].retry = backoff(radius[r].try++) + 20; // 3s, 4s, 6s, 10s... + LOG(4, s, session[s].tunnel, "Send RADIUS id %d sock %d state %s try %d\n", + r >> RADIUS_SHIFT, r & RADIUS_MASK, + radius_state(radius[r].state), radius[r].try); + + if (radius[r].try > config->numradiusservers * 2) + { + if (s) + { + if (state == RADIUSAUTH) + sessionshutdown(s, "RADIUS timeout.", CDN_ADMIN_DISC, TERM_REAUTHENTICATION_FAILURE); + else + { + LOG(1, s, session[s].tunnel, "RADIUS timeout, but in state %s so don't timeout session\n", + radius_state(state)); + radiusclear(r, s); + } + STAT(radius_timeout); + } + else + { + STAT(radius_retries); + radius[r].state = RADIUSWAIT; + radius[r].retry = 100; + } + return; + } + // contruct RADIUS access request + switch (state) + { + case RADIUSAUTH: + b[0] = AccessRequest; // access request + break; + case RADIUSSTART: + case RADIUSSTOP: + case RADIUSINTERIM: + b[0] = AccountingRequest; // accounting request + break; + default: + LOG(0, 0, 0, "Unknown radius state %d\n", state); + } + b[1] = r >> RADIUS_SHIFT; // identifier + memcpy(b + 4, radius[r].auth, 16); + p = b + 20; + if (s) + { + *p = 1; // user name + p[1] = strlen(session[s].user) + 2; + strcpy((char *) p + 2, session[s].user); + p += p[1]; + } + if (state == RADIUSAUTH) + { + if (radius[r].chap) + { + *p = 3; // CHAP password + p[1] = 19; // length + p[2] = radius[r].id; // ID + memcpy(p + 3, radius[r].pass, 16); // response from CHAP request + p += p[1]; + *p = 60; // CHAP Challenge + p[1] = 18; // length + memcpy(p + 2, radius[r].auth, 16); + p += p[1]; + } + else + { + strcpy(pass, radius[r].pass); + pl = strlen(pass); + while (pl & 15) + pass[pl++] = 0; // pad + if (pl) + { // encrypt + hasht hash; + int p = 0; + while (p < pl) + { + MD5_CTX ctx; + MD5_Init(&ctx); + MD5_Update(&ctx, config->radiussecret, strlen(config->radiussecret)); + if (p) + MD5_Update(&ctx, pass + p - 16, 16); + else + MD5_Update(&ctx, radius[r].auth, 16); + MD5_Final(hash, &ctx); + do + { + pass[p] ^= hash[p & 15]; + p++; + } + while (p & 15); + } + } + *p = 2; // password + p[1] = pl + 2; + if (pl) + memcpy(p + 2, pass, pl); + p += p[1]; + } + } + else // accounting + { + *p = 40; // accounting type + p[1] = 6; + *(uint32_t *) (p + 2) = htonl(state - RADIUSSTART + 1); // start=1, stop=2, interim=3 + p += p[1]; + if (s) + { + *p = 44; // session ID + p[1] = 18; + sprintf((char *) p + 2, "%08X%08X", session[s].unique_id, session[s].opened); + p += p[1]; + if (state == RADIUSSTART) + { // start + *p = 41; // delay + p[1] = 6; + *(uint32_t *) (p + 2) = htonl(time(NULL) - session[s].opened); + p += p[1]; + sess_local[s].last_interim = time_now; // Setup "first" Interim + } + else + { // stop, interim + *p = 42; // input octets + p[1] = 6; + *(uint32_t *) (p + 2) = htonl(session[s].cin); + p += p[1]; + + *p = 43; // output octets + p[1] = 6; + *(uint32_t *) (p + 2) = htonl(session[s].cout); + p += p[1]; + + *p = 46; // session time + p[1] = 6; + *(uint32_t *) (p + 2) = htonl(time(NULL) - session[s].opened); + p += p[1]; + + *p = 47; // input packets + p[1] = 6; + *(uint32_t *) (p + 2) = htonl(session[s].pin); + p += p[1]; + + *p = 48; // output packets + p[1] = 6; + *(uint32_t *) (p + 2) = htonl(session[s].pout); + p += p[1]; + + *p = 52; // input gigawords + p[1] = 6; + *(uint32_t *) (p + 2) = htonl(session[s].cin_wrap); + p += p[1]; + + *p = 53; // output gigawords + p[1] = 6; + *(uint32_t *) (p + 2) = htonl(session[s].cout_wrap); + p += p[1]; + + if (state == RADIUSSTOP && radius[r].term_cause) + { + *p = 49; // acct-terminate-cause + p[1] = 6; + *(uint32_t *) (p + 2) = htonl(radius[r].term_cause); + p += p[1]; + + if (radius[r].term_msg) + { + *p = 26; // vendor-specific + *(uint32_t *) (p + 2) = htonl(9); // Cisco + p[6] = 1; // Cisco-AVPair + p[7] = 2 + sprintf((char *) p + 8, "disc-cause-ext=%s", radius[r].term_msg); + p[1] = p[7] + 6; + p += p[1]; + } + } + } + + { + struct param_radius_account acct = { &tunnel[session[s].tunnel], &session[s], &p }; + run_plugins(PLUGIN_RADIUS_ACCOUNT, &acct); + } + } + } + if (s) + { + *p = 5; // NAS-Port + p[1] = 6; + *(uint32_t *) (p + 2) = htonl(s); + p += p[1]; + + *p = 6; // Service-Type + p[1] = 6; + *(uint32_t *) (p + 2) = htonl(2); // Framed-User + p += p[1]; + + *p = 7; // Framed-Protocol + p[1] = 6; + *(uint32_t *) (p + 2) = htonl(1); // PPP + p += p[1]; + } + if (s && session[s].ip) + { + *p = 8; // Framed-IP-Address + p[1] = 6; + *(uint32_t *) (p + 2) = htonl(session[s].ip); + p += p[1]; + } + if (s && session[s].route[0].ip) + { + int r; + for (r = 0; s && r < MAXROUTE && session[s].route[r].ip; r++) + { + int width = 32; + if (session[s].route[r].mask) + { + int mask = session[s].route[r].mask; + while (!(mask & 1)) + { + width--; + mask >>= 1; + } + } + + *p = 22; // Framed-Route + p[1] = sprintf((char *) p + 2, "%s/%d %s 1", + fmtaddr(htonl(session[s].route[r].ip), 0), + width, fmtaddr(htonl(session[s].ip), 1)) + 2; + + p += p[1]; + } + } + if (*session[s].called) + { + *p = 30; // called + p[1] = strlen(session[s].called) + 2; + strcpy((char *) p + 2, session[s].called); + p += p[1]; + } + if (*session[s].calling) + { + *p = 31; // calling + p[1] = strlen(session[s].calling) + 2; + strcpy((char *) p + 2, session[s].calling); + p += p[1]; + } + // NAS-IP-Address + *p = 4; + p[1] = 6; + *(uint32_t *)(p + 2) = config->bind_address; + p += p[1]; + + // All AVpairs added + *(uint16_t *) (b + 2) = htons(p - b); + if (state != RADIUSAUTH) + { + // Build auth for accounting packet + calc_auth(b, p - b, zero, b + 4); + memcpy(radius[r].auth, b + 4, 16); + } + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + *(uint32_t *) & addr.sin_addr = config->radiusserver[(radius[r].try - 1) % config->numradiusservers]; + { + // get radius port + uint16_t port = config->radiusport[(radius[r].try - 1) % config->numradiusservers]; + // assume RADIUS accounting port is the authentication port +1 + addr.sin_port = htons((state == RADIUSAUTH) ? port : port+1); + } + + LOG_HEX(5, "RADIUS Send", b, (p - b)); + sendto(radfds[r & RADIUS_MASK], b, p - b, 0, (void *) &addr, sizeof(addr)); +} + +static void handle_avpair(sessionidt s, uint8_t *avp, int len) +{ + uint8_t *key = avp; + uint8_t *value = memchr(avp, '=', len); + uint8_t tmp[2048] = ""; + + if (value) + { + *value++ = 0; + len -= value - key; + } + else + { + value = tmp; + len = 0; + } + + // strip quotes + if (len > 2 && (*value == '"' || *value == '\'') && value[len - 1] == *value) + { + value++; + len--; + value[len - 1] = 0; + } + // copy and null terminate + else if (len < sizeof(tmp) - 1) + { + memcpy(tmp, value, len); + tmp[len] = 0; + value = tmp; + } + else + return; + + // Run hooks + { + struct param_radius_response p = { &tunnel[session[s].tunnel], &session[s], (char *) key, (char *) value }; + run_plugins(PLUGIN_RADIUS_RESPONSE, &p); + } +} + +// process RADIUS response +void processrad(uint8_t *buf, int len, char socket_index) +{ + uint8_t b[MAXETHER]; + uint16_t r; + sessionidt s; + tunnelidt t = 0; + hasht hash; + uint8_t routes = 0; + int r_code; + int r_id; + + CSTAT(processrad); + + LOG_HEX(5, "RADIUS Response", buf, len); + if (len < 20 || len < ntohs(*(uint16_t *) (buf + 2))) + { + LOG(1, 0, 0, "Duff RADIUS response length %d\n", len); + return ; + } + + r_code = buf[0]; // response type + r_id = buf[1]; // radius reply indentifier. + + len = ntohs(*(uint16_t *) (buf + 2)); + r = socket_index | (r_id << RADIUS_SHIFT); + s = radius[r].session; + LOG(3, s, session[s].tunnel, "Received %s, radius %d response for session %u (%s, id %d)\n", + radius_state(radius[r].state), r, s, radius_code(r_code), r_id); + + if (!s && radius[r].state != RADIUSSTOP) + { + LOG(1, s, session[s].tunnel, " Unexpected RADIUS response\n"); + return; + } + if (radius[r].state != RADIUSAUTH && radius[r].state != RADIUSSTART + && radius[r].state != RADIUSSTOP && radius[r].state != RADIUSINTERIM) + { + LOG(1, s, session[s].tunnel, " Unexpected RADIUS response\n"); + return; + } + t = session[s].tunnel; + calc_auth(buf, len, radius[r].auth, hash); + do { + if (memcmp(hash, buf + 4, 16)) + { + LOG(0, s, session[s].tunnel, " Incorrect auth on RADIUS response!! (wrong secret in radius config?)\n"); + return; // Do nothing. On timeout, it will try the next radius server. + } + + if ((radius[r].state == RADIUSAUTH && r_code != AccessAccept && r_code != AccessReject) || + ((radius[r].state == RADIUSSTART || radius[r].state == RADIUSSTOP || radius[r].state == RADIUSINTERIM) && r_code != AccountingResponse)) + { + LOG(1, s, session[s].tunnel, " Unexpected RADIUS response %s\n", radius_code(r_code)); + return; // We got something we didn't expect. Let the timeouts take + // care off finishing the radius session if that's really correct. + } + + if (radius[r].state == RADIUSAUTH) + { + // run post-auth plugin + struct param_post_auth packet = { + &tunnel[t], + &session[s], + session[s].user, + (r_code == AccessAccept), + radius[r].chap ? PPPCHAP : PPPPAP + }; + + run_plugins(PLUGIN_POST_AUTH, &packet); + r_code = packet.auth_allowed ? AccessAccept : AccessReject; + + // process auth response + if (radius[r].chap) + { + // CHAP + uint8_t *p = makeppp(b, sizeof(b), 0, 0, s, t, PPPCHAP); + if (!p) return; // Abort! + + *p = (r_code == AccessAccept) ? 3 : 4; // ack/nak + p[1] = radius[r].id; + *(uint16_t *) (p + 2) = ntohs(4); // no message + tunnelsend(b, (p - b) + 4, t); // send it + + LOG(3, s, session[s].tunnel, " CHAP User %s authentication %s.\n", session[s].user, + (r_code == AccessAccept) ? "allowed" : "denied"); + } + else + { + // PAP + uint8_t *p = makeppp(b, sizeof(b), 0, 0, s, t, PPPPAP); + if (!p) return; // Abort! + + // ack/nak + *p = r_code; + p[1] = radius[r].id; + *(uint16_t *) (p + 2) = ntohs(5); + p[4] = 0; // no message + tunnelsend(b, (p - b) + 5, t); // send it + + LOG(3, s, session[s].tunnel, " PAP User %s authentication %s.\n", session[s].user, + (r_code == AccessAccept) ? "allowed" : "denied"); + } + + if (r_code == AccessAccept) + { + // Login successful + // Extract IP, routes, etc + uint8_t *p = buf + 20; + uint8_t *e = buf + len; + for (; p + 2 <= e && p[1] && p + p[1] <= e; p += p[1]) + { + if (*p == 26 && p[1] >= 7) + { + // Vendor-Specific Attribute + uint32_t vendor = ntohl(*(int *)(p + 2)); + uint8_t attrib = *(p + 6); + int attrib_length = *(p + 7) - 2; + + LOG(4, s, session[s].tunnel, " Radius reply contains Vendor-Specific. Vendor=%u Attrib=%u Length=%d\n", vendor, attrib, attrib_length); + if (vendor == 9 && attrib == 1) // Cisco-AVPair + { + if (attrib_length < 0) continue; + LOG(3, s, session[s].tunnel, " Cisco-AVPair value: %.*s\n", + attrib_length, p + 8); + + handle_avpair(s, p + 8, attrib_length); + continue; + } + else if (vendor == 529 && attrib >= 135 && attrib <= 136) // Ascend + { + // handle old-format ascend DNS attributes below + p += 6; + } + else + { + LOG(3, s, session[s].tunnel, " Unknown vendor-specific\n"); + continue; + } + } + + if (*p == 8) + { + // Framed-IP-Address + if (p[1] < 6) continue; + session[s].ip = ntohl(*(uint32_t *) (p + 2)); + session[s].ip_pool_index = -1; + LOG(3, s, session[s].tunnel, " Radius reply contains IP address %s\n", + fmtaddr(htonl(session[s].ip), 0)); + + if (session[s].ip == 0xFFFFFFFE) + session[s].ip = 0; // assign from pool + } + else if (*p == 135) + { + // DNS address + if (p[1] < 6) continue; + session[s].dns1 = ntohl(*(uint32_t *) (p + 2)); + LOG(3, s, session[s].tunnel, " Radius reply contains primary DNS address %s\n", + fmtaddr(htonl(session[s].dns1), 0)); + } + else if (*p == 136) + { + // DNS address + if (p[1] < 6) continue; + session[s].dns2 = ntohl(*(uint32_t *) (p + 2)); + LOG(3, s, session[s].tunnel, " Radius reply contains secondary DNS address %s\n", + fmtaddr(htonl(session[s].dns2), 0)); + } + else if (*p == 22) + { + // Framed-Route + in_addr_t ip = 0, mask = 0; + uint8_t u = 0; + uint8_t bits = 0; + uint8_t *n = p + 2; + uint8_t *e = p + p[1]; + while (n < e && (isdigit(*n) || *n == '.')) + { + if (*n == '.') + { + ip = (ip << 8) + u; + u = 0; + } + else + u = u * 10 + *n - '0'; + n++; + } + ip = (ip << 8) + u; + if (*n == '/') + { + n++; + while (n < e && isdigit(*n)) + bits = bits * 10 + *n++ - '0'; + mask = (( -1) << (32 - bits)); + } + else if ((ip >> 24) < 128) + mask = 0xFF0000; + else if ((ip >> 24) < 192) + mask = 0xFFFF0000; + else + mask = 0xFFFFFF00; + + if (routes == MAXROUTE) + { + LOG(1, s, session[s].tunnel, " Too many routes\n"); + } + else if (ip) + { + LOG(3, s, session[s].tunnel, " Radius reply contains route for %s/%s\n", + fmtaddr(htonl(ip), 0), fmtaddr(htonl(mask), 1)); + + session[s].route[routes].ip = ip; + session[s].route[routes].mask = mask; + routes++; + } + } + else if (*p == 11) + { + // Filter-Id + char *filter = (char *) p + 2; + int l = p[1] - 2; + char *suffix; + int f; + uint8_t *fp = 0; + + LOG(3, s, session[s].tunnel, " Radius reply contains Filter-Id \"%.*s\"\n", l, filter); + if ((suffix = memchr(filter, '.', l))) + { + int b = suffix - filter; + if (l - b == 3 && !memcmp("in", suffix+1, 2)) + fp = &session[s].filter_in; + else if (l - b == 4 && !memcmp("out", suffix+1, 3)) + fp = &session[s].filter_out; + + l = b; + } + + if (!fp) + { + LOG(3, s, session[s].tunnel, " Invalid filter\n"); + continue; + } + + if ((f = find_filter(filter, l)) < 0 || !*ip_filters[f].name) + { + LOG(3, s, session[s].tunnel, " Unknown filter\n"); + } + else + { + *fp = f + 1; + ip_filters[f].used++; + } + } + else if (*p == 99) + { + // Framed-IPv6-Route + struct in6_addr r6; + int prefixlen; + uint8_t *n = p + 2; + uint8_t *e = p + p[1]; + uint8_t *m = memchr(n, '/', e - p); + + *m++ = 0; + inet_pton(AF_INET6, (char *) n, &r6); + + prefixlen = 0; + while (m < e && isdigit(*m)) { + prefixlen = prefixlen * 10 + *m++ - '0'; + } + + if (prefixlen) + { + LOG(3, s, session[s].tunnel, + " Radius reply contains route for %s/%d\n", + n, prefixlen); + session[s].ipv6route = r6; + session[s].ipv6prefixlen = prefixlen; + } + } + } + } + else if (r_code == AccessReject) + { + LOG(2, s, session[s].tunnel, " Authentication rejected for %s\n", session[s].user); + sessionkill(s, "Authentication rejected"); + break; + } + + if (!session[s].dns1 && config->default_dns1) + { + session[s].dns1 = ntohl(config->default_dns1); + LOG(3, s, t, " Sending dns1 = %s\n", fmtaddr(config->default_dns1, 0)); + } + if (!session[s].dns2 && config->default_dns2) + { + session[s].dns2 = ntohl(config->default_dns2); + LOG(3, s, t, " Sending dns2 = %s\n", fmtaddr(config->default_dns2, 0)); + } + + // Valid Session, set it up + session[s].unique_id = 0; + sessionsetup(s, t); + } + else + { + // An ack for a stop or start record. + LOG(3, s, t, " RADIUS accounting ack recv in state %s\n", radius_state(radius[r].state)); + break; + } + } while (0); + + // finished with RADIUS + radiusclear(r, s); +} + +// Send a retry for RADIUS/CHAP message +void radiusretry(uint16_t r) +{ + sessionidt s = radius[r].session; + tunnelidt t = 0; + + CSTAT(radiusretry); + + if (s) t = session[s].tunnel; + + switch (radius[r].state) + { + case RADIUSCHAP: // sending CHAP down PPP + sendchap(s, t); + break; + case RADIUSAUTH: // sending auth to RADIUS server + case RADIUSSTART: // sending start accounting to RADIUS server + case RADIUSSTOP: // sending stop accounting to RADIUS server + case RADIUSINTERIM: // sending interim accounting to RADIUS server + radiussend(r, radius[r].state); + break; + default: + case RADIUSNULL: // Not in use + case RADIUSWAIT: // waiting timeout before available, in case delayed reply from RADIUS server + // free up RADIUS task + radiusclear(r, s); + LOG(3, s, session[s].tunnel, "Freeing up radius session %d\n", r); + break; + } +} + +extern int daefd; + +void processdae(uint8_t *buf, int len, struct sockaddr_in *addr, int alen, struct in_addr *local) +{ + int i, r_code, r_id, length, attribute_length; + uint8_t *packet, attribute; + hasht hash; + char username[MAXUSER] = ""; + in_addr_t nas = 0; + in_addr_t ip = 0; + uint32_t port = 0; + uint32_t error = 0; + sessionidt s = 0; + tunnelidt t; + int fin = -1; + int fout = -1; + uint8_t *avpair[64]; + int avpair_len[sizeof(avpair)/sizeof(*avpair)]; + int avp = 0; + int auth_only = 0; + uint8_t *p; + + LOG(3, 0, 0, "DAE request from %s\n", fmtaddr(addr->sin_addr.s_addr, 0)); + LOG_HEX(5, "DAE Request", buf, len); + + if (len < 20 || len < ntohs(*(uint16_t *) (buf + 2))) + { + LOG(1, 0, 0, "Duff DAE request length %d\n", len); + return; + } + + r_code = buf[0]; // request type + r_id = buf[1]; // radius indentifier. + + if (r_code != DisconnectRequest && r_code != CoARequest) + { + LOG(1, 0, 0, "Unrecognised DAE request %s\n", radius_code(r_code)); + return; + } + + if (!config->cluster_iam_master) + { + master_forward_dae_packet(buf, len, addr->sin_addr.s_addr, addr->sin_port); + return; + } + + len = ntohs(*(uint16_t *) (buf + 2)); + + LOG(3, 0, 0, "Received DAE %s, id %d\n", radius_code(r_code), r_id); + + // check authenticator + calc_auth(buf, len, zero, hash); + if (memcmp(hash, buf + 4, 16) != 0) + { + LOG(1, 0, 0, "Incorrect vector in DAE request (wrong secret in radius config?)\n"); + return; + } + + // unpack attributes + packet = buf + 20; + length = len - 20; + + while (length > 0) + { + attribute = *packet++; + attribute_length = *packet++; + if (attribute_length < 2) + break; + + length -= attribute_length; + attribute_length -= 2; + switch (attribute) + { + case 1: /* username */ + len = attribute_length < MAXUSER ? attribute_length : MAXUSER - 1; + memcpy(username, packet, len); + username[len] = 0; + LOG(4, 0, 0, " Received DAE User-Name: %s\n", username); + break; + + case 4: /* nas ip address */ + nas = *(uint32_t *) packet; // net order + if (nas != config->bind_address) + error = 403; // NAS identification mismatch + + LOG(4, 0, 0, " Received DAE NAS-IP-Address: %s\n", fmtaddr(nas, 0)); + break; + + case 5: /* nas port */ + port = ntohl(*(uint32_t *) packet); + if (port < 1 || port > MAXSESSION) + error = 404; + + LOG(4, 0, 0, " Received DAE NAS-Port: %u\n", port); + break; + + case 6: /* service type */ + { + uint32_t service_type = ntohl(*(uint32_t *) packet); + auth_only = service_type == 8; // Authenticate only + + LOG(4, 0, 0, " Received DAE Service-Type: %u\n", service_type); + } + break; + + case 8: /* ip address */ + ip = *(uint32_t *) packet; // net order + LOG(4, 0, 0, " Received DAE Framed-IP-Address: %s\n", fmtaddr(ip, 0)); + break; + + case 11: /* filter id */ + LOG(4, 0, 0, " Received DAE Filter-Id: %.*s\n", attribute_length, packet); + if (!(p = memchr(packet, '.', attribute_length))) + { + error = 404; // invalid request + break; + } + + len = p - packet; + i = find_filter((char *) packet, len); + if (i < 0 || !*ip_filters[i].name) + { + error = 404; + break; + } + + if (!memcmp(p, ".in", attribute_length - len)) + fin = i + 1; + else if (!memcmp(p, ".out", attribute_length - len)) + fout = i + 1; + else + error = 404; + + break; + + case 26: /* vendor specific */ + if (attribute_length >= 6 + && ntohl(*(uint32_t *) packet) == 9 // Cisco + && *(packet + 4) == 1 // Cisco-AVPair + && *(packet + 5) >= 2) // length + { + int len = *(packet + 5) - 2; + uint8_t *a = packet + 6; + + LOG(4, 0, 0, " Received DAE Cisco-AVPair: %.*s\n", len, a); + if (avp < sizeof(avpair)/sizeof(*avpair) - 1) + { + avpair[avp] = a; + avpair_len[avp++] = len; + } + } + break; + } + + packet += attribute_length; + } + + if (!error && auth_only) + { + if (fin != -1 || fout != -1 || avp) + error = 401; // unsupported attribute + else + error = 405; // unsupported service + } + + if (!error && !(port || ip || *username)) + error = 402; // missing attribute + + // exact match for SID if given + if (!error && port) + { + s = port; + if (!session[s].opened) + error = 503; // not found + } + + if (!error && ip) + { + // find/check session by IP + i = sessionbyip(ip); + if (!i || (s && s != i)) // not found or mismatching port + error = 503; + else + s = i; + } + + if (!error && *username) + { + if (s) + { + if (strcmp(session[s].user, username)) + error = 503; + } + else if (!(s = sessionbyuser(username))) + error = 503; + } + + t = session[s].tunnel; + + switch (r_code) + { + case DisconnectRequest: // Packet of Disconnect/Death + if (error) + { + r_code = DisconnectNAK; + break; + } + + LOG(3, s, t, " DAE Disconnect %d (%s)\n", s, session[s].user); + r_code = DisconnectACK; + + sessionshutdown(s, "Requested by PoD", CDN_ADMIN_DISC, TERM_ADMIN_RESET); // disconnect session + break; + + case CoARequest: // Change of Authorization + if (error) + { + r_code = CoANAK; + break; + } + + LOG(3, s, t, " DAE Change %d (%s)\n", s, session[s].user); + r_code = CoAACK; + + // reset + { + struct param_radius_reset p = { &tunnel[session[s].tunnel], &session[s] }; + run_plugins(PLUGIN_RADIUS_RESET, &p); + } + + // apply filters + if (fin == -1) + fin = 0; + else + LOG(3, s, t, " Filter in %d (%s)\n", fin, ip_filters[fin - 1].name); + + if (fout == -1) + fout = 0; + else + LOG(3, s, t, " Filter out %d (%s)\n", fout, ip_filters[fout - 1].name); + + filter_session(s, fin, fout); + + // process cisco av-pair(s) + for (i = 0; i < avp; i++) + { + LOG(3, s, t, " Cisco-AVPair: %.*s\n", avpair_len[i], avpair[i]); + handle_avpair(s, avpair[i], avpair_len[i]); + } + + cluster_send_session(s); + break; + } + + // send response + packet = buf; + *packet++ = r_code; + *packet++ = r_id; + // skip len + auth + packet += 2 + 16; + len = packet - buf; + + // add attributes + if (error) + { + // add error cause + *packet++ = 101; + *packet++ = 6; + *(uint32_t *) packet = htonl(error); + len += 6; + } + + *((uint16_t *)(buf + 2)) = htons(len); + + // make vector + calc_auth(buf, len, hash, buf + 4); + + LOG(3, 0, 0, "Sending DAE %s, id=%d\n", radius_code(r_code), r_id); + + // send DAE response + if (sendtofrom(daefd, buf, len, MSG_DONTWAIT | MSG_NOSIGNAL, (struct sockaddr *) addr, alen, local) < 0) + LOG(0, 0, 0, "Error sending DAE response packet: %s\n", strerror(errno)); +} diff --git a/scripts/l2tpns-capture b/scripts/l2tpns-capture new file mode 100644 index 0000000..6b31daf --- /dev/null +++ b/scripts/l2tpns-capture @@ -0,0 +1,68 @@ +#! /usr/bin/perl -w + +# +# Accept intercept data from l2tpns, write to a file in pcap format +# (http://wiki.ethereal.com/Development/LibpcapFileFormat) suffixed +# with timestamp. Killing the process with SIGHUP causes a new file +# to be opened. +# + +use strict; +use IO::File; +use IO::Socket; +use Time::HiRes 'gettimeofday'; + +(my $cmd = $0) =~ s!.*/!!; + +die "Usage: $cmd PREFIX PORT\n" unless @ARGV == 2 and $ARGV[1] =~ /^\d+$/; + +my ($prefix, $port) = @ARGV; +my $sock = IO::Socket::INET->new( + LocalPort => $port, + Proto => 'udp', + Type => SOCK_DGRAM, +) or die "$cmd: can't bind to port $port ($!)\n"; + +my $restart = 0; +$SIG{HUP} = sub { $restart++ }; + +my $header = pack LSSlLLL => + 0xa1b2c3d4, # magic no + 2, # version maj + 4, # version min + 0, # timezone offset (GMT) + 0, # timestamp accuracy + 65536, # snaplen + 12; # link type (RAW_IP) + +my $cap; +my $buf; +my $file; +for (;;) +{ + unless ($cap) + { + $file = $prefix . time; + $cap = IO::File->new("> $file") + or die "$0: can't create capture file $file ($!)\n"; + + $cap->print($header) + or die "$0: error writing to $file ($!)\n"; + } + + while ($sock->recv($buf, 1600)) + { + $cap->print( + # packet header: sec, usec, included size, original size + (pack LLLL => (gettimeofday), (length $buf) x 2), + $buf + ) or die "$0: error writing to $file ($!)\n"; + } + + if ($restart) + { + $restart = 0; + $cap->close; + undef $cap; + } +} diff --git a/scripts/l2tpns-monitor b/scripts/l2tpns-monitor new file mode 100644 index 0000000..d17695e --- /dev/null +++ b/scripts/l2tpns-monitor @@ -0,0 +1,28 @@ +#!/bin/sh +stopfile=/tmp/l2tpns.stop +first=`date +%s` +min_first_time=3 +restart_delay=5 +prog=${0##*/} + +while : +do + echo "`date`: Starting l2tpns $@" + start=`date +%s` + /usr/sbin/l2tpns ${1+"$@"} + RETVAL=$? + stop=`date +%s` + t=$(($stop - $start)); + first=$(($stop - $first)); + echo "`date`: l2tpns exited after $t seconds, status $RETVAL" + if [ $first -lt $min_first_time ]; then + echo "`date`: l2tpns exited immediately, $prog exiting" + exit $RETVAL + fi + if [ -f $stopfile ]; then + ls -l $stopfile + echo "`date`: stop file found, $prog exiting" + exit + fi + sleep $restart_delay +done >>/var/log/$prog 2>&1 & # execute in background diff --git a/scripts/l2tpns.script b/scripts/l2tpns.script new file mode 100644 index 0000000..ba4cdf4 --- /dev/null +++ b/scripts/l2tpns.script @@ -0,0 +1,93 @@ +#!/bin/bash +# +# Startup script for l2tpns +# +# chkconfig: 2345 83 25 +# description: l2tpns. +# processname: l2tpns +# pidfile: /var/run/l2tpns.pid +# config: /etc/l2tpns + +# Source function library. +. /etc/rc.d/init.d/functions + +if [ -f /etc/sysconfig/lt2pns ]; then + . /etc/sysconfig/lt2pns +fi + +# Path to the l2tpns-monitor script, server binary, and short-form for messages. +l2tpns_monitor=/usr/sbin/l2tpns-monitor +l2tpns=/usr/sbin/l2tpns +prog=${l2tpns##*/} +RETVAL=0 + +start() { + echo -n $"Starting $prog: " + rm -f /tmp/l2tpns.stop + daemon --check=$prog $l2tpns_monitor $OPTIONS + RETVAL=$? + echo + sleep 5 + pid=`pidofproc $l2tpns_monitor` + if [ -z "$pid" ] || [ "$pid" -eq 0 ]; then + echo -n "Error starting $prog" + echo_failure + echo + return 99 + fi + [ $RETVAL = 0 ] && touch /var/lock/subsys/l2tpns + return $RETVAL +} +stop() { + echo -n $"Stopping $prog: " + echo >/tmp/l2tpns.stop + killproc $l2tpns + RETVAL=$? + echo + [ $RETVAL = 0 ] && rm -f /var/lock/subsys/l2tpns /var/run/l2tpns.pid +} +reload() { + echo -n $"Reloading $prog: " + killproc $l2tpns -HUP + RETVAL=$? + echo +} + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + status) + status $l2tpns + RETVAL=$? + ;; + restart) + stop + sleep 5 + start + ;; + condrestart) + if [ -f /var/run/l2tpns.pid ] ; then + stop + start + fi + ;; + reload) + reload + ;; + coldrestart) + stop + sleep 10 + rm -f /tmp/l2tpns.dump + start + ;; + *) + echo $"Usage: $prog {start|stop|restart|condrestart|reload|status|coldrestart}" + exit 1 +esac + +exit $RETVAL diff --git a/sessionctl.c b/sessionctl.c new file mode 100644 index 0000000..5971d4c --- /dev/null +++ b/sessionctl.c @@ -0,0 +1,74 @@ +#include +#include "l2tpns.h" +#include "plugin.h" +#include "control.h" + +/* session control */ + +char const *cvs_id = "$Id: sessionctl.c,v 1.5 2006/04/13 11:14:35 bodea Exp $"; + +int plugin_api_version = PLUGIN_API_VERSION; +static struct pluginfuncs *f = 0; + +char *plugin_control_help[] = { + " drop USER|SID [REASON] Shutdown user session", + " kill USER|SID [REASON] Kill user session", + 0 +}; + +int plugin_control(struct param_control *data) +{ + sessionidt session; + sessiont *s = 0; + char *end; + char *reason; + + if (data->argc < 1) + return PLUGIN_RET_OK; + + if (strcmp(data->argv[0], "drop") && strcmp(data->argv[0], "kill")) + return PLUGIN_RET_OK; // not for us + + if (!data->iam_master) + return PLUGIN_RET_NOTMASTER; + + if (data->argc < 2 || data->argc > 3) + { + data->response = NSCTL_RES_ERR; + data->additional = "requires username or session id and optional reason"; + return PLUGIN_RET_STOP; + } + + if (!(session = strtol(data->argv[1], &end, 10)) || *end) + session = f->get_session_by_username(data->argv[1]); + + if (session) + s = f->get_session_by_id(session); + + if (!s || !s->ip) + { + data->response = NSCTL_RES_ERR; + data->additional = "session not found"; + return PLUGIN_RET_STOP; + } + + if (data->argc > 2) + reason = data->argv[2]; + else + reason = "Requested by administrator."; + + if (data->argv[0][0] == 'd') + f->sessionshutdown(session, reason, CDN_ADMIN_DISC, TERM_ADMIN_RESET); + else + f->sessionkill(session, reason); + + data->response = NSCTL_RES_OK; + data->additional = 0; + + return PLUGIN_RET_STOP; +} + +int plugin_init(struct pluginfuncs *funcs) +{ + return ((f = funcs)) ? 1 : 0; +} diff --git a/setrxspeed.c b/setrxspeed.c new file mode 100644 index 0000000..2e9e663 --- /dev/null +++ b/setrxspeed.c @@ -0,0 +1,41 @@ +#include +#include "l2tpns.h" +#include "plugin.h" + +/* fudge up session rx speed if not set */ + +char const *cvs_id = "$Id: setrxspeed.c,v 1.4 2005/10/11 09:04:53 bodea Exp $"; + +int plugin_api_version = PLUGIN_API_VERSION; +static struct pluginfuncs *f = 0; + +int plugin_post_auth(struct param_post_auth *data) +{ + if (!data->auth_allowed) + return PLUGIN_RET_OK; + + if (data->s->rx_connect_speed) + return PLUGIN_RET_OK; + + switch (data->s->tx_connect_speed) + { + case 256: + data->s->rx_connect_speed = 64; + break; + + case 512: + data->s->rx_connect_speed = 128; + break; + + case 1500: + data->s->rx_connect_speed = 256; + break; + } + + return PLUGIN_RET_OK; +} + +int plugin_init(struct pluginfuncs *funcs) +{ + return ((f = funcs)) ? 1 : 0; +} diff --git a/snoopctl.c b/snoopctl.c new file mode 100644 index 0000000..6c79087 --- /dev/null +++ b/snoopctl.c @@ -0,0 +1,122 @@ +#include +#include "l2tpns.h" +#include "plugin.h" +#include "control.h" + +/* snoop control */ + +char const *cvs_id = "$Id: snoopctl.c,v 1.7 2005/10/11 09:04:53 bodea Exp $"; + +int plugin_api_version = PLUGIN_API_VERSION; +static struct pluginfuncs *f = 0; + +char *plugin_control_help[] = { + " snoop USER|SID IP PORT Intercept user traffic", + " unsnoop USER|SID Stop intercepting user", + 0 +}; + +int plugin_control(struct param_control *data) +{ + sessionidt session; + sessiont *s = 0; + int flag; + char *end; + + if (data->argc < 1) + return PLUGIN_RET_OK; + + if (strcmp(data->argv[0], "snoop") && strcmp(data->argv[0], "unsnoop")) + return PLUGIN_RET_OK; // not for us + + if (!data->iam_master) + return PLUGIN_RET_NOTMASTER; + + flag = data->argv[0][0] != 'u'; + + if (flag) + { + if (data->argc != 4) + { + data->response = NSCTL_RES_ERR; + data->additional = "requires username or session id and host, port"; + return PLUGIN_RET_STOP; + } + } + else + { + if (data->argc != 2) + { + data->response = NSCTL_RES_ERR; + data->additional = "requires username or session id"; + return PLUGIN_RET_STOP; + } + } + + if (!(session = strtol(data->argv[1], &end, 10)) || *end) + session = f->get_session_by_username(data->argv[1]); + + if (session) + s = f->get_session_by_id(session); + + if (!s || !s->ip) + { + data->response = NSCTL_RES_ERR; + data->additional = "session not found"; + return PLUGIN_RET_STOP; + } + + if (flag) + { + in_addr_t ip = inet_addr(data->argv[2]); + uint16_t port = atoi(data->argv[3]); + + if (!ip || ip == INADDR_NONE) + { + data->response = NSCTL_RES_ERR; + data->additional = "invalid ip address"; + return PLUGIN_RET_STOP; + } + + if (!port) + { + data->response = NSCTL_RES_ERR; + data->additional = "invalid port"; + return PLUGIN_RET_STOP; + } + + if (ip == s->snoop_ip && port == s->snoop_port) + { + data->response = NSCTL_RES_ERR; + data->additional = "already intercepted"; + return PLUGIN_RET_STOP; + } + + s->snoop_ip = ip; + s->snoop_port = port; + } + else + { + if (!s->snoop_ip) + { + data->response = NSCTL_RES_ERR; + data->additional = "not intercepted"; + return PLUGIN_RET_STOP; + } + + s->snoop_ip = 0; + s->snoop_port = 0; + } + + f->session_changed(session); + + data->response = NSCTL_RES_OK; + data->additional = 0; + + return PLUGIN_RET_STOP; +} + +int plugin_init(struct pluginfuncs *funcs) +{ + return ((f = funcs)) ? 1 : 0; +} diff --git a/stripdomain.c b/stripdomain.c new file mode 100644 index 0000000..748efc9 --- /dev/null +++ b/stripdomain.c @@ -0,0 +1,31 @@ +#include +#include "l2tpns.h" +#include "plugin.h" + +/* strip domain part of username before sending RADIUS requests */ + +char const *cvs_id = "$Id: stripdomain.c,v 1.8 2005/10/11 09:04:53 bodea Exp $"; + +int plugin_api_version = PLUGIN_API_VERSION; +static struct pluginfuncs *f = 0; + +int plugin_pre_auth(struct param_pre_auth *data) +{ + char *p; + + if (!data->continue_auth) return PLUGIN_RET_STOP; + + // Strip off @domain + if ((p = strchr(data->username, '@'))) + { + f->log(3, 0, 0, "Stripping off trailing domain name \"%s\"\n", p); + *p = 0; + } + + return PLUGIN_RET_OK; +} + +int plugin_init(struct pluginfuncs *funcs) +{ + return ((f = funcs)) ? 1 : 0; +} diff --git a/tbf.c b/tbf.c new file mode 100644 index 0000000..223c40d --- /dev/null +++ b/tbf.c @@ -0,0 +1,354 @@ +// L2TPNS: token bucket filters + +char const *cvs_id_tbf = "$Id: tbf.c,v 1.13 2005/07/31 10:04:10 bodea Exp $"; + +#include +#include "l2tpns.h" +#include "util.h" +#include "tbf.h" + +tbft *filter_list = NULL; +static int filter_list_size = 0; + +static int timer_chain = -1; // Head of timer chain. + +static void tbf_run_queue(int tbf_id); + +void init_tbf(int num_tbfs) +{ + if (!(filter_list = shared_malloc(sizeof(*filter_list) * num_tbfs))) + return; + + filter_list_size = num_tbfs; + filter_list[0].sid = -1; // Reserved. +} +// +// Put a TBF on the timer list. +// This is a doubly linked list.. +// We put ourselves on the tail of the list. +// +static void add_to_timer(int id) +{ + if (!filter_list) + return; + + if (timer_chain == -1) { + filter_list[id].next = filter_list[id].prev = id; + timer_chain = id; + return; + } + + filter_list[id].next = timer_chain; + filter_list[id].prev = filter_list[timer_chain].prev; + filter_list[filter_list[timer_chain].prev].next = id; + filter_list[timer_chain].prev = id; +} + +// +// Remove a TBF from the timer list. +// This is a doubly linked list. +static void del_from_timer(int id) +{ + if (!filter_list) + return; + + if (filter_list[id].next == id) { // Last element in chain? + if (timer_chain != id) { // WTF? + LOG(0, 0, 0, "Removed a singleton element from TBF, but tc didn't point to it!\n"); + } else + timer_chain = -1; + filter_list[id].next = filter_list[id].prev = 0; + return; + } + + filter_list[filter_list[id].next].prev = filter_list[id].prev; + filter_list[filter_list[id].prev].next = filter_list[id].next; + if (timer_chain == id) + timer_chain = filter_list[id].next; + + filter_list[id].next = filter_list[id].prev = 0; // Mark as off the timer chain. +} + +// +// Free a token bucket filter structure for re-use. +// + +int free_tbf(int tid) +{ + if (tid < 1) // Make sure we don't free id # 0 + return -1; + + if (!filter_list) // WTF? + return -1; + + if (filter_list[tid].next) + del_from_timer(tid); + filter_list[tid].sid = 0; + + return 0; // Done! +} + +// +// Allocate a new token bucket filter. +// +int new_tbf(int sid, int max_credit, int rate, void (*f)(sessionidt, uint8_t *, int)) +{ + int i; + static int p = 0; + + LOG(4, 0, 0, "Allocating new TBF (sess %d, rate %d, helper %p)\n", sid, rate, f); + + if (!filter_list) + return 0; // Couldn't alloc memory! + + for (i = 0 ; i < filter_list_size ; ++i, p = (p+1)%filter_list_size ) { + if (filter_list[p].sid) + continue; + + memset((void*) &filter_list[p], 0, sizeof(filter_list[p]) ); // Clear counters and data. + filter_list[p].sid = sid; + filter_list[p].credit = max_credit; + filter_list[p].queued = 0; + filter_list[p].max_credit = max_credit; + filter_list[p].rate = rate; + filter_list[p].oldest = 0; + filter_list[p].send = f; + return p; + } + + LOG(0, 0, 0, "Ran out of token bucket filters! Sess %d will be un-throttled\n", sid); + return 0; +} + +// +// Sanity check all the TBF records. This is +// typically done when we become a master.. +// +void fsck_tbfs(void) +{ + int i , sid; + + if (!filter_list) + return; + + for (i = 1; i < filter_list_size; ++i) { + if (!filter_list[i].sid) // Is it used?? + continue; + + sid = filter_list[i].sid; + if (i != session[sid].tbf_in && + i != session[sid].tbf_out) { // Ooops. + + free_tbf(i); // Mark it as free... + } + } + + for (i = 0; i < config->cluster_highest_sessionid ; ++i) { + if (session[i].tbf_in && filter_list[session[i].tbf_in].sid != i) { + filter_list[session[i].tbf_in].sid = i; // Ouch!? FIXME. What to do here? + } + if (session[i].tbf_out && filter_list[session[i].tbf_out].sid != i) { + filter_list[session[i].tbf_out].sid = i; // Ouch!? FIXME. What to do here? + } + } +} + + +// +// Run a packet through a token bucket filter. +// If we can send it right away, we do. Else we +// try and queue it to send later. Else we drop it. +// +int tbf_queue_packet(int tbf_id, uint8_t *data, int size) +{ + int i; + tbft *f; + + if (!filter_list) + return -1; + + if (tbf_id > filter_list_size || tbf_id < 1) { // Out of range ID?? + // Very bad. Just drop it. + return -1; + } + + f = &filter_list[tbf_id]; + + if (!f->sid) // Is this a real structure?? + return -1; + + tbf_run_queue(tbf_id); // Caculate credit and send any queued packets if possible.. + + f->b_queued += size; + f->p_queued ++; + + if (!f->queued && f->credit > size) { // If the queue is empty, and we have + // enough credit, just send it now. + f->credit -= size; + if (f->send) { + f->send(f->sid, data, size); + f->b_sent += size; + f->p_sent ++; + } else { + f->b_dropped += size; + f->p_dropped ++; + } + return size; + } + + // Not enough credit. Can we have room in the queue? + if (f->queued >= TBF_MAX_QUEUE) { + f->p_dropped ++; + f->b_dropped += size; + return -1; // No, just drop it. + } + + // Is it too big to fit into a queue slot? + if (size >= TBF_MAX_SIZE) { + f->p_dropped ++; + f->b_dropped += size; + return -1; // Yes, just drop it. + } + + // Ok. We have a slot, and it's big enough to + // contain the packet, so queue the packet! + i = ( f->oldest + f->queued ) % TBF_MAX_QUEUE; + memcpy(f->packets[i], data, size); + + f->sizes[i] = size; + f->queued ++; + f->p_delayed ++; + + if (!f->next) // Are we off the timer chain? + add_to_timer(tbf_id); // Put ourselves on the timer chain. + + return 0; // All done. +} + +// +// Send queued packets from the filter if possible. +// (We're normally only called if this is possible.. ) +static void tbf_run_queue(int tbf_id) +{ + tbft * f; + + if (!filter_list) + return; + + f = &filter_list[tbf_id]; + + // Calculate available credit... + f->credit += (TIME - f->lasttime) * f->rate / 10; // current time is 1/10th of a second. + if (f->credit > f->max_credit) + f->credit = f->max_credit; + f->lasttime = TIME; + + while (f->queued > 0 && f->credit >= f->sizes[f->oldest]) { // While we have enough credit.. + + if (f->send) { + f->send(f->sid, f->packets[f->oldest], f->sizes[f->oldest]); + f->b_sent += f->sizes[f->oldest]; + f->p_sent ++; + } else { + f->b_dropped += f->sizes[f->oldest]; + f->p_dropped ++; + } + + f->credit -= f->sizes[f->oldest]; + + f->oldest = (f->oldest + 1 ) % TBF_MAX_QUEUE; + f->queued--; // One less queued packet.. + } + + if (f->queued) // Still more to do. Hang around on the timer list. + return; + + if (f->next) // Are we on the timer list?? + del_from_timer(tbf_id); // Nothing more to do. Get off the timer list. +} + +// +// Periodically walk the timer list.. +// +int tbf_run_timer(void) +{ + int i = timer_chain; + int count = filter_list_size + 1; // Safety check. + int last = -1; + int tbf_id; // structure being processed. + + if (timer_chain < 0) + return 0; // Nothing to do... + + if (!filter_list) // No structures built yet. + return 0; + + last = filter_list[i].prev; // last element to process. + + do { + tbf_id = i; + i = filter_list[i].next; // Get the next in the queue. + + tbf_run_queue(tbf_id); // Run the timer queue.. + } while ( timer_chain > 0 && i && tbf_id != last && --count > 0); + + +#if 0 // Debugging. + for (i = 0; i < filter_list_size; ++i) { + if (!filter_list[i].next) + continue; + if (filter_list[i].lasttime == TIME) // Did we just run it? + continue; + + LOG(1, 0, 0, "Missed tbf %d! Not on the timer chain?(n %d, p %d, tc %d)\n", i, + filter_list[i].next, filter_list[i].prev, timer_chain); + tbf_run_queue(i); + } +#endif + + return 1; +} + +int cmd_show_tbf(struct cli_def *cli, char *command, char **argv, int argc) +{ + int i; + int count = 0; + + if (CLI_HELP_REQUESTED) + return CLI_HELP_NO_ARGS; + + if (!config->cluster_iam_master) { + cli_error(cli, "Can't do this on a slave. Do it on %s", + fmtaddr(config->cluster_master_address, 0)); + + return CLI_OK; + } + + if (!filter_list) + return CLI_OK; + + cli_print(cli,"%6s %5s %5s %6s %6s | %7s %7s %8s %8s %8s %8s", "TBF#", "Sid", "Rate", "Credit", "Queued", + "ByteIn","PackIn","ByteSent","PackSent", "PackDrop", "PackDelay"); + + for (i = 1; i < filter_list_size; ++i) { + if (!filter_list[i].sid) // Is it used? + continue; // No. + + cli_print(cli, "%5d%1s %5d %5d %6d %6d | %7d %7d %8d %8d %8d %8d", + i, (filter_list[i].next ? "*" : " "), + filter_list[i].sid, + filter_list[i].rate * 8, + filter_list[i].credit, + filter_list[i].queued, + + filter_list[i].b_queued, + filter_list[i].p_queued, + filter_list[i].b_sent, + filter_list[i].p_sent, + filter_list[i].p_dropped, + filter_list[i].p_delayed); + ++count; + } + cli_print(cli, "%d tbf entries used, %d total", count, filter_list_size); + return CLI_OK; +} diff --git a/tbf.h b/tbf.h new file mode 100644 index 0000000..925e4f7 --- /dev/null +++ b/tbf.h @@ -0,0 +1,45 @@ +#ifndef __TBF_H__ +#define __TBF_H__ + +// Need a time interval. + +#define TBF_MAX_QUEUE 2 // Maximum of 2 queued packet per +#define TBF_MAX_SIZE 3000 // Maxiumum queued packet size is 2048. + +#define TBF_MAX_CREDIT 6000 // Maximum 6000 bytes of credit. +#define TBF_RATE 360 // 360 bytes per 1/10th of a second. + +typedef struct { + int credit; + int lasttime; + int queued; + int oldest; // Position of packet in the ring buffer. + sessionidt sid; // associated session ID. + int max_credit; // Maximum amount of credit available (burst size). + int rate; // How many bytes of credit per second we get? (sustained rate) + void (*send)(sessionidt s, uint8_t *, int); // Routine to actually send out the data. + int prev; // Timer chain position. + int next; // Timer chain position. + + uint32_t b_queued; // Total bytes sent through this TBF + uint32_t b_sent; // Total bytes sucessfully made it to the network. + uint32_t p_queued; // ditto packets. + uint32_t p_sent; // ditto packets. + uint32_t b_dropped; // Total bytes dropped. + uint32_t p_dropped; // Total packets dropped. + uint32_t p_delayed; // Total packets not sent immediately. + + int sizes[TBF_MAX_QUEUE]; + uint8_t packets[TBF_MAX_QUEUE][TBF_MAX_SIZE]; +} tbft; + +void init_tbf(int num_tbfs); +int tbf_run_timer(void); +int tbf_queue_packet(int tbf_id, uint8_t * data, int size); +int new_tbf(int sid, int max_credit, int rate, void (*f)(sessionidt, uint8_t *, int)); +int free_tbf(int tid); +void fsck_tbfs(void); + +int cmd_show_tbf(struct cli_def *cli, char *command, char **argv, int argc); + +#endif /* __TBF_H__ */ diff --git a/test/README b/test/README new file mode 100644 index 0000000..356aa58 --- /dev/null +++ b/test/README @@ -0,0 +1,13 @@ +generateload, bounce + + L2TP load test. "generateload" simulates a LAC, each session + sends UDP packets to a specified IP address, which should be + running "bounce" to return the packets to LNS. + +radius + + RADIUS authentication load test. + +ping-sweep + + Send pings of varying sizes to a target host. diff --git a/test/bounce.c b/test/bounce.c new file mode 100644 index 0000000..5443b67 --- /dev/null +++ b/test/bounce.c @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PORT 39000 + +void sigalarm(int junk); +unsigned long long recv_count = 0; +unsigned long pps = 0; +unsigned long bytes = 0; +unsigned long dropped = 0, seq = 0; +unsigned port = PORT; + +int main(int argc, char *argv[]) +{ + int on = 1; + struct sockaddr_in addr; + int s; + char *packet; + + while ((s = getopt(argc, argv, "?p:")) > 0) + { + switch (s) + { + case 'p' : + port = atoi(optarg); + break; + case '?' : + printf("Options:\n"); + printf("\t-p port to listen on\n"); + return(0); + break; + } + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + if (bind(s, (void *) &addr, sizeof(addr)) < 0) + { + perror("bind"); + return -1; + } + + signal(SIGALRM, sigalarm); + alarm(1); + + printf("Waiting on port %d\n", port); + packet = (char *)malloc(65535); + while (1) + { + struct sockaddr_in addr; + int alen = sizeof(addr), l; + unsigned int iseq; + + l = recvfrom(s, packet, 65535, 0, (void *) &addr, &alen); + if (l < 0) continue; + recv_count++; + pps++; + bytes += l; + iseq = *((unsigned int *) packet); + if (seq != iseq) + dropped += (iseq - seq); + seq = iseq + 1; + + sendto(s, packet, l, 0, (struct sockaddr *)&addr, alen); + } + + free(packet); +} + +void sigalarm(int junk) +{ + printf("Recv: %10llu %0.1fMbits/s (%lu pps) (%5ld dropped)\n", recv_count, (bytes / 1024.0 / 1024.0 * 8), pps, dropped); + pps = bytes = 0; + alarm(1); +} + diff --git a/test/generateload.c b/test/generateload.c new file mode 100644 index 0000000..0b32199 --- /dev/null +++ b/test/generateload.c @@ -0,0 +1,1288 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PPPLCP 0xc021 +#define PPPPAP 0xc023 +#define PPPCHAP 0xc223 +#define PPPIPCP 0x8021 +#define PPPIP 0x0021 +#define PPPCCP 0x80fd + +#define CONFREQ 1 +#define CONFACK 2 +#define CONFNAK 3 +#define CONFREJ 4 +#define TERMREQ 5 +#define TERMACK 6 +#define CODEREJ 7 +#define PROTREJ 8 +#define ECHOREQ 9 +#define ECHOREP 10 +#define DISCREQ 11 + +#define PACKET_LENGTH 1000 +#define TARGET_PPS 5000 +#define TARGET "211.29.131.33" +#define GWADDR "211.29.131.30" +#define NUM_SESSIONS 1 +#define MAX_PACKETS 0 +#define AVG_SIZE 5 + +typedef unsigned short u16; +typedef unsigned int u32; +typedef unsigned char u8; + +char *lcp_codes[] = { + "reserved", + "CONFREQ", + "CONFACK", + "CONFNAK", + "CONFREJ", + "TERMREQ", + "TERMACK", + "CODEREJ", + "PROTREJ", + "ECHOREQ", + "ECHOREP", + "DISCREQ", +}; + +char *mtypes[] = { + "reserved", + "SCCRQ", + "SCCRP", + "SCCCN", + "StopCCN", // 4 + "reserved", + "HELLO", + "OCRQ", + "OCRP", + "OCCN", + "ICRQ", // 10 + "ICRP", + "ICCN", + "reserved", + "CDN", + "WEN", // 15 + "SLI", +}; + +char *attributes[] = { + "Message Type", // 0 + "Result Code", // 1 + "Protocol Version", // 2 + "Framing Capabilities", // 3 + "Bearer Capabilities", // 4 + "Tie Breaker", // 5 + "Firmware Revision", // 6 + "Host Name", // 7 + "Vendor Name", // 8 + "Assigned Tunnel ID", // 9 + "Receive Window Size", // 10 + "Challenge", // 11 + "Q.931 Cause Code", // 12 + "Challenge Response", // 13 + "Assigned Session ID", // 14 + "Call Serial Number", // 15 + "Minimum BPS", // 16 + "Maximum BPS", // 17 + "Bearer Type", // 18 (2 = Analog, 1 = Digital) + "Framing Type", // 19 (2 = Async, 1 = Sync) + "Reserved 20", // 20 + "Called Number", // 21 + "Calling Number", // 22 + "Sub Address", // 23 + "Tx Connect Speed", // 24 + "Physical Channel ID", // 25 + "Initial Received LCP CONFREQ", // 26 + "Last Sent LCP CONFREQ", // 27 + "Last Received LCP CONFREQ", // 28 + "Proxy Authen Type", // 29 + "Proxy Authen Name", // 30 + "Proxy Authen Challenge", // 31 + "Proxy Authen ID", // 32 + "Proxy Authen Response", // 33 + "Call Errors", // 34 + "ACCM", // 35 + "Random Vector", // 36 + "Private Group ID", // 37 + "Rx Connect Speed", // 38 + "Sequencing Required", // 39 +}; + +char *result_codes[] = { + "Reserved", + "General request to clear control connection", + "General error--Error Code indicates the problem", + "Control channel already exists", + "Requester is not authorized to establish a control channel", + "The protocol version of the requester is not supported", + "Requester is being shut down", + "Finite State Machine error", +}; + +char *error_codes[] = { + "No general error", + "No control connection exists yet for this LAC-LNS pair", + "Length is wrong", + "One of the field values was out of range or reserved field was non-zero", + "Insufficient resources to handle this operation now", + "The Session ID is invalid in this context", + "A generic vendor-specific error occurred in the LAC", + "Try another LNS", + "Session or tunnel was shutdown due to receipt of an unknown AVP with the M-bit set", +}; + + +typedef struct +{ + char buf[4096]; + int length; +} controlt; + +typedef struct avp_s +{ + int length; + int type; + struct avp_s *next; + char value[1024]; +} avp; + +typedef struct +{ + int length; + u16 session; + u16 tunnel; + u16 ns; + u16 nr; + u16 mtype; + char *buf; + avp *first; + avp *last; +} control_message; + +typedef struct { +unsigned long long send_count , recv_count ; +unsigned long long spkt , rpkt ; +unsigned int dropped; +unsigned long sbytes , rbytes ; +int quitit; +struct sessiont +{ + short remote_session; + char open; + int ppp_state; + unsigned char ppp_identifier; + int addr; +} sessions[65536]; + +int active_sessions ; +} sharedt; + +sharedt * ss; + +void controlsend(controlt * c, short t, short s); +void controlnull(short t); +controlt *controlnew(u16 mtype); +void controls(controlt * c, u16 avp, char *val, u8 m); +void control16(controlt * c, u16 avp, u16 val, u8 m); +void control32(controlt * c, u16 avp, u32 val, u8 m); +void controlfree(controlt *c); +control_message *parsecontrol(char *buf, int length); +void dump_control_message(control_message *c); +u32 avp_get_32(control_message *c, int id); +u16 avp_get_16(control_message *c, int id); +char *avp_get_s(control_message *c, int id); +void reader_thread(int udpfd); +void skip_zlb(); +void cm_free(control_message *m); +controlt *ppp_new(u16 session, int protocol); +void ppp_free(controlt *packet); +controlt *ppp_lcp(u16 s, unsigned char type, char identifier); +controlt *ppp_ipcp(u16 s, unsigned char type, char identifier); +void ppp_send(controlt *c); +void ppp_add_16(controlt * c, u16 val); +void ppp_add_32(controlt * c, u32 val); +void ppp_add_s(controlt * c, char *val); +void ppp_lcp_add_option(controlt *c, unsigned char option, unsigned char length, int data); +void dump_ppp_packet(char *packet, int l); +controlt *ppp_pap(u16 s, unsigned char type, char identifier, char *username, char *password); +char *inet_toa(unsigned long addr); +__u16 checksum(unsigned char *addr, int count); +void sigalarm(int junk); +void sigint(int signal); +void clean_shutdown(); +void print_report(); + +int ns = 0, nr = 0; +int udpfd; +int t = 0; +struct sockaddr_in gatewayaddr = {0}; +int numsessions = NUM_SESSIONS; +int packet_length = PACKET_LENGTH; +int target_pps = TARGET_PPS; +char *target = TARGET; +char *gwaddr = GWADDR; +int max_packets = MAX_PACKETS; +int ppsend; +int do_init = 1; +char **session_usernames; +char *base_username = "dslloadtest"; +char *base_password = "testing"; +char *suffix = "@optusnet.com.au"; + +int main(int argc, char *argv[]) +{ + int s; + char *packet; + + ss = (sharedt*) mmap(NULL, sizeof(*ss), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, 0, 0); + + // Process Arguments {{{ + while ((s = getopt(argc, argv, "?hs:g:l:p:m:t:nU:P:")) > 0) + { + switch (s) + { + case 's' : + numsessions = atoi(optarg); + if (numsessions <= 0) + { + printf("You must have at least 1 session\n"); + return -1; + } + break; + case 'l' : + packet_length = atoi(optarg); + if (packet_length < 64) + { + printf("You must have at least 64 byte packets\n"); + return -1; + } + break; + case 'n' : + do_init = 0; + break; + case 'p' : + target_pps = atoi(optarg); + break; + case 'm' : + max_packets = atoi(optarg); + if (max_packets < 50) + { + printf("You must send at least 50 packets.\n"); + return -1; + } + break; + case 't' : + target = strdup(optarg); + break; + case 'g' : + gwaddr = strdup(optarg); + break; + case 'U' : + base_username = strdup(optarg); + break; + case 'P' : + base_password = strdup(optarg); + break; + case 'h' : + case '?' : + printf("Options:\n"); + printf("\t-s number of ss->sessions\n"); + printf("\t-l packet length\n"); + printf("\t-p target pps\n"); + printf("\t-m maximum number of packets\n"); + printf("\t-t target IP address\n"); + printf("\t-g gateway IP address\n"); + printf("\t-U username (or base if multiple)\n"); + printf("\t-P password\n"); + return(0); + break; + } + } + if (target_pps) + ppsend = target_pps / 50; + else + ppsend = 0; + + packet = calloc(4096, 1); + + memset(ss->sessions, 0, sizeof(ss->sessions)); + + if (do_init) + printf("Creating %d ss->sessions to %s\n", numsessions, gwaddr); + printf("Targeting %d packets per second\n", target_pps); + if (max_packets) printf("Sending a maximum of %d packets\n", max_packets); + printf("Sending packets to %s\n", target); + printf("Sending %d byte packets\n", packet_length); + + session_usernames = (char **)calloc(sizeof(char *), numsessions); + if (numsessions > 1) + { + int sul = strlen(base_username) + 10; + int i; + + for (i = 0; i < numsessions; i++) + { + session_usernames[i] = (char *)calloc(sul, 1); + snprintf(session_usernames[i], sul, "%s%d", base_username, i+1); + } + } + else + { + session_usernames[0] = strdup(base_username); + } + // }}} + + // Create socket/*{{{*/ + { + int on = 1; + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(38001); + + udpfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (udpfd <= 0) + { + perror("socket"); + return -1; + } + + setsockopt(udpfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + if (bind(udpfd, (void *) &addr, sizeof(addr)) < 0) + { + perror("bind"); + return -1; + } + + printf("Bound to port %d\n", htons(addr.sin_port)); + }/*}}}*/ + + gatewayaddr.sin_family = AF_INET; + gatewayaddr.sin_port = htons(1701); + inet_aton(gwaddr, &gatewayaddr.sin_addr); + + // Create tunnel/*{{{*/ + if (do_init) { + controlt *c; + control_message *r; + + c = controlnew(1); // SCCRQ + controls(c, 7, "loadtest", 0); // Tunnel Hostname + controls(c, 8, "OIE", 0); // Vendor Name + control16(c, 9, 1, 0); // Assigned Tunnel ID + control16(c, 2, 256, 0); // Version 1.0 + control16(c, 3, 1, 0); // Framing (Async) + control16(c, 4, 1, 0); // Bearer (Digital) + control16(c, 10, 20, 0); // Receive Window Size + controlsend(c, 0, 0); + controlfree(c); + + // Receive reply/*{{{*/ + { + struct sockaddr_in addr; + int alen = sizeof(addr), l; + + l = recvfrom(udpfd, packet, 4096, 0, (void *) &addr, &alen); + if (l < 0) + { + printf("Error creating tunnel: %s\n", strerror(errno)); + return -1; + } + printf("Received "); + r = parsecontrol(packet, l); + if (!r->first) + { + printf("Invalid packet.. no first avp\n"); + return -1; + } + + printf("Assigned tunnel: %d\n", t = avp_get_16(r, 9)); + cm_free(r); + + c = controlnew(3); // SCCCN + controlsend(c, t, 0); + controlfree(c); + skip_zlb(); + }/*}}}*/ + }/*}}}*/ + + + // Create ss->sessions/*{{{*/ + if (do_init) + { + for (s = 1; s <= numsessions; s++) + { + controlt *c; + + c = controlnew(10); // ICRQ + controls(c, 21, "12356", 0); // Called Number + controls(c, 22, "000", 0); // Calling Number + control16(c, 14, s, 0); // Assigned Session ID + controlsend(c, t, 0); + controlfree(c); + usleep(15000); // 15 ms + } + } + printf("All session create requests sent...\n");/*}}}*/ + + if ( fork() == 0) { + reader_thread(udpfd); + exit(0); + } + + { + char tmp[512]; + fprintf(stderr, "Press enter to begin sending traffic\n"); + fgets(tmp, 512, stdin); + } + + fprintf(stderr, "Beginning sending traffic through %d ss->sessions\n", ss->active_sessions); + printf(" TS: Total Packets Sent\n"); + printf(" TL: Total Packets Lost\n"); + printf(" PL: Packet Loss\n"); + printf(" SS: Send Speed\n"); + printf(" RS: Receive Speed\n"); + printf(" SP: Packets/Second Sent\n"); + printf(" RP: Packets/Second Received\n"); + printf(" NS: Number of active ss->sessions\n"); + + signal(SIGALRM, sigalarm); + signal(SIGINT, sigint); + alarm(1); + + // Traffic generation loop {{{ + { + struct sockaddr_in to; + struct iphdr *iph; + struct udphdr *udph; + char *data; + int len = 0; + unsigned int seq = 0; + controlt *c; + + // Get address + memset(&to, 0, sizeof(struct sockaddr_in)); + to.sin_family = AF_INET; + inet_aton(target, &to.sin_addr); + + c = ppp_new(1, PPPIP); + + iph = (struct iphdr *)(c->buf + c->length); + udph = (struct udphdr *)(c->buf + c->length + sizeof(struct iphdr)); + data = (char *)(c->buf + c->length + sizeof(struct iphdr) + sizeof(struct udphdr)); + len = sizeof(struct iphdr) + sizeof(struct udphdr); + c->length += len; + + //IP + c->length += sizeof(struct iphdr); + iph->tos = 0; + iph->id = ntohs(1); + iph->frag_off = ntohs(1 << 14); + iph->ttl = 30; + iph->check = 0; + iph->version = 4; + iph->ihl = 5; + iph->protocol = 17; + memcpy(&iph->daddr, &to.sin_addr, sizeof(iph->daddr)); + + // UDP + udph->source = ntohs(39999); + udph->dest = ntohs(39000); + udph->check = 0; + + // Data + memset(data, 64, 1500); + + udph->len = ntohs(sizeof(struct udphdr) + packet_length); + iph->tot_len = ntohs(len + packet_length); + c->length += packet_length; + + while (!ss->quitit && ss->active_sessions) + { + int i; + for (i = 1; i <= numsessions && !ss->quitit; i++) + { + // Skip ss->sessions that aren't active yet + if (!ss->sessions[i].open || ss->sessions[i].ppp_state != 2) + continue; + + *(u16 *)(c->buf + 4) = htons(ss->sessions[i].remote_session); // Session ID + iph->saddr = ss->sessions[i].addr; + iph->check = 0; + iph->check = ntohs(checksum((char *)iph, sizeof(struct iphdr))); + + *((unsigned int *) data) = seq++; + ppp_send(c); + + ss->send_count++; + ss->spkt++; + ss->sbytes += c->length; + + if (ppsend && ss->send_count % ppsend == 0) + { + struct timespec req; + req.tv_sec = 0; + req.tv_nsec = 5 * 1000 * 1000; + nanosleep(&req, NULL); + } + + if (max_packets && ss->send_count >= max_packets) ss->quitit++; + } + } + + c->length -= packet_length; + + }/*}}}*/ + + clean_shutdown(); + print_report(); + + close(udpfd); + return 0; +} + +void print_report() +{ + float loss; + + loss = 100 - (((ss->recv_count * 1.0) / (ss->send_count * 1.0)) * 100.0); + + printf("\n"); + printf("Total Packets Sent: %llu\n", ss->send_count); + printf("Total Packets Received: %llu\n", ss->recv_count); + printf("Overall Packet Loss: %0.2f%%", loss); + printf("\n"); +} + +void clean_shutdown()/*{{{*/ +{ + int i; + for (i = 0; i < numsessions; i++) + { + // Close Session + controlt *c; + + if (!ss->sessions[i].open) continue; + c = controlnew(14); // CDN + control16(c, 14, i, 0); // Assigned Session ID + control16(c, 1, 1, 0); // Result Code + controlsend(c, t, ss->sessions[i].remote_session); + controlfree(c); + } + + // Close Tunnel + { + controlt *c; + + c = controlnew(4); // StopCCN + control16(c, 9, 1, 0); // Assigned Tunnel ID + control16(c, 1, 1, 0); // Result Code + controlsend(c, t, 0); + controlfree(c); + } +}/*}}}*/ + +void sigint(int signal) +{ + ss->quitit++; +} + +void sigalarm(int junk) +{ + static unsigned long long last_rpkts[AVG_SIZE], last_spkts[AVG_SIZE]; + static int last = 0, avg_count = 0; + register unsigned int avg_s = 0, avg_r = 0, i; + float loss; + + last_rpkts[last] = ss->rpkt; + last_spkts[last] = ss->spkt; + last = (last + 1) % AVG_SIZE; + if (avg_count < AVG_SIZE) avg_count++; + + for (i = 0; i < avg_count; i++) + { + avg_s += last_spkts[i]; + avg_r += last_rpkts[i]; + } + avg_s /= avg_count; + avg_r /= avg_count; + + loss = 100 - (((avg_r * 1.0) / (avg_s * 1.0)) * 100.0); + fprintf(stderr, "TS:%llu TL:%lld DR:%4d PL:%-3.2f%% SS:%0.1fMbits/s RS:%0.1fMbits/s NS:%u SP:%u RP:%u\n", + ss->send_count, ss->send_count-ss->recv_count, ss->dropped, loss, + (ss->sbytes/1024.0/1024.0*8), (ss->rbytes/1024.0/1024.0*8), + ss->active_sessions, + avg_s, avg_r); + + ss->spkt = ss->rpkt = 0; + ss->sbytes = ss->rbytes = 0; + alarm(1); +} + +__u16 checksum(unsigned char *addr, int count) +{ + register long sum = 0; + + for (; count > 1; count -= 2) + { + sum += ntohs(*(u16 *)addr); + addr += 2; + } + + if (count > 0) sum += *(unsigned char *)addr; + + // take only 16 bits out of the 32 bit sum and add up the carries + if (sum >> 16) + sum = (sum & 0xFFFF) + (sum >> 16); + + // one's complement the result + sum = ~sum; + + return ((u16) sum); +} + +// Control Stuff {{{ +void control16(controlt * c, u16 avp, u16 val, u8 m) +{ + u16 l = (m ? 0x8008 : 0x0008); + *(u16 *) (c->buf + c->length + 0) = htons(l); + *(u16 *) (c->buf + c->length + 2) = htons(0); + *(u16 *) (c->buf + c->length + 4) = htons(avp); + *(u16 *) (c->buf + c->length + 6) = htons(val); + c->length += 8; +} + +// add an AVP (32 bit) +void control32(controlt * c, u16 avp, u32 val, u8 m) +{ + u16 l = (m ? 0x800A : 0x000A); + *(u16 *) (c->buf + c->length + 0) = htons(l); + *(u16 *) (c->buf + c->length + 2) = htons(0); + *(u16 *) (c->buf + c->length + 4) = htons(avp); + *(u32 *) (c->buf + c->length + 6) = htonl(val); + c->length += 10; +} + +// add an AVP (32 bit) +void controls(controlt * c, u16 avp, char *val, u8 m) +{ + u16 l = ((m ? 0x8000 : 0) + strlen(val) + 6); + *(u16 *) (c->buf + c->length + 0) = htons(l); + *(u16 *) (c->buf + c->length + 2) = htons(0); + *(u16 *) (c->buf + c->length + 4) = htons(avp); + memcpy(c->buf + c->length + 6, val, strlen(val)); + c->length += 6 + strlen(val); +} + +// new control connection +controlt *controlnew(u16 mtype) +{ + controlt *c; + c = calloc(sizeof(controlt), 1); + c->length = 12; + control16(c, 0, mtype, 1); + return c; +} + +void controlnull(short t) +{ + controlt *c; + c = calloc(sizeof(controlt), 1); + c->length = 12; + controlsend(c, t, 0); + controlfree(c); + ns--; +} + +// add a control message to a tunnel, and send if within window +void controlsend(controlt * c, short t, short s) +{ + *(u16 *) (c->buf + 0) = htons(0xC802); // flags/ver + *(u16 *) (c->buf + 2) = htons(c->length); // length + *(u16 *) (c->buf + 4) = htons(t); // tunnel + *(u16 *) (c->buf + 6) = htons(s); // session + *(u16 *) (c->buf + 8) = htons(ns++); // sequence + *(u16 *) (c->buf + 10) = htons(nr); // sequence +// printf("Sending "); +// cm_free(parsecontrol(c->buf, c->length)); + sendto(udpfd, c->buf, c->length, 0, (struct sockaddr *)&gatewayaddr, sizeof(gatewayaddr)); +} + +void controlfree(controlt *c) +{ + if (!c) return; + free(c); +} + +control_message *parsecontrol(char *buf, int length) +{ + char *p = buf; + control_message *c; + + c = calloc(sizeof(control_message), 1); + c->buf = buf; + c->length = length; + + c->tunnel = ntohs(*(u16 *)(buf + 4)); + c->session = ntohs(*(u16 *)(buf + 6)); + c->ns = ntohs(*(u16 *)(buf + 8)); + c->nr = nr = ntohs(*(u16 *)(buf + 10)); + p += 12; + while ((p - buf) < length) + { + avp *a = calloc(sizeof(avp), 1); + a->length = ntohs(*(short *)(p)) & 0x3FF; + a->type = ntohs(*(short *)(p + 4)); + memcpy(a->value, p + 6, a->length - 6); + if (a->type == 0) c->mtype = ntohs(*(short *)a->value); + p += a->length; + if (c->last) + c->last->next = a; + else + c->first = a; + c->last = a; + } + if (c->first) + dump_control_message(c); + return c; +} + +void dump_control_message(control_message *c) +{ + avp *a; + printf("Control Message (type=%u s=%u t=%d ns=%d nr=%d)\n", c->mtype, c->session, c->tunnel, c->ns, c->nr); + for (a = c->first; a; a = a->next) + { + printf(" avp: %s, len: %d", attributes[a->type], a->length - 6); + switch (a->type) + { + // Short + case 6 : + case 9 : + case 10 : + case 39 : + case 14 : printf(", value: %u\n", ntohs(*(short *)a->value)); + break; + + // Integer + case 16 : + case 17 : + case 24 : + case 25 : + case 38 : + case 15 : printf(", value: %u\n", ntohl(*(u32 *)a->value)); + break; + + // String + case 7 : + case 21 : + case 22 : + case 23 : + case 37 : + case 8 : printf(", value: \"%s\"\n", a->value); + break; + + case 2 : printf(", value: %d.%d\n", *(char *)a->value, *(char *)a->value + 1); + break; + case 0 : printf(", value: %s\n", mtypes[ntohs(*(short *)a->value)]); + break; + case 19 : + case 3 : printf(", value: (%d) %s %s\n", ntohl(*(u32 *)a->value), + (ntohl(*(u32 *)a->value) & 0x01) ? "synchronous" : "", + (ntohl(*(u32 *)a->value) & 0x02) ? "asynchronous" : ""); + break; + case 18 : + case 4 : printf(", value: (%d) %s %s\n", ntohl(*(u32 *)a->value), + (ntohl(*(u32 *)a->value) & 0x01) ? "digital" : "", + (ntohl(*(u32 *)a->value) & 0x02) ? "analog" : ""); + break; + + default : printf("\n"); + break; + } + } + printf("\n"); +} + +u16 avp_get_16(control_message *c, int id) +{ + avp *a; + + for (a = c->first; a; a = a->next) + if (a->type == id) return ntohs(*(short *)a->value); + return 0; +} + +u32 avp_get_32(control_message *c, int id) +{ + avp *a; + + for (a = c->first; a; a = a->next) + if (a->type == id) return ntohl(*(u32 *)a->value); + return 0; +} + +char *avp_get_s(control_message *c, int id) +{ + avp *a; + + for (a = c->first; a; a = a->next) + if (a->type == id) return (char *)a->value; + return 0; +} + +void cm_free(control_message *m) +{ + avp *a, *n; + + for (a = m->first; a; ) + { + n = a->next; + free(a); + a = n; + } + + free(m); +} + +// }}} + +void reader_thread(int updfd)/*{{{*/ +{ + unsigned char *packet; + unsigned int seq = 0; + + printf("Starting reader thread\n"); + packet = malloc(4096); + while (!ss->quitit) + { + struct sockaddr_in addr; + int alen = sizeof(addr); + control_message *m; + int l; + int s; + int pfc = 0; + +// memset(packet, 0, 4096); + if ((l = recvfrom(udpfd, packet, 4096, 0, (void *) &addr, &alen)) < 0) break; + ss->rbytes += l; + if (!do_init) + { + ss->recv_count++; + ss->rpkt++; + continue; + } + if (l < 12) + { + printf("Short packet received: %d bytes\n", l); + } + s = ntohs(*(u16 *)(packet + 4)); + if (!s) + { + printf("Invalid session ID\n"); + continue; + } + if (packet[0] == 0xc8) + { + // Control Packet + printf("Reader Received "); + m = parsecontrol(packet, l); + printf("\n"); + s = m->session; + + switch (m->mtype) + { + case 4 : printf("StopCCN\n"); + printf("Killing tunnel %d\n", avp_get_16(m, 9)); + ss->quitit++; + break; + case 6 : printf("HELLO, sending ZLB ACK\n"); + controlnull(t); + break; + case 11 : + { + controlt *c; + + printf("Received ICRP. Responding with CONFREQ\n"); + + ss->sessions[s].remote_session = avp_get_16(m, 14); + ss->sessions[s].open = 1; + ss->sessions[s].ppp_state = 1; + + c = controlnew(12); // ICCN + controlsend(c, t, ss->sessions[s].remote_session); + controlfree(c); + + c = ppp_lcp(s, CONFREQ, 0); + ppp_lcp_add_option(c, 1, 2, htons(1500)); // MRU = 1400 + ppp_lcp_add_option(c, 3, 2, htons(0xC023)); // Authentication Protocol - PAP + ppp_send(c); + controlfree(c); + break; + } + case 14 : { + int s; + printf("CDN\n"); + s = avp_get_16(m, 14); + printf("Killing session %d\n", s); + ss->sessions[s].open = 0; + ss->sessions[s].ppp_state = 0; + ss->active_sessions--; + controlnull(t); + break; + } + + } + if (m->mtype == 4) + { + printf("StopCCN Received.. Dieing\n"); + ss->quitit++; + break; + } + cm_free(m); + } + else + { + // Data Packet + unsigned short protocol = ntohs(*(u16 *)(packet + 6)); + + if (protocol == 0xff03) + { + pfc = 2; + packet += 2; + protocol = ntohs(*(u16 *)(packet + 6)); + } + if (protocol != PPPIP) + { + printf("Received "); + dump_ppp_packet(packet + 6, l - 6); + } + + if (protocol == PPPLCP) + { + controlt *r; + unsigned char ppp_id = *(char *)(packet + 9); + + switch (*(char *)(packet + 8)) + { + case CONFREQ : + r = ppp_lcp(s, CONFACK, ppp_id); + ppp_send(r); + break; + case CONFACK : + r = ppp_pap(s, CONFREQ, 0, session_usernames[s-1], base_password); + ppp_send(r); + break; + case TERMREQ : + r = ppp_lcp(s, TERMACK, ppp_id); + ppp_send(r); + break; + case ECHOREQ : + r = ppp_lcp(s, ECHOREP, ppp_id); + ppp_add_32(r, 0); + ppp_send(r); + break; + } + } + else if (protocol == PPPIPCP) + { + controlt *r; + int taddr = 0; + u32 address = *(u32 *)(packet + 14); + + switch (*(char *)(packet + 8)) + { + case CONFREQ : + r = ppp_ipcp(s, CONFREQ, time(NULL) % 255); + ppp_lcp_add_option(r, 3, 4, htonl(taddr)); // Request 0.0.0.0 + ppp_send(r); + controlfree(r); + r = ppp_ipcp(s, CONFACK, time(NULL) % 255); + ppp_lcp_add_option(r, 3, 4, address); // ACK gateway IP + ppp_send(r); + controlfree(r); + break; + case CONFNAK : + // Request whatever address we are given - it's ours + r = ppp_ipcp(s, CONFREQ, time(NULL) % 255); + ppp_lcp_add_option(r, 3, 4, address); + ppp_send(r); + controlfree(r); + printf("Session %d: %s\n", s, inet_toa(address)); + ss->sessions[s].ppp_state = 2; + ss->sessions[s].addr = address; + ss->active_sessions++; + break; + case CONFACK : + printf("Conf-Ack Received\n"); + break; + case TERMREQ : + printf("Term-Req Received\n"); + break; + case ECHOREQ : + printf("Echo-Req Received\n"); + break; + case ECHOREP : + printf("Echo-Rep Received\n"); + break; + } + } + else if (protocol == PPPPAP) + { + if (*(u16 *)(packet + 8) == 3) + { + controlt *c; + printf("Closing Connection\n"); + + c = controlnew(14); // CDN + control16(c, 14, ss->sessions[s].remote_session, 0); // Assigned Session ID + controlsend(c, t, 0); + controlfree(c); + ss->sessions[s].open = 0; + } + } + else if (protocol == PPPIP) + { + struct iphdr *iph = (struct iphdr *)(packet + 8); + char * data = (char*) (packet + 8 + sizeof(struct iphdr) + sizeof(struct udphdr)); + if (!ss->sessions[s].open) + { + printf("Packet for closed session %d\n", s); + continue; + } + + if (iph->protocol == 17) + { + int iseq; + ss->recv_count++; + ss->rpkt++; + iseq = *((unsigned int *) data); + if (seq != iseq) { + ss->dropped += (iseq - seq) ; + } + seq = iseq + 1; // Next sequence number to expect. + } + } + } + packet -= pfc; + } + free(packet); + + printf("Closing reader thread\n"); + +}/*}}}*/ + +void skip_zlb() /*{{{*/ +{ + struct sockaddr_in addr; + int alen = sizeof(addr); + char buf[1024]; + int l; + l = recvfrom(udpfd, buf, 1024, MSG_PEEK, (void *) &addr, &alen); + if (l < 0) + { + printf("recvfrom: %s\n", strerror(errno)); + return; + } + if (l <= 12) + { + printf("Skipping ZLB (l=%d)\n", l); + recvfrom(udpfd, buf, 1024, 0, (void *) &addr, &alen); + } +} +/*}}}*/ + +// PPP Stuff {{{ +controlt *ppp_new(u16 session, int protocol) +{ + controlt *c = calloc(sizeof(controlt), 1); + *(u16 *)(c->buf + 4) = htons(ss->sessions[session].remote_session); // Tunnel + *(u16 *)(c->buf + 6) = htons(protocol); + c->length += 8; + + return c; +} + +void ppp_free(controlt *c) +{ + free(c); +} + +controlt *ppp_lcp(u16 s, unsigned char type, char identifier) +{ + controlt *c; + + if (!identifier) identifier = ss->sessions[s].ppp_identifier++; + c = ppp_new(s, PPPLCP); + *(char *)(c->buf + c->length + 0) = type; + *(char *)(c->buf + c->length + 1) = identifier; + *(u16 *)(c->buf + c->length + 2) = ntohs(4); + c->length += 4; + + return c; +} + +controlt *ppp_ipcp(u16 s, unsigned char type, char identifier) +{ + controlt *c; + + if (!identifier) identifier = ss->sessions[s].ppp_identifier++; + c = ppp_new(s, PPPIPCP); + *(char *)(c->buf + c->length + 0) = type; + *(char *)(c->buf + c->length + 1) = identifier; + *(u16 *)(c->buf + c->length + 2) = ntohs(4); + c->length += 4; + + return c; +} + +controlt *ppp_pap(u16 s, unsigned char type, char identifier, char *username, char *password) +{ + controlt *c; + + if (!identifier) identifier = ss->sessions[s].ppp_identifier++; + c = ppp_new(s, PPPPAP); + *(char *)(c->buf + c->length + 0) = type; + *(char *)(c->buf + c->length + 1) = identifier; + *(u16 *)(c->buf + c->length + 2) = ntohs(4); + c->length += 4; + + *(char *)(c->buf + c->length) = strlen(username) + strlen(suffix); + memcpy((c->buf + c->length + 1), username, strlen(username)); + memcpy((c->buf + c->length + 1 + strlen(username)), suffix, strlen(suffix)); + c->length += strlen(username) + 1 + strlen(suffix); + + *(char *)(c->buf + c->length) = strlen(password); + memcpy((c->buf + c->length + 1), password, strlen(password)); + c->length += strlen(password) + 1; + + return c; +} + +void ppp_send(controlt *c) +{ + *(u16 *)(c->buf + 0) = htons(0x0002); // flags/ver + *(u16 *)(c->buf + 2) = htons(t); // tunnel + *(u16 *)(c->buf + 10) = ntohs(c->length - 8); + if (sendto(udpfd, c->buf, c->length, 0, (struct sockaddr *)&gatewayaddr, sizeof(gatewayaddr)) < 0) + perror("sendto"); + if (htons(*(u16 *)(c->buf + 6)) != PPPIP) + { + printf("PPP Sending "); + dump_ppp_packet(c->buf + 6, c->length - 6); + } +} + +void ppp_add_16(controlt *c, u16 val) +{ + *(u16 *) (c->buf + c->length) = htons(val); + c->length += 2; +} + +void ppp_add_32(controlt *c, u32 val) +{ + *(u32 *) (c->buf + c->length) = htons(val); + c->length += 4; +} + +void ppp_add_s(controlt *c, char *val) +{ + memcpy(c->buf + c->length, val, strlen(val)); + c->length += strlen(val); +} + +void ppp_lcp_add_option(controlt *c, unsigned char option, unsigned char length, int data) +{ + *(char *)(c->buf + c->length + 0) = option; + *(char *)(c->buf + c->length + 1) = length + 2; + memcpy(c->buf + c->length + 2, &data, length); + c->length += 2 + length; +} + +void dump_ppp_packet(char *packet, int l) +{ + char *p = packet; + int protocol ; + if (*(unsigned char *)p == 0xff) p += 2; + protocol = ntohs(*(u16 *)(p)); + printf("PPP Packet\n"); + switch (protocol) + { + case PPPCCP : printf(" Protocol: PPPCCP\n"); break; + } + if (protocol == PPPLCP) + { + printf(" Protocol: PPPLCP\n"); + printf(" LCP Code: %s\n", lcp_codes[*(u8 *)(p + 2)]); + } + else if (protocol == PPPPAP) + { + printf(" Protocol: PPPPAP\n"); + if (*(char *)(p + 2) == 2) + { + printf(" Authentication accepted\n"); + } + else if (*(char *)(p + 2) == 3) + { + printf(" Authentication denied\n"); + } + } + else if (protocol == PPPIPCP) + { + printf(" Protocol: PPPIPCP\n"); + printf(" IPCP Code: %s\n", lcp_codes[*(u8 *)(p + 2)]); + printf(" Address: %s\n", inet_toa(*(u32 *)(p + 8))); + } + else if (protocol == PPPIP) + { + struct iphdr *iph; + struct protoent *pr; + + iph = (struct iphdr *)(p + 2); + + printf(" Protocol: PPPIP\n"); + printf(" Length: %d\n", l); + printf(" IP Version: %d\n", iph->version); + if (iph->version != 4) return; + pr = getprotobynumber(iph->protocol); + printf(" IP Header Length: %d\n", iph->ihl); + printf(" IP TTL: %d\n", iph->ttl); + printf(" IP Protocol: %s (%d)\n", (pr ? pr->p_name : "unknown"), iph->protocol); + printf(" IP Checksum: %x\n", ntohs(iph->check)); + } + else + { + printf(" Protocol: unknown 0x%x\n", protocol); + } + printf("\n"); +} + +char *inet_toa(unsigned long addr) +{ + struct in_addr in; + memcpy(&in, &addr, sizeof(unsigned long)); + return inet_ntoa(in); +} + +// }}} + diff --git a/test/ping-sweep b/test/ping-sweep new file mode 100644 index 0000000..2e8b483 --- /dev/null +++ b/test/ping-sweep @@ -0,0 +1,121 @@ +#! /usr/bin/perl -w + +# Ping test: run through packet sizes (default: 56-3000) + +use strict; +use Socket; + +use constant TRIES => 4; +use constant TIMEOUT => 3; # 3s +use constant MAXPACK => 16*1024; + +use constant ICMP_TYPE_ECHOREPLY => 0; # ICMP packet types +use constant ICMP_TYPE_ECHO => 8; +use constant ICMP_CODE => 0; # No ICMP code for ECHO and ECHOREPLY + +use constant SOL_IP => 0; +use constant IP_MTU_DISCOVER => 10; +use constant IP_PMTUDISC_DONT => 0; +use constant IP_PMTUDISC_WANT => 1; +use constant IP_PMTUDISC_DO => 2; + +my $verbose = shift if @ARGV and $ARGV[0] =~ /^--?v(erbose)?$/; +my ($host, $min, $max) = @ARGV; + +die "Usage: $0 [-v] HOST [MIN [MAX]]\n" unless $host; +my $addr = inet_aton $host or die "$0: invalid host $host\n"; +my $sin = sockaddr_in 0, $addr; + +$min = 56 if @ARGV < 2; +$max = 3000 if @ARGV < 3; +$max = $min if $min > $max; + +my $icmp = getprotobyname 'icmp' or die "$0: can't get ICMP proto ($!)\n"; +socket my $sock, PF_INET, SOCK_RAW, $icmp + or die "$0: can't create ICMP socket ($!)\n"; + +setsockopt $sock, SOL_IP, IP_MTU_DISCOVER, IP_PMTUDISC_DONT + or die "$0: can't disable PMTU discovery ($!)\n"; + +{ + my $seq = 0; + sub icmp_out + { + my $len = shift; + + # fill data with the *$len*$len*$len*... + my $d = sprintf '*%d', $len; + my $data = $d x (int ($len / length $d) + 1); + + my $s = 0 + $seq++; + $seq %= 65536; + + my $pack = pack "C C n n n a$len" => + ICMP_TYPE_ECHO, # icmp_type + ICMP_CODE, # icmp_code + 0, # icmp_cksum + $$, # icmp_id + $s, # icmp_seq + $data; # payload + + my $cksum = 0; + $cksum += $_ for unpack 'n*' => $pack . "\x00"; + my $wrap; + $cksum = ($cksum & 0xffff) + $wrap while ($wrap = ($cksum >> 16)); + + substr $pack, 2, 2, pack n => ~$cksum; + ($s, $pack); + } +} + +sub icmp_in +{ + my ($pack, $seq) = @_; + return unless length $pack >= 28; + my ($type, $code, $cksum, $id, $s) = unpack 'C C n n n' => substr $pack, 20; + return $type == ICMP_TYPE_ECHOREPLY + and $code == ICMP_CODE + and $id == $$ + and $s == $seq; +} + +$|++ if $verbose; + +for (my $size = $min; $size <= $max; $size++) +{ + my ($seq, $pack) = icmp_out $size; + + print "$size: " if $verbose; + my $res = 0; + + for (my $t = 0; $t < TRIES; $t++) + { + send $sock, $pack, 0, $sin + or die "$0: sendto failed ($!)\n"; + + my $rin = ''; + (vec $rin, fileno $sock, 1) = 1; + select $rin, undef, undef, TIMEOUT or next; + + my $peer = recv $sock, my $buf, MAXPACK, 0 + or die "$0: recvfrom failed ($!)\n"; + + next unless (sockaddr_in $peer)[1] eq $addr + and icmp_in $buf, $seq; + + # OK + $res++; + last; + } + + if ($verbose) + { + print +($res ? 'OK' : 'FAIL'), "\n"; + } + else + { + print "$size\n" unless $res; + } +} + +1; diff --git a/test/radius.c b/test/radius.c new file mode 100644 index 0000000..e31ce60 --- /dev/null +++ b/test/radius.c @@ -0,0 +1,723 @@ +/* RADIUS authentication load test */ + +#define _SVID_SOURCE +#define _POSIX_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../md5.h" + +extern char *optarg; +extern int optind; + +struct user { + char *user; + char *pass; + int flags; +#define F_FAKE 1 +#define F_BAD 2 +#define F_USED 4 + char *request; + int request_len; + struct user *next; +}; + +typedef uint32_t u32; + +struct user_list { + struct user *entry; + int attempts; + int response; + u32 begin; + u32 retry; + u32 end; +}; + +struct stats { + int total; + int out; + int in; + int err; + int ready; +}; + +enum { + AccessRequest = 1, + AccessAccept, + AccessReject, + AccessFail = 99 +}; + +#define USAGE "Usage: %s [-i input] [-n instances] [-f fake] [-b bad] " \ + "[-l limit] server port secret\n" + +#define MAX_ATTEMPTS 5 + +void *xmalloc(size_t size) +{ + void *p = malloc(size); + if (!p) + { + fprintf(stderr, "out of memory allocating %d bytes\n", size); + exit(1); + } + + return p; +} + +char *xstrdup(char *s) +{ + int l = strlen(s); + char *p = xmalloc(l + 1); + return strcpy(p, s); +} + +void *xmmap(size_t size) +{ + void *p = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, 0, 0); + + if (p == MAP_FAILED) + { + fprintf(stderr, "out of memory allocating %d shared bytes\n", size); + exit(1); + } + + return p; +} + +void logmsg(char *fmt, ...) +{ + static int new = 1; + + if (new) + { + static char time_s[] = "YYYY-MM-DD HH:MM:SS "; + time_t now = time(NULL); + + strftime(time_s, sizeof(time_s), "%Y-%m-%d %T ", localtime(&now)); + fputs(time_s, stdout); + } + + va_list ap; + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + + fflush(stdout); + + new = strchr(fmt, '\n') != NULL; +} + +void catch(int sig __attribute__ ((unused)) ) {} + +void child(struct user_list *users, int count, int rshift, + struct stats *stats, in_addr_t addr, int port, int limit) + __attribute__ ((noreturn)); + +time_t basetime; + +int main(int argc, char *argv[]) +{ + char *input = 0; + int instances = 1; + int fake = 0; + int bad = 0; + int limit = 100000; + int o; + + while ((o = getopt(argc, argv, "i:n:f:b:l:")) != -1) + { + switch (o) + { + case 'i': /* input file */ + input = optarg; + break; + + case 'n': /* parallel instances */ + instances = atoi(optarg); + if (instances < 1 || instances > 32) + { + fprintf(stderr, "invalid instances value: `%s' (1-32)\n", optarg); + return 2; + } + break; + + case 'f': /* percentage of additional fake users to add */ + fake = atoi(optarg); + if (fake < 1 || fake > 100) + { + fprintf(stderr, "invalid fake value: `%s' (1-100)\n", optarg); + return 2; + } + break; + + case 'b': /* percentage of users to use incorrect passwords for */ + bad = atoi(optarg); + if (bad < 1 || bad > 100) + { + fprintf(stderr, "invalid bad value: `%s' (1-100)\n", optarg); + return 2; + } + break; + + case 'l': /* limit number of messages per 1/10 sec */ + limit = atoi(optarg); + if (limit < 1) + { + fprintf(stderr, "invalid limit value: `%s'\n", optarg); + return 2; + } + break; + + default: + fprintf(stderr, USAGE, argv[0]); + return 2; + } + } + + if (argc - optind != 3) + { + fprintf(stderr, USAGE, argv[0]); + return 2; + } + + char *server = argv[optind++]; + char *port_s = argv[optind++]; + char *secret = argv[optind]; + + int port = atoi(port_s); + if (port < 1) + { + fprintf(stderr, "invalid port: `%s'\n", port_s); + return 2; + } + + in_addr_t server_addr; + { + struct hostent *h; + if (!(h = gethostbyname(server)) || h->h_addrtype != AF_INET) + { + fprintf(stderr, "invalid server `%s' (%s)\n", server, + h ? "no address" : hstrerror(h_errno)); + + return 1; + } + + memcpy(&server_addr, h->h_addr, sizeof(server_addr)); + } + + time(&basetime); /* start clock */ + + FILE *in = stdin; + if (input && !(in = fopen(input, "r"))) + { + fprintf(stderr, "can't open input file `%s' (%s)\n", input, + strerror(errno)); + + return 1; + } + + logmsg("Loading users from %s: ", input ? input : "stdin"); + + struct user *users = 0; + struct user *u = 0; + + int count = 0; + char buf[1024]; + + while (fgets(buf, sizeof(buf), in)) + { + count++; + + /* format: username \t password \n */ + char *p = strchr(buf, '\t'); + if (!p) + { + fprintf(stderr, "invalid input line %d (no TAB)\n", count); + return 1; + } + + *p++ = 0; + if (!u) + { + users = xmalloc(sizeof(struct user)); + u = users; + } + else + { + u->next = xmalloc(sizeof(struct user)); + u = u->next; + } + + u->user = xstrdup(buf); + while (*p == '\t') + p++; + + char *q = strchr(p, '\n'); + if (q) + *q = 0; + + if (!*p) + { + fprintf(stderr, "invalid input line %d (no password)\n", count); + return 1; + } + + u->pass = xstrdup(p); + u->flags = 0; + u->next = 0; + } + + if (input) + fclose(in); + + logmsg("%d\n", count); + + if (!count) + return 1; + + char *fake_pw = "__fake__"; + if (fake) + { + /* add f fake users to make a total of which fake% are bogus */ + int f = ((count * fake) / (100.0 - fake) + 0.5); + char fake_user[] = "__fake_99999999"; + + logmsg("Generating %d%% extra fake users: ", fake); + for (int i = 0; i < f; i++, count++) + { + snprintf(fake_user, sizeof(fake_user), "__fake_%d", i); + u->next = xmalloc(sizeof(struct user)); + u = u->next; + u->user = xstrdup(fake_user); + u->pass = fake_pw; + u->flags = F_FAKE; + u->next = 0; + } + + logmsg("%d\n", f); + } + + if (bad) + { + int b = (count * bad) / 100.0 + 0.5; + + logmsg("Setting %d%% bad passwords: ", bad); + + u = users; + for (int i = 0; i < b; i++, u = u->next) + { + if (u->pass != fake_pw) + free(u->pass); + + u->pass = "__bad__"; + u->flags |= F_BAD; + } + + logmsg("%d\n", b); + } + + struct user **unsorted = xmalloc(sizeof(struct user) * count); + + u = users; + for (int i = 0; i < count; i++, u = u->next) + unsorted[i] = u; + + struct user_list *random = xmmap(sizeof(struct user_list) * count); + memset(random, 0, sizeof(struct user_list) * count); + + logmsg("Randomising users: "); + + srand(time(NULL) ^ getpid()); + + for (int i = 0; i < count; ) + { + int j = 1.0 * count * rand() / RAND_MAX; + if (unsorted[j]->flags & F_USED) + continue; + + random[i++].entry = unsorted[j]; + unsorted[j]->flags |= F_USED; + } + + logmsg("done\n"); + logmsg("Building RADIUS queries: "); + + { + char pass[128]; + + for (u = users; u; u = u->next) + { + int pw_len = strlen(u->pass); + int len = 4 /* code, identifier, length */ + + 16 /* authenticator */ + + 2 + strlen(u->user) /* user */ + + 2 + ((pw_len / 16) + ((pw_len % 16) ? 1 : 0)) * 16; + /* encoded password */ + + char *p = xmalloc(len); + u->request = p; + u->request_len = len; + + *p++ = AccessRequest; + *p++ = 0; /* identifier set in child */ + *(uint16_t *) p = htons(len); + p += 2; + + /* authenticator */ + for (int j = 0; j < 16; j++) + *p++ = rand(); + + *p = 1; /* user name */ + p[1] = strlen(u->user) + 2; + strcpy(p + 2, u->user); + p += p[1]; + + strcpy(pass, u->pass); + while (pw_len % 16) + pass[pw_len++] = 0; /* pad */ + + for (int j = 0; j < pw_len; j += 16) + { + MD5_CTX ctx; + MD5_Init(&ctx); + MD5_Update(&ctx, secret, strlen(secret)); + if (j) + MD5_Update(&ctx, pass + j - 16, 16); + else + /* authenticator */ + MD5_Update(&ctx, u->request + 4, 16); + + uint8_t digest[16]; + MD5_Final(digest, &ctx); + + for (int k = 0; k < 16; k++) + pass[j + k] ^= digest[k]; + } + + *p = 2; /* password */ + p[1] = pw_len + 2; + memcpy(p + 2, pass, pw_len); + p += p[1]; + } + } + + logmsg("done\n"); + + signal(SIGUSR1, catch); + + struct stats *stats = xmmap(sizeof(struct stats) * instances); + memset(stats, 0, sizeof(struct stats) * instances); + + logmsg("Spawning %d processes: ", instances); + + int per_child = count / instances; + int rshift = 0; + for (u32 tmp = per_child; tmp & 0xff00; tmp >>= 1) + rshift++; + + for (int i = 0, offset = 0; i < instances; i++) + { + int slack = i ? 0 : count % instances; + + stats[i].total = per_child + slack; + if (!fork()) + child(random + offset, per_child + slack, rshift, stats + i, + server_addr, port, limit / instances); + + offset += per_child + slack; + } + + logmsg("done\n"); + + /* wait for children to setup */ + int ready = 0; + do { + ready = 0; + for (int i = 0; i < instances; i++) + ready += stats[i].ready; + + sleep(1); + } while (ready < instances); + + /* go! */ + kill(0, SIGUSR1); + + logmsg("Processing...\n"); + logmsg(" total: "); + + for (int i = 0; i < instances; i++) + logmsg("[%5d %5s %5s]", stats[i].total, "", ""); + + logmsg("\n"); + logmsg(" out/in/err: "); + + int done = 0; + do { + for (int i = 0; i < instances; i++) + logmsg("[%5d %5d %5d]", stats[i].out, stats[i].in, + stats[i].err); + + logmsg("\n"); + + if (waitpid(-1, NULL, WNOHANG) > 0) + done++; + + if (done < instances) + { + sleep(1); + logmsg(" "); + } + } while (done < instances); + + int a_hist[MAX_ATTEMPTS + 1]; + memset(&a_hist, 0, sizeof(a_hist)); + + u32 min = 0; + u32 max = 0; + u32 r_hist[64]; + memset(&r_hist, 0, sizeof(r_hist)); + int hsz = sizeof(r_hist) / sizeof(*r_hist); + + for (int i = 0; i < count; i++) + { + if ((random[i].response != AccessAccept && + random[i].response != AccessReject) || + (random[i].attempts < 1 || + random[i].attempts > MAX_ATTEMPTS)) + { + a_hist[MAX_ATTEMPTS]++; + continue; + } + + a_hist[random[i].attempts - 1]++; + + u32 interval = random[i].end - random[i].begin; + + if (!i || interval < min) + min = interval; + + if (interval > max) + max = interval; + + /* histogram in 1/10s intervals */ + int t = interval / 10 + 0.5; + if (t > hsz - 1) + t = hsz - 1; + + r_hist[t]++; + } + + logmsg("Send attempts:\n"); + for (int i = 0; i < MAX_ATTEMPTS; i++) + logmsg(" %6d: %d\n", i + 1, a_hist[i]); + + logmsg(" failed: %d\n", a_hist[MAX_ATTEMPTS]); + + logmsg("Response time in seconds (min %.2f, max %.2f)\n", + min / 100.0, max / 100.0); + + for (int i = 0; i < hsz; i++) + { + if (i < hsz - 1) + logmsg(" %3.1f:", i / 10.0); + else + logmsg(" more:"); + + logmsg(" %6d\n", r_hist[i]); + } + + return 0; +} + +/* time in sec/100 since program commenced */ +u32 now(void) +{ + struct timeval t; + gettimeofday(&t, 0); + return (t.tv_sec - basetime) * 100 + t.tv_usec / 10000 + 1; +} + +void child(struct user_list *users, int count, int rshift, + struct stats *stats, in_addr_t addr, int port, int limit) +{ + int sockets = 1 << rshift; + unsigned rmask = sockets - 1; + + int *sock = xmalloc(sizeof(int) * sockets); + + fd_set r_in; + int nfd = 0; + + FD_ZERO(&r_in); + + for (int s = 0; s < sockets; s++) + { + if ((sock[s] = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) + { + fprintf(stderr, "can't create a UDP socket (%s)\n", + strerror(errno)); + + exit(1); + } + + int flags = fcntl(sock[s], F_GETFL, 0); + fcntl(sock[s], F_SETFL, flags | O_NONBLOCK); + + struct sockaddr_in svr; + memset(&svr, 0, sizeof(svr)); + svr.sin_family = AF_INET; + svr.sin_port = htons(port); + svr.sin_addr.s_addr = addr; + + connect(sock[s], (struct sockaddr *) &svr, sizeof(svr)); + + FD_SET(sock[s], &r_in); + if (sock[s] + 1 > nfd) + nfd = sock[s] + 1; + } + + for (int i = 0; i < count; i++) + /* set identifier */ + *((unsigned char *) users[i].entry->request + 1) = i >> rshift; + + stats->ready = 1; + pause(); + + u32 out_timer = now(); + int out_count = 0; + + while ((stats->in + stats->err) < count) + { + u32 time_now = now(); + + while (out_timer + 10 < time_now) + { + out_timer += 10; + if (out_count > 0) + out_count -= limit; + } + + for (int pass = 1; pass <= 2; pass++) + { + for (int i = 0; i < count && out_count < limit; i++) + { + if (users[i].response) + continue; + + if (users[i].attempts) + { + if (users[i].retry > time_now) + continue; + } + else if (pass == 1) + { + /* retries only on the first pass */ + continue; + } + + struct user *e = users[i].entry; + if (write(sock[i & rmask], e->request, e->request_len) + != e->request_len) + break; + + time_now = now(); + out_count++; + + if (!users[i].attempts) + { + users[i].begin = time_now; + stats->out++; + } + + if (++users[i].attempts > MAX_ATTEMPTS) + { + users[i].response = AccessFail; + stats->err++; + continue; + } + + users[i].retry = time_now + 200 + 100 * (1 << users[i].attempts); + } + } + + struct timeval tv = { 0, 100000 }; + + fd_set r; + memcpy(&r, &r_in, sizeof(r)); + + if (select(nfd, &r, NULL, NULL, &tv) < 1) + continue; + + char buf[4096]; + + for (int s = 0; s < sockets; s++) + { + if (!FD_ISSET(sock[s], &r)) + continue; + + int sz; + + while ((sz = read(sock[s], buf, sizeof(buf))) > 0) + { + if (sz < 2) + { + fprintf(stderr, "short packet returned\n"); + continue; + } + + if (buf[0] != AccessAccept && buf[0] != AccessReject) + { + fprintf(stderr, "unrecognised response type %d\n", + (int) buf[0]); + + continue; + } + + int i = s | (((unsigned char) buf[1]) << rshift); + if (i < 0 || i > count) + { + fprintf(stderr, "bogus identifier returned %d\n", i); + continue; + } + + if (!users[i].attempts) + { + fprintf(stderr, "unexpected identifier returned %d\n", i); + continue; + } + + if (users[i].response) + continue; + + int expect = (users[i].entry->flags & (F_FAKE|F_BAD)) + ? AccessReject : AccessAccept; + + if (buf[0] != expect) + fprintf(stderr, "unexpected response %d for user %s " + "(expected %d)\n", (int) buf[0], users[i].entry->user, + expect); + + users[i].response = buf[0]; + users[i].end = now(); + stats->in++; + } + } + } + + exit(0); +} diff --git a/throttlectl.c b/throttlectl.c new file mode 100644 index 0000000..0f0b055 --- /dev/null +++ b/throttlectl.c @@ -0,0 +1,135 @@ +#include +#include "l2tpns.h" +#include "plugin.h" +#include "control.h" + +/* throttle control */ + +char const *cvs_id = "$Id: throttlectl.c,v 1.9 2005/10/11 09:04:53 bodea Exp $"; + +int plugin_api_version = PLUGIN_API_VERSION; +static struct pluginfuncs *f = 0; + +char *plugin_control_help[] = { + " throttle USER|SID [RATE|[in|out] RATE ...] Throttle user traffic", + " unthrottle USER|SID Stop throttling user", + 0 +}; + +int plugin_control(struct param_control *data) +{ + sessionidt session; + sessiont *s = 0; + int flag; + char *end; + int rate_in = 0; + int rate_out = 0; + + if (data->argc < 1) + return PLUGIN_RET_OK; + + if (strcmp(data->argv[0], "throttle") && + strcmp(data->argv[0], "unthrottle")) + return PLUGIN_RET_OK; // not for us + + if (!data->iam_master) + return PLUGIN_RET_NOTMASTER; + + flag = data->argv[0][0] == 't'; + + if (flag) + { + if (data->argc < 2 || data->argc > 6) + { + data->response = NSCTL_RES_ERR; + data->additional = "requires username or session id and optional rate(s)"; + return PLUGIN_RET_STOP; + } + } + else + { + if (data->argc != 2) + { + data->response = NSCTL_RES_ERR; + data->additional = "requires username or session id"; + return PLUGIN_RET_STOP; + } + } + + if (!(session = strtol(data->argv[1], &end, 10)) || *end) + session = f->get_session_by_username(data->argv[1]); + + if (session) + s = f->get_session_by_id(session); + + if (!s || !s->ip) + { + data->response = NSCTL_RES_ERR; + data->additional = "session not found"; + return PLUGIN_RET_STOP; + } + + if (flag) + { + rate_in = rate_out = -1; + if (data->argc == 2) + { + unsigned long *rate = f->getconfig("throttle_speed", UNSIGNED_LONG); + rate_in = rate_out = *rate; + } + else if (data->argc == 3) + { + rate_in = rate_out = atoi(data->argv[2]); + } + else + { + int i; + for (i = 2; i < data->argc - 1; i += 2) + { + int len = strlen(data->argv[i]); + if (!strncmp(data->argv[i], "in", len)) + { + rate_in = atoi(data->argv[i+1]); + } + else if (!strncmp(data->argv[i], "out", len)) + { + rate_out = atoi(data->argv[i+1]); + } + else + { + data->response = NSCTL_RES_ERR; + data->additional = "invalid rate"; + return PLUGIN_RET_STOP; + } + } + } + + if (!rate_in || !rate_out) + { + data->response = NSCTL_RES_ERR; + data->additional = "invalid rate"; + return PLUGIN_RET_STOP; + } + } + + if (rate_in != -1 && rate_in == s->throttle_in && + rate_out != -1 && rate_out == s->throttle_out) + { + data->response = NSCTL_RES_ERR; + data->additional = flag ? "already throttled" : "not throttled"; + return PLUGIN_RET_STOP; + } + + f->throttle(session, rate_in, rate_out); + f->session_changed(session); + + data->response = NSCTL_RES_OK; + data->additional = 0; + + return PLUGIN_RET_STOP; +} + +int plugin_init(struct pluginfuncs *funcs) +{ + return ((f = funcs)) ? 1 : 0; +} diff --git a/util.c b/util.c new file mode 100644 index 0000000..f3303b4 --- /dev/null +++ b/util.c @@ -0,0 +1,175 @@ +/* Misc util functions */ + +char const *cvs_id_util = "$Id: util.c,v 1.14 2006/04/05 01:45:57 bodea Exp $"; + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "l2tpns.h" +#ifdef BGP +#include "bgp.h" +#endif + +// format ipv4 addr as a dotted-quad; n chooses one of 4 static buffers +// to use +char *fmtaddr(in_addr_t addr, int n) +{ + static char addrs[4][16]; + struct in_addr in; + + if (n < 0 || n >= 4) + return ""; + + in.s_addr = addr; + return strcpy(addrs[n], inet_ntoa(in)); +} + +void *shared_malloc(unsigned int size) +{ + void * p; + p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, 0, 0); + + if (p == MAP_FAILED) + p = NULL; + + return p; +} + +extern int forked; +extern int cluster_sockfd, tunfd, udpfd, controlfd, daefd, snoopfd, ifrfd, ifr6fd, rand_fd; +extern int *radfds; + +pid_t fork_and_close() +{ + pid_t pid = fork(); + int i; + + if (pid) + return pid; + + forked++; + if (config->scheduler_fifo) + { + struct sched_param params = {0}; + params.sched_priority = 0; + if (sched_setscheduler(0, SCHED_OTHER, ¶ms)) + { + LOG(0, 0, 0, "Error setting scheduler to OTHER after fork: %s\n", strerror(errno)); + LOG(0, 0, 0, "This is probably really really bad.\n"); + } + } + + signal(SIGPIPE, SIG_DFL); + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGUSR1, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGKILL, SIG_DFL); + signal(SIGTERM, SIG_DFL); + + // Close sockets + if (clifd != -1) close(clifd); + if (cluster_sockfd != -1) close(cluster_sockfd); + if (tunfd != -1) close(tunfd); + if (udpfd != -1) close(udpfd); + if (controlfd != -1) close(controlfd); + if (daefd != -1) close(daefd); + if (snoopfd != -1) close(snoopfd); + if (ifrfd != -1) close(ifrfd); + if (ifr6fd != -1) close(ifr6fd); + if (rand_fd != -1) close(rand_fd); + if (epollfd != -1) close(epollfd); + + for (i = 0; radfds && i < RADIUS_FDS; i++) + close(radfds[i]); + +#ifdef BGP + for (i = 0; i < BGP_NUM_PEERS; i++) + if (bgp_peers[i].sock != -1) + close(bgp_peers[i].sock); +#endif /* BGP */ + + return pid; +} + +ssize_t recvfromto(int s, void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t *fromlen, struct in_addr *toaddr) +{ + ssize_t r; + struct msghdr msg; + struct cmsghdr *cmsg; + struct iovec vec; + char cbuf[128]; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = from; + msg.msg_namelen = *fromlen; + + vec.iov_base = buf; + vec.iov_len = len; + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + + msg.msg_control = cbuf; + msg.msg_controllen = sizeof(cbuf); + + if ((r = recvmsg(s, &msg, flags)) < 0) + return r; + + if (fromlen) + *fromlen = msg.msg_namelen; + + memset(toaddr, 0, sizeof(*toaddr)); + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) + { + if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_PKTINFO) + { + struct in_pktinfo *i = (struct in_pktinfo *) CMSG_DATA(cmsg); + memcpy(toaddr, &i->ipi_addr, sizeof(*toaddr)); + break; + } + } + + return r; +} + +ssize_t sendtofrom(int s, void const *buf, size_t len, int flags, + struct sockaddr const *to, socklen_t tolen, struct in_addr const *from) +{ + struct msghdr msg; + struct cmsghdr *cmsg; + struct iovec vec; + struct in_pktinfo pktinfo; + char cbuf[CMSG_SPACE(sizeof(pktinfo))]; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = (struct sockaddr *) to; + msg.msg_namelen = tolen; + + vec.iov_base = (void *) buf; + vec.iov_len = len; + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + + msg.msg_control = cbuf; + msg.msg_controllen = sizeof(cbuf); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_IP; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(pktinfo)); + + memset(&pktinfo, 0, sizeof(pktinfo)); + memcpy(&pktinfo.ipi_spec_dst, from, sizeof(*from)); + memcpy(CMSG_DATA(cmsg), &pktinfo, sizeof(pktinfo)); + + return sendmsg(s, &msg, flags); +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..ee066f6 --- /dev/null +++ b/util.h @@ -0,0 +1,13 @@ +#ifndef __UTIL_H__ +#define __UTIL_H__ + +char *fmtaddr(in_addr_t addr, int n); +void *shared_malloc(unsigned int size); +pid_t fork_and_close(void); +ssize_t sendtofrom(int s, void const *buf, size_t len, int flags, + struct sockaddr const *to, socklen_t tolen, struct in_addr const *from); + +ssize_t recvfromto(int s, void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t *fromlen, struct in_addr *toaddr); + +#endif /* __UTIL_H__ */ -- 2.20.1