From: Benjamin Cama Date: Thu, 7 Jul 2011 10:45:05 +0000 (+0200) Subject: Imported Upstream version 2.1.21 X-Git-Tag: upstream/2.1.21^0 X-Git-Url: http://git.sameswireless.fr/l2tpns.git/commitdiff_plain/f2a3180cc0123acabfed9e48200be0a063f695c0 Imported Upstream version 2.1.21 --- f2a3180cc0123acabfed9e48200be0a063f695c0 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__ */