--- /dev/null
+ 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.
+\f
+ 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.)
+\f
+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.
+\f
+ 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.
+\f
+ 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
+\f
+ 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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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.
+
+ <signature of Ty Coon>, 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.
--- /dev/null
+* Fri Dec 1 2006 Brendan O'Dea <bod@optus.net> 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 <bod@optus.net> 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 <bod@optus.net> 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 <bod@optus.net> 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 <bod@optus.net> 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 <bod@optus.net> 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 <bod@optus.net> 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 <bod@optus.net> 2.1.14
+- Run PLUGIN_RADIUS_ACCOUNT for Start records.
+
+* Wed Dec 7 2005 Brendan O'Dea <bod@optus.net> 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 <bod@optus.net> 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 <bod@optus.net> 2.1.11
+- Fix fragment handling in ip_filter.
+- Exclude counter when comparing filter rules.
+
+* Sat Nov 5 2005 Brendan O'Dea <bod@optus.net> 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 <bod@optus.net> 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 <bod@optus.net> 2.1.8
+- Move code from signal handlers into mainloop, avoiding a race
+ condition when forking CLI.
+
+* Fri Sep 16 2005 Brendan O'Dea <bod@optus.net> 2.1.7
+- This time, for sure: really fix Protocol-Reject.
+
+* Fri Sep 16 2005 Brendan O'Dea <bod@optus.net> 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 <bod@optus.net> 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 <bod@optus.net> 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 <bod@optus.net> 2.1.3
+- Fail IPCP negotiation only on ConfigRej of IP-Address.
+
+* Wed Aug 10 2005 Brendan O'Dea <bod@optus.net> 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 <bod@optusnet.com.au> 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 <bod@optusnet.com.au> 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 <bod@optusnet.com.au> 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 <bod@optusnet.com.au> 2.0.12
+- The "This time, for sure!" release.
+- Fix throttlectl plugin argument parsing.
+
+* Wed Dec 1 2004 Brendan O'Dea <bod@optusnet.com.au> 2.0.11
+- Don't send a RADIUS start record when ungardening on shutdown.
+
+* Wed Dec 1 2004 Brendan O'Dea <bod@optusnet.com.au> 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 <bod@optusnet.com.au> 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 <bod@optusnet.com.au> 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 <bod@optusnet.com.au> 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 <bod@optusnet.com.au> 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 <bod@optusnet.com.au> 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 <bod@optusnet.com.au> 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 <bod@optusnet.com.au> 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 <david@dparrish.com> 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 <bod@optusnet.com.au> 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 <david@dparrish.com> 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 <david@dparrish.com> 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 <david@dparrish.com> 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 <david@dparrish.com> 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
--- /dev/null
+.\" -*- 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 <noodles@earth.li>,
+for the Debian GNU/Linux system (but may be used by others).
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<HTML>
+<HEAD>
+<TITLE>L2TPNS Manual</TITLE>
+<STYLE TYPE="text/css">
+H1 {
+ text-align: center;
+}
+
+H2 {
+ border-top: 1px solid black;
+ border-bottom: 1px solid black;
+ background-color: lightblue;
+ padding-left: 10px;
+}
+
+H3 {
+ text-decoration: underline;
+}
+</STYLE>
+</HEAD>
+<BODY>
+<H1>L2TPNS Manual</H1>
+<OL>
+ <LI><A HREF="#Overview">Overview</A></LI>
+ <LI><A HREF="#Installation">Installation</A>
+ <OL>
+ <LI><A HREF="#Requirements">Requirements</A></LI>
+ <LI><A HREF="#Compile">Compile</A></LI>
+ <LI><A HREF="#Install">Install</A></LI>
+ <LI><A HREF="#Running">Running</A></LI>
+ </OL>
+ </LI>
+ <LI><A HREF="#Configuration">Configuration</A>
+ <OL>
+ <LI><A HREF="#startup-config">startup-config</A></LI>
+ <LI><A HREF="#users">users</A></LI>
+ <LI><A HREF="#ip-pool">ip_pool</A></LI>
+ <LI><A HREF="#build-garden">build-garden</A></LI>
+ </OL>
+ </LI>
+ <LI><A HREF="#ControllingtheProcess">Controlling the Process</A>
+ <OL>
+ <LI><A HREF="#Command-LineInterface">Command-Line Interface</A></LI>
+ <LI><A HREF="#nsctl">nsctl</A></LI>
+ <LI><A HREF="#Signals">Signals</A></LI>
+ </OL>
+ </LI>
+ <LI><A HREF="#Throttling">Throttling</A></LI>
+ <LI><A HREF="#Interception">Interception</A></LI>
+ <LI><A HREF="#Authentication">Authentication</A></LI>
+ <LI><A HREF="#Plugins">Plugins</A></LI>
+ <LI><A HREF="#WalledGarden">Walled Garden</A></LI>
+ <LI><A HREF="#Filtering">Filtering</A></LI>
+ <LI><A HREF="#Clustering">Clustering</A></LI>
+ <LI><A HREF="#Routing">Routing</A></LI>
+ <LI><A HREF="#Performance">Performance</A></LI>
+</OL>
+
+<H2 ID="Overview">Overview</H2>
+l2tpns is half of a complete L2TP implementation. It supports only the
+LNS side of the connection.<P>
+
+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.<P>
+
+There are a couple of other L2TP implementations, of which <A
+HREF="http://sourceforge.net/projects/l2tpd">l2tpd</A> 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.<P>
+
+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.<P>
+
+This allows it to scale extremely well to very high loads and very high
+numbers of connections.<P>
+
+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.<P>
+
+<BR>
+<EM>Documentation is not my best skill. If you find any problems
+with this document, or if you wish to contribute, please email <A
+HREF="mailto:l2tpns-users@lists.sourceforge.net?subject=L2TPNS+Documentation">the mailing list</A>.</EM><P>
+
+<H2 ID="Installation">Installation</H2>
+<H3 ID="Requirements">Requirements</H3>
+
+<OL>
+<LI>Linux kernel version 2.4 or above, with the Tun/Tap interface either
+compiled in, or as a module.</LI>
+
+<LI>libcli 1.8.0 or greater.<BR>You can get this from <A
+HREF="http://sourceforge.net/projects/libcli">http://sourceforge.net/projects/libcli</A></LI>
+</OL>
+
+<H3 ID="Compile">Compile</H3>
+
+You can generally get away with just running <B>make</B> from the source
+directory. This will compile the daemon, associated tools and any modules
+shipped with the distribution.<P>
+
+<H3 ID="Install">Install</H3>
+
+After you have successfully compiled everything, run <B>make
+install</B> to install it. By default, the binaries are installed into
+<EM>/usr/sbin</EM>, the configuration into <EM>/etc/l2tpns</EM>, and the
+modules into <EM>/usr/lib/l2tpns</EM>.<P>
+
+You will definately need to edit the configuration files before you
+start. See the <A HREF="#Configuration">Configuration</A> section for
+more information.<P>
+
+<H3 ID="Running">Running</H3>
+
+You only need to run <B>/usr/sbin/l2tpns</B> as root to start it. It does
+not detach to a daemon process, so you should perhaps run it from init.<P>
+
+By default there is no log destination set, so all log messages will go to
+stdout.<P>
+
+<H2 ID="Configuration">Configuration</H2>
+
+All configuration of the software is done from the files installed into
+/etc/l2tpns.
+
+<H3 ID="startup-config">startup-config</H3>
+
+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
+<EM>write memory</EM> 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.<P>
+
+A list of the possible configuration directives follows. Each of these
+should be set by a line like:<P>
+<PRE>
+set configstring "value"
+set ipaddress 192.168.1.1
+set boolean true
+</PRE>
+
+<P>
+<UL>
+<LI><B>debug</B> (int)<BR>
+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:
+ <OL>
+ <LI VALUE=0>Critical Errors - Things are probably broken</LI>
+ <LI>Errors - Things might have gone wrong, but probably will recover</LI>
+ <LI>Warnings - Just in case you care what is not quite perfect</LI>
+ <LI>Information - Parameters of control packets</LI>
+ <LI>Calls - For tracing the execution of the code</LI>
+ <LI>Packets - Everything, including a hex dump of all packets processed... probably twice</LI>
+ </OL><P>
+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.
+</LI>
+
+<LI><B>log_file</B> (string)<BR>
+This will be where all logging and debugging information is written
+to. This may be either a filename, such as <EM>/var/log/l2tpns</EM>, or
+the special magic string <EM>syslog:facility</EM>, where <EM>facility</EM>
+is any one of the syslog logging facilities, such as local5.
+</LI>
+
+<LI><B>pid_file</B> (string)<BR>
+If set, the process id will be written to the specified file. The
+value must be an absolute path.
+</LI>
+
+<LI><B>l2tp_secret</B> (string)<BR>
+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.
+</LI>
+
+<LI><B>l2tp_mtu</B> (int)<BR>
+MTU of interface for L2TP traffic (default: 1500). Used to set link
+MRU and adjust TCP MSS.
+</LI>
+
+<LI><B>ppp_restart_time</B> (int)<BR>
+<B>ppp_max_configure</B> (int)<BR>
+<B>ppp_max_failure</B> (int)<BR>
+PPP counter and timer values, as described in §4.1 of
+<a href="ftp://ftp.rfc-editor.org/in-notes/rfc1661.txt">RFC1661</a>.
+</LI>
+
+<LI><B>primary_dns</B> (ip address)
+<LI><B>secondary_dns</B> (ip address)<BR>
+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.
+</LI>
+
+<LI><B>primary_radius</B> (ip address)
+<LI><B>secondary_radius</B> (ip address)<BR>
+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.<br>
+<strong>Note:</strong> in addition to the source IP address and
+identifier, the RADIUS server <strong>must</strong> 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).
+</LI>
+
+<LI><B>primary_radius_port</B> (short)
+<LI><B>secondary_radius_port</B> (short)<BR>
+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.
+</LI>
+
+<LI><B>radius_accounting</B> (boolean)<BR>
+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.
+</LI>
+
+<LI><B>radius_secret</B> (string)<BR>
+This secret will be used in all RADIUS queries. If this is not set then
+RADIUS queries will fail.
+</LI>
+
+<LI><B>radius_authtypes</B> (string)</BR>
+A comma separated list of supported RADIUS authentication methods
+(<B>pap</B> or <B>chap</B>), in order of preference (default <B>pap</B>).
+</LI>
+
+<LI><B>radius_dae_port</B> (short)<BR>
+Port for DAE RADIUS (Packet of Death/Disconnect, Change of Authorization)
+requests (default: <B>3799</B>).
+</LI>
+
+<LI><B>allow_duplicate_users</B> (boolean)</BR>
+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.
+</LI>
+
+<LI><B>bind_address</B> (ip address)<BR>
+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.
+</LI>
+
+<LI><B>peer_address</B> (ip address)<BR>
+Address to send to clients as the default gateway.
+</L1>
+
+<LI><B>send_garp</B> (boolean)<BR>
+Determines whether or not to send a gratuitous ARP for the
+bind_address when the server is ready to handle traffic (default:
+true).<BR>
+This value is ignored if BGP is configured.
+</LI>
+
+<LI><B>throttle_speed</B> (int)<BR>
+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.
+</LI>
+
+<LI><B>throttle_buckets</B> (int)<BR>
+Number of token buckets to allocate for throttling. Each throttled
+session requires two buckets (in and out).
+</LI>
+
+<LI><B>accounting_dir</B> (string)<BR>
+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.<BR> The fields are username, ip, qos,
+uptxoctets, downrxoctets. The qos field is 1 if a standard user, and
+2 if the user is throttled.
+</LI>
+
+<LI><B>setuid</B> (int)<BR>
+After starting up and binding the interface, change UID to this. This
+doesn't work properly.
+</LI>
+
+<LI><B>dump_speed</B> (boolean)<BR>
+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 <EM>uptime</EM> command on the CLI.
+</LI>
+
+<LI><B>multi_read_count</B> (int)<BR>
+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.
+</LI>
+
+<LI><B>scheduler_fifo</B> (boolean)<BR>
+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.
+</LI>
+
+<LI><B>lock_pages</B> (boolean)<BR>
+Keep all pages mapped by the l2tpns process in memory.
+</LI>
+
+<LI><B>icmp_rate</B> (int)<BR>
+Maximum number of host unreachable ICMP packets to send per second.
+</LI>
+
+<LI><B>packet_limit</B> (int><BR>
+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).
+</LI>
+
+<LI><B>cluster_address</B> (ip address)<BR>
+Multicast cluster address (default: 239.192.13.13). See the section
+on <A HREF="#Clustering">Clustering</A> for more information.
+</LI>
+
+<LI><B>cluster_interface</B> (string)<BR>
+Interface for cluster packets (default: eth0).
+</LI>
+
+<LI><B>cluster_mcast_ttl</B> (int)<BR>
+TTL for multicast packets (default: 1).
+</LI>
+
+<LI><B>cluster_hb_interval</B> (int)<BR>
+Interval in tenths of a second between cluster heartbeat/pings.
+</LI>
+
+<LI><B>cluster_hb_timeout</B> (int)<BR>
+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.
+</LI>
+
+<LI><B>cluster_master_min_adv</B> (int)<BR>
+Determines the minumum number of up to date slaves required before the
+master will drop routes (default: 1).
+</LI>
+</UL>
+
+<P>BGP routing configuration is entered by the command:
+The routing configuration section is entered by the command
+<DL><DD><B>router bgp</B> <I>as</I></DL>
+where <I>as</I> specifies the local AS number.
+
+<P>Subsequent lines prefixed with
+<DL><DD><B>neighbour</B> <I>peer</I></DL>
+define the attributes of BGP neighhbours. Valid commands are:
+<DL>
+ <DD><B>neighbour</B> <I>peer</I> <B>remote-as</B> <I>as</I>
+ <DD><B>neighbout</B> <I>peer</I> <B>timers</B> <I>keepalive hold</I>
+</DL>
+
+Where <I>peer</I> specifies the BGP neighbour as either a hostname or
+IP address, <I>as</I> is the remote AS number and <I>keepalive</I>,
+<I>hold</I> are the timer values in seconds.
+
+<P>Named access-lists are configured using one of the commands:
+<DL>
+ <DD><B>ip access-list standard</B> <I>name</I>
+ <DD><B>ip access-list extended</B> <I>name</I>
+</DL>
+
+<P>Subsequent lines prefixed with <B>permit</B> or <B>deny</B>
+define the body of the access-list. Standard access-list syntax:
+<DL>
+ <DD>{<B>permit</B>|<B>deny</B>}
+ {<I>host</I>|<I>source source-wildcard</I>|<B>any</B>}
+ [{<I>host</I>|<I>destination destination-wildcard</I>|<B>any</B>}]
+</DL>
+
+Extended access-lists:
+
+<DIV STYLE="margin-left: 4em; text-indent: -2em">
+ <P>{<B>permit</B>|<B>deny</B>} <B>ip</B>
+ {<I>host</I>|<I>source source-wildcard</I>|<B>any</B>}
+ {<I>host</I>|<I>destination destination-wildcard</I>|<B>any</B>} [<B>fragments</B>]
+ <P>{<B>permit</B>|<B>deny</B>} <B>udp</B>
+ {<I>host</I>|<I>source source-wildcard</I>|<B>any</B>}
+ [{<B>eq</B>|<B>neq</B>|<B>gt</B>|<B>lt</B>} <I>port</I>|<B>range</B> <I>from</I> <I>to</I>]
+ {<I>host</I>|<I>destination destination-wildcard</I>|<B>any</B>}
+ [{<B>eq</B>|<B>neq</B>|<B>gt</B>|<B>lt</B>} <I>port</I>|<B>range</B> <I>from</I> <I>to</I>]
+ [<B>fragments</B>]
+ <P>{<B>permit</B>|<B>deny</B>} <B>tcp</B>
+ {<I>host</I>|<I>source source-wildcard</I>|<B>any</B>}
+ [{<B>eq</B>|<B>neq</B>|<B>gt</B>|<B>lt</B>} <I>port</I>|<B>range</B> <I>from</I> <I>to</I>]
+ {<I>host</I>|<I>destination destination-wildcard</I>|<B>any</B>}
+ [{<B>eq</B>|<B>neq</B>|<B>gt</B>|<B>lt</B>} <I>port</I>|<B>range</B> <I>from</I> <I>to</I>]
+ [{<B>established</B>|{<B>match-any</B>|<B>match-all</B>}
+ {<B>+</B>|<B>-</B>}{<B>fin</B>|<B>syn</B>|<B>rst</B>|<B>psh</B>|<B>ack</B>|<B>urg</B>}
+ ...|<B>fragments</B>]
+</DIV>
+
+<H3 ID="users">users</H3>
+
+Usernames and passwords for the command-line interface are stored in
+this file. The format is <I>username</I><B>:</B><I>password</I> where
+<I>password</I> may either by plain text, an MD5 digest (prefixed by
+<B>$1</B><I>salt</I><B>$</B>) or a DES password, distinguished from
+plain text by the prefix <B>{crypt}</B>.<P>
+
+The username <B>enable</B> has a special meaning and is used to set
+the enable password.<P>
+
+<B>Note:</B> If this file doesn't exist, then anyone who can get to
+port 23 will be allowed access without a username / password.<P>
+
+<H3 ID="ip-pool">ip_pool</H3>
+
+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.:<P>
+
+<PRE>
+ 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
+</PRE>
+
+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.
+
+<H3 ID="build-garden">build-garden</H3>
+
+The garden plugin on startup creates a NAT table called "garden" then
+sources the <B>build-garden</B> script to populate that table. All
+packets from gardened users will be sent through this table. Example:
+
+<PRE>
+ 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
+</PRE>
+
+<H2 ID="ControllingtheProcess">Controlling the Process</H2>
+
+A running l2tpns process can be controlled in a number of ways. The primary
+method of control is by the Command-Line Interface (CLI).<P>
+
+You can also remotely send commands to modules via the nsctl client
+provided.<P>
+
+Also, there are a number of signals that l2tpns understands and takes action
+when it receives them.
+
+<H3 ID="Command-LineInterface">Command-Line Interface</H3>
+
+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
+<A HREF="#users">users</A> for information on restricting access based
+on a username and password.<P>
+
+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
+<A HREF="http://sourceforge.net/projects/libcli">libcli</A>
+library. Some general documentation of the interface is
+<A HREF="http://sourceforge.net/docman/display_doc.php?docid=20501&group_id=79019">
+here</A>.<P>
+
+After you have connected to the telnet port (and perhaps logged in), you
+will be presented with a <I>hostname</I><B>></B> prompt.<P>
+
+Enter <EM>help</EM> to get a list of possible commands. A brief
+overview of the more important commands follows:
+
+<UL>
+<LI><B>show session</B><BR>
+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.<P>
+The columns listed in the overview are:
+<TABLE>
+ <TR><TD><B>SID</B></TD><TD>Session ID</TD></TR>
+ <TR><TD><B>TID</B></TD><TD>Tunnel ID - Use with <EM>show tunnel tid</EM></TD></TR>
+ <TR><TD><B>Username</B></TD><TD>The username given in the PPP
+ authentication. If this is *, then LCP authentication has not
+ completed.</TD></TR>
+ <TR><TD><B>IP</B></TD><TD>The IP address given to the session. If
+ this is 0.0.0.0, LCP negotiation has not completed.</TD></TR>
+ <TR><TD><B>I</B></TD><TD>Intercept - Y or N depending on whether the
+ session is being snooped. See <EM>snoop</EM>.</TD></TR>
+ <TR><TD><B>T</B></TD><TD>Throttled - Y or N if the session is
+ currently throttled. See <EM>throttle</EM>.</TD></TR>
+ <TR><TD><B>G</B></TD><TD>Walled 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.</TD></TR>
+ <TR><TD><B>opened</B></TD><TD>The number of seconds since the
+ session started</TD></TR>
+ <TR><TD><B>downloaded</B></TD><TD>Number of bytes downloaded by the user</TD></TR>
+ <TR><TD><B>uploaded</B></TD><TD>Number of bytes uploaded by the user</TD></TR>
+ <TR><TD><B>idle</B></TD><TD>The number of seconds since traffic was
+ detected on the session</TD></TR>
+ <TR><TD><B>LAC</B></TD><TD>The IP address of the LAC the session is
+ connected to.</TD></TR>
+ <TR><TD><B>CLI</B></TD><TD>The Calling-Line-Identification field
+ provided during the session setup. This field is generated by the
+ LAC.</TD></TR>
+</TABLE>
+<P>
+</LI>
+
+<LI><B>show users</B><BR>
+With no arguments, display a list of currently connected users. If an
+argument is given, the session details for the given username are
+displayed.
+</LI>
+
+<LI><B>show tunnel</B><BR>
+This will show all the open tunnels in a summary, or detail on a single
+tunnel if you give a tunnel id.<P>
+The columns listed in the overview are:
+<TABLE>
+ <TR><TD><B>TID</B></TD><TD>Tunnel ID</TD></TR>
+ <TR><TD><B>Hostname</B></TD><TD>The hostname for the tunnel as
+ provided by the LAC. This has no relation to DNS, it is just
+ a text field.</TD></TR>
+ <TR><TD><B>IP</B></TD><TD>The IP address of the LAC</TD></TR>
+ <TR><TD><B>State</B></TD><TD>Tunnel state - Free, Open, Dieing,
+ Opening</TD></TR>
+ <TR><TD><B>Sessions</B></TD><TD>The number of open sessions on the
+ tunnel</TD></TR>
+</TABLE>
+<P>
+</LI>
+
+<LI><B>show pool</B><BR>
+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.<P>
+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:
+<PRE>
+IP Address Used Session User
+192.168.100.6 N [joe.user] 1548s
+</PRE>
+<P>
+</LI>
+
+<LI><B>show radius</B><BR>
+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:
+<TABLE>
+ <TR><TD><B>Radius</B></TD><TD>The ID of the RADIUS request. This is
+ sent in the packet to the RADIUS server for identification.</TD></TR>
+ <TR><TD><B>State</B></TD><TD>The state of the request - WAIT, CHAP,
+ AUTH, IPCP, START, STOP, NULL.</TD></TR>
+ <TR><TD><B>Session</B></TD><TD>The session ID that this RADIUS
+ request is associated with</TD></TR>
+ <TR><TD><B>Retry</B></TD><TD>If a response does not appear to the
+ request, it will retry at this time. This is a unix timestamp.</TD></TR>
+ <TR><TD><B>Try</B></TD><TD>Retry count. The RADIUS request is
+ discarded after 3 retries.</TD></TR>
+</TABLE>
+<P>
+</LI>
+
+<LI><B>show running-config</B><BR>
+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.
+<P>
+</LI>
+
+<LI><B>show counters</B><BR>
+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.<P>
+You can reset these counters by running <EM>clear counters</EM>.
+<P>
+</LI>
+
+<LI><B>show cluster</B><BR>
+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.<P>
+See <A HREF="#Clustering">Clustering</A> for more information.
+<P>
+</LI>
+
+<LI><B>write memory</B><BR>
+This will write the current running configuration to the config file
+<B>startup-config</B>, which will be run on a restart.
+<P>
+</LI>
+
+<LI><B>snoop</B><BR>
+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 <EM>no snoop username</EM> to disable interception
+for the session.<P>
+
+If you want interception to be permanent, you will have to modify the RADIUS
+response for the user. See <A HREF="#Interception">Interception</A>.
+<P>
+</LI>
+
+<LI><B>throttle</B><BR>
+You must specify a username, which will be throttled for the current
+session. Specify <EM>no throttle username</EM> to disable throttling
+for the current session.<P>
+
+If you want throttling to be permanent, you will have to modify the
+RADIUS response for the user. See <A HREF="#Throttling">Throttling</A>.
+<P>
+</LI>
+
+<LI><B>drop session</B><BR>
+This will cleanly disconnect a session. You must specify a session id, which
+you can get from <EM>show session</EM>. This will send a disconnect message
+to the remote end.
+<P>
+</LI>
+
+<LI><B>drop tunnel</B><BR>
+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.
+<P>
+</LI>
+
+<LI><B>uptime</B><BR>
+This will show how long the l2tpns process has been running, and the current
+bandwidth utilization:
+<PRE>
+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
+</PRE>
+The bandwidth line contains 4 sets of values.<BR>
+UDP-ETH is the current bandwidth going from the LAC to the ethernet
+(user uploads), in mbits/sec.<BR>
+ETH-UDP is the current bandwidth going from ethernet to the LAC (user
+downloads).<BR>
+TOTAL is the total aggregate bandwidth in mbits/s.<BR>
+IN and OUT are packets/per-second going between UDP-ETH and ETH-UDP.
+<P>
+These counters are updated every second.
+<P>
+</LI>
+
+<LI><B>configure terminal</B><BR>
+Enter configuration mode. Use <EM>exit</EM> or ^Z to exit this mode.
+The following commands are valid in this mode:<P>
+</LI>
+
+<LI><B>load plugin</B><BR>
+Load a plugin. You must specify the plugin name, and it will search in
+/usr/lib/l2tpns for <EM>plugin</EM>.so. You can unload a loaded plugin with
+<EM>remove plugin</EM>.
+<P>
+</LI>
+
+<LI><B>set</B><BR>
+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.<P>
+
+You can set any <A HREF="#startup-config">startup-config</A> value in
+this way, although some may require a restart to take effect.<P>
+</LI>
+</UL>
+
+<H3 ID="nsctl">nsctl</H3>
+
+nsctl allows messages to be passed to plugins.<P>
+
+Arguments are <EM>command</EM> and optional <EM>args</EM>. See
+<STRONG>nsctl</STRONG>(8) for more details.<P>
+
+Built-in command are <EM>load_plugin</EM>, <EM>unload_plugin</EM> and
+<EM>help</EM>. Any other commands are passed to plugins for processing.
+
+<H3 ID="Signals">Signals</H3>
+
+While the process is running, you can send it a few different signals, using
+the kill command.
+<PRE>
+killall -HUP l2tpns
+</PRE>
+
+The signals understood are:
+<DL>
+<DT>SIGHUP</DT><DD>Reload the config from disk and re-open log file.</DD>
+<DT>SIGTERM, SIGINT</DT><DD>Stop process. Tunnels and sessions are not
+terminated. This signal should be used to stop l2tpns on a
+<A HREF="#Clustering">cluster node</A> where there are other machines to
+continue handling traffic.</DD>
+<DT>SIGQUIT</DT><DD>Shut down tunnels and sessions, exit process when
+complete.</DD>
+</DL>
+
+<H2 ID="Throttling">Throttling</H2>
+
+l2tpns contains support for slowing down user sessions to whatever speed you
+desire. You must first enable the global setting <EM>throttle_speed</EM>
+before this will be activated.<P>
+
+If you wish a session to be throttled permanently, you should set the
+Vendor-Specific RADIUS value <B>Cisco-Avpair="throttle=yes"</B>, which
+will be handled by the <EM>autothrottle</EM> module.<P>
+
+Otherwise, you can enable and disable throttling an active session using
+the <EM>throttle</EM> CLI command.<P>
+
+<H2 ID="Interception">Interception</H2>
+
+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.<P>
+
+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 <EM>snoop_host</EM> and <EM>snoop_port</EM> configuration
+variables.<P>
+
+The UDP packet contains just the raw IP frame, with no extra headers.<P>
+
+To enable interception on a connected user, use the <EM>snoop username</EM>
+and <EM>no snoop username</EM> CLI commands. These will enable interception
+immediately.<P>
+
+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
+<B>Cisco-Avpair="intercept=yes"</B>. For this feature to be enabled,
+you need to have the <EM>autosnoop</EM> module loaded.<P>
+
+<H2 ID="Authentication">Authentication</H2>
+
+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.<P>
+
+This request is sent to the RADIUS server, which will hopefully respond with
+Auth-Accept or Auth-Reject.<P>
+
+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.<P>
+
+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.<P>
+
+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
+<PRE>
+key=value,key2=value2,key3=value3,key<EM>n</EM>=<EM>value</EM>
+</PRE>
+
+Each key-value pair is separated and passed to any modules loaded. The
+<EM>autosnoop</EM> and <EM>autothrottle</EM> understand the keys
+<EM>intercept</EM> and <EM>throttle</EM> respectively. For example, to have
+a user who is to be throttled and intercepted, the Cisco-Avpair value should
+contain:
+<PRE>
+intercept=yes,throttle=yes
+</PRE>
+
+<H2 ID="Plugins">Plugins</H2>
+
+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.<P>
+
+There are a few example modules included - autosnoop, autothrottle and
+garden.<P>
+
+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.<P>
+
+The function should return <B>PLUGIN_RET_OK</B> if it is all OK. If it returns
+<B>PLUGIN_RET_STOP</B>, then it is assumed to have worked, but that no further
+modules should be run for this event.<P>
+A return of <B>PLUGIN_RET_ERROR</B> means that this module failed, and
+no further processing should be done for this event. <EM>Use this with care.</EM>
+
+Every event function called takes a specific structure named
+param_<EM>event</EM>, which varies in content with each event. The
+function name for each event will be <B>plugin_<EM>event</EM></B>,
+so for the event <EM>timer</EM>, the function declaration should look like:
+<PRE>
+int plugin_timer(struct param_timer *data);
+</PRE>
+
+A list of the available events follows, with a list of all the fields in the
+supplied structure:
+<TABLE CELLSPACING=0 CELLPADDING=0><TR BGCOLOR=LIGHTGREEN><TD>
+<TABLE CELLSPACING=1 CELLPADDING=3>
+ <TR BGCOLOR=LIGHTGREEN><TH><B>Event</B></TH><TH><B>Description</B></TH><TH><B>Parameters</B></TH></TR>
+ <TR VALIGN=TOP BGCOLOR=WHITE><TD><B>pre_auth</B></TD>
+ <TD>This 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.
+ </TD>
+ <TD>
+ <DL>
+ <DT>t<DD>Tunnel
+ <DT>s<DD>Session
+ <DT>username
+ <DT>password
+ <DT>protocol<DD>0xC023 for PAP, 0xC223 for CHAP
+ <DT>continue_auth<DD>Set to 0 to stop processing authentication modules
+ </DL>
+ </TD>
+ </TR>
+ <TR VALIGN=TOP BGCOLOR=WHITE><TD><B>post_auth</B></TD>
+ <TD>This 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.
+ </TD>
+ <TD>
+ <DL>
+ <DT>t<DD>Tunnel
+ <DT>s<DD>Session
+ <DT>username
+ <DT>auth_allowed<DD>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
+ <DT>protocol<DD>0xC023 for PAP, 0xC223 for CHAP
+ </DL>
+ </TD>
+ </TR>
+ <TR VALIGN=TOP BGCOLOR=WHITE><TD><B>packet_rx</B></TD>
+ <TD>This is called whenever a session receives a
+ packet. <FONT COLOR=RED>Use this sparingly, as this will
+ seriously slow down the system.</FONT>
+ </TD>
+ <TD>
+ <DL>
+ <DT>t<DD>Tunnel
+ <DT>s<DD>Session
+ <DT>buf<DD>The raw packet data
+ <DT>len<DD>The length of buf
+ </DL>
+ </TD>
+ </TR>
+ <TR VALIGN=TOP BGCOLOR=WHITE><TD><B>packet_tx</B></TD>
+ <TD>This is called whenever a session sends a
+ packet. <FONT COLOR=RED>Use this sparingly, as this will
+ seriously slow down the system.</FONT>
+ </TD>
+ <TD>
+ <DL>
+ <DT>t<DD>Tunnel
+ <DT>s<DD>Session
+ <DT>buf<DD>The raw packet data
+ <DT>len<DD>The length of buf
+ </DL>
+ </TD>
+ </TR>
+ <TR VALIGN=TOP BGCOLOR=WHITE><TD><B>timer</B></TD>
+ <TD>This is run every second, no matter what is happening.
+ This is called from a signal handler, so make sure anything
+ you do is reentrant.
+ </TD>
+ <TD>
+ <DL>
+ <DT>time_now<DD>The current unix timestamp
+ </DL>
+ </TD>
+ </TR>
+ <TR VALIGN=TOP BGCOLOR=WHITE><TD><B>new_session</B></TD>
+ <TD>This is called after a session is fully set up. The
+ session is now ready to handle traffic.
+ </TD>
+ <TD>
+ <DL>
+ <DT>t<DD>Tunnel
+ <DT>s<DD>Session
+ </DL>
+ </TD>
+ </TR>
+ <TR VALIGN=TOP BGCOLOR=WHITE><TD><B>kill_session</B></TD>
+ <TD>This is called when a session is about to be shut down.
+ This may be called multiple times for the same session.
+ </TD>
+ <TD>
+ <DL>
+ <DT>t<DD>Tunnel
+ <DT>s<DD>Session
+ </DL>
+ </TD>
+ </TR>
+ <TR VALIGN=TOP BGCOLOR=WHITE><TD><B>radius_response</B></TD>
+ <TD>This is called whenever a RADIUS response includes a
+ Cisco-Avpair value. The value is split up into
+ <EM>key=value</EM> pairs, and each is processed through all
+ modules.
+ </TD>
+ <TD>
+ <DL>
+ <DT>t<DD>Tunnel
+ <DT>s<DD>Session
+ <DT>key
+ <DT>value
+ </DL>
+ </TD>
+ </TR>
+ <TR VALIGN=TOP BGCOLOR=WHITE><TD><B>radius_reset</B></TD>
+ <TD>This is called whenever a RADIUS CoA request is
+ received to reset any options to default values before
+ the new values are applied.
+ </TD>
+ <TD>
+ <DL>
+ <DT>t<DD>Tunnel
+ <DT>s<DD>Session
+ </DL>
+ </TD>
+ </TR>
+ <TR VALIGN=TOP BGCOLOR=WHITE><TD><B>control</B></TD>
+ <TD>This is called in whenever a nsctl packet is received.
+ This should handle the packet and form a response if
+ required.
+ </TD>
+ <TD>
+ <DL>
+ <DT>iam_master<DD>Cluster master status
+ <DT>argc<DD>The number of arguments
+ <DT>argv<DD>Arguments
+ <DT>response<DD>Return value: NSCTL_RES_OK or NSCTL_RES_ERR
+ <DT>additional<DD>Extended response text
+ </DL>
+ </TD>
+ </TR>
+</TABLE>
+</TD></TR></TABLE>
+
+<H2 ID="WalledGarden">Walled Garden</H2>
+
+Walled Garden is implemented so that you can provide perhaps limited service
+to sessions that incorrectly authenticate.<P>
+
+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
+<EM>garden</EM> in the session structure, and adds an iptables rule to
+the <B>garden_users</B> chain to force all packets for the session's IP
+address to traverse the <B>garden</B> chain.<P>
+
+This doesn't <EM>just work</EM>. To set this all up, you will to
+setup the <B>garden</B> nat table with the
+<A HREF="#build-garden">build-garden</A> 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
+<EM>build-garden</EM>:
+<PRE>
+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
+</PRE>
+
+l2tpns will add entries to the garden_users chain as appropriate.<P>
+
+You can check the amount of traffic being captured using the following
+command:
+<PRE>
+iptables -t nat -L garden -nvx
+</PRE>
+
+<H2 ID="Filtering">Filtering</H2>
+
+Sessions may be filtered by specifying <B>Filter-Id</B> attributes in
+the RADIUS reply. <I>filter</I>.<B>in</B> specifies that the named
+access-list <I>filter</I> should be applied to traffic from the
+customer, <I>filter</I>.<B>out</B> specifies a list for traffic to the
+customer.
+
+<H2 ID="Clustering">Clustering</H2>
+
+An l2tpns cluster consists of of one* or more servers configured with
+the same configuration, notably the multicast <B>cluster_address</B>.<P>
+
+*A stand-alone server is simply a degraded cluster.<P>
+
+Initially servers come up as cluster slaves, and periodically (every
+<B>cluster_hb_interval</B>/10 seconds) send out ping packets
+containing the start time of the process to the multicast
+<B>cluster_address</B>.<P>
+
+A cluster master sends heartbeat rather than ping packets, which
+contain those session and tunnel changes since the last heartbeat.<P>
+
+When a slave has not seen a heartbeat within
+<B>cluster_hb_timeout</B>/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).<P>
+
+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 <A HREF="#Routing">Routing</A>) for
+the <B>bind_address</B> and for all addresses/networks in
+<B>ip_pool</B>. 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.<P>
+
+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.<P>
+
+<H2 ID="Routing">Routing</H2>
+If you are running a single instance, you may simply statically route
+the IP pools to the <B>bind_address</B> (l2tpns will send a gratuitous
+arp).<P>
+
+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
+<B>as_number</B> to a private value such as 64512.<P>
+
+<H2 ID="Performance">Performance</H2>
+
+Performance is great.<P>
+
+I'd like to include some pretty graphs here that show a linear performance
+increase, with no impact by number of connected sessions.<P>
+
+That's really what it looks like.<P>
+
+<BR>
+David Parrish<BR>
+<A HREF="mailto:l2tpns-users@lists.sourceforge.net?subject=L2TPNS%20Documentation">l2tpns-users@lists.sourceforge.net</A>
+</BODY>
+</HTML>
--- /dev/null
+.\" -*- 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 <noodles@the.earth.li>,
+for the Debian GNU/Linux system (but may be used by others).
--- /dev/null
+.\" -*- 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)
--- /dev/null
+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
--- /dev/null
+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 <tid,sid>, 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.
+
--- /dev/null
+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
--- /dev/null
+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 <adrian.kennard@aaisp.net.uk>
+David Parrish <dparrish@gmail.com>
+Michael O'Reilly <michael@optus.net>
+Brendan O'Dea <bod@optus.net>
+Bradley Baetz <bradley.baetz@optus.net>
+Iain Wade <iwade@optus.net>
+Yuri <yuri@actcom.net.il>
+Juergen Kammer <j.kammer@eurodata.de>
+Simon Talbot <simont@nse.co.uk>
+Jonathan McDowell <noodles@earth.li>
+Bjørn Augestad <augestad@users.sourceforge.net>
+Roberto Chostakovis <rchostakovis@users.sourceforge.net>
+Jordan Hrycaj <jordan@mjh.teddy-net.com>
+Vladislav Bjelic <vladislav@gmail.com>
+Alex Kiernan <alex.kiernan@gmail.com>
+Dominique Rousseau <d.rousseau@nnx.com>
+Tim Devries <tdevries@northrock.bm>
+Slobodan Tomic <stomic@loznica.com>
+Michael Chapman <mike.chapman@optus.net>
+Charlie Brady <charlieb@e-smith.com>
+Jon Morby <jon@fido.net>
+Paul Martin <pm@zetnet.net>
+Jonathan Yarden <jyarden@bluegrass.net>
+Patrick Cole <z@amused.net>
+Rhys Kidd <rhys.kidd@staff.westnet.com.au>
--- /dev/null
+// L2TPNS: arp
+
+char const *cvs_id_arp = "$Id: arp.c,v 1.7 2005/07/31 10:04:09 bodea Exp $";
+
+#include <string.h>
+#include <unistd.h>
+#include <net/ethernet.h>
+#include <net/if_arp.h>
+#include <linux/if_packet.h>
+
+#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);
+}
--- /dev/null
+#include <string.h>
+#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;
+}
--- /dev/null
+#include <string.h>
+#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;
+}
--- /dev/null
+/*
+ * 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 <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <fcntl.h>
+
+#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 <sys/epoll.h>
+#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);
+}
--- /dev/null
+/* 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__ */
--- /dev/null
+// 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 <stdio.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <syslog.h>
+#include <malloc.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <time.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <dlfcn.h>
+#include <netdb.h>
+#include <libcli.h>
+
+#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, " <cr>");
+
+ 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;
+}
--- /dev/null
+// 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <string.h>
+#include <malloc.h>
+#include <errno.h>
+#include <libcli.h>
+
+#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);
+}
--- /dev/null
+// 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__ */
--- /dev/null
+// L2TPNS: constants
+
+char const *cvs_id_constants = "$Id: constants.c,v 1.7 2005/07/31 10:04:10 bodea Exp $";
+
+#include <stdio.h>
+#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
+)
--- /dev/null
+#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__ */
--- /dev/null
+// L2TPNS: control
+
+char const *cvs_id_control = "$Id: control.c,v 1.5 2005/07/31 10:04:10 bodea Exp $";
+
+#include <string.h>
+#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");
+}
--- /dev/null
+#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__ */
--- /dev/null
+10.10.10.0/24
+10.13.10.0/24
--- /dev/null
+/var/log/l2tpns {
+ daily
+ missingok
+ rotate 14
+ compress
+ postrotate
+ /usr/bin/killall -HUP l2tpns
+ endscript
+}
--- /dev/null
+# 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"
--- /dev/null
+# List username:password combinations here for cli users
--- /dev/null
+/* 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 <sys/select.h>
+
+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__ */
--- /dev/null
+#include <string.h>
+#include <malloc.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#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]);
+ }
+}
+
--- /dev/null
+// L2TPNS: icmp
+
+char const *cvs_id_icmp = "$Id: icmp.c,v 1.10 2005/08/10 11:25:56 bodea Exp $";
+
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <asm/types.h>
+#include <linux/ip.h>
+#include <linux/icmp.h>
+#include <netinet/icmp6.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <memory.h>
+
+#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;
+}
--- /dev/null
+// 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 <arpa/inet.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/if_tun.h>
+#define SYSLOG_NAMES
+#include <syslog.h>
+#include <malloc.h>
+#include <math.h>
+#include <net/route.h>
+#include <sys/mman.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
+#include <linux/if.h>
+#include <stddef.h>
+#include <time.h>
+#include <dlfcn.h>
+#include <unistd.h>
+#include <sched.h>
+#include <sys/sysinfo.h>
+#include <libcli.h>
+
+#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 <sys/epoll.h>
+#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 <file>\tConfig file\n"
+ "\t-h <hostname>\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;
+}
--- /dev/null
+// 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 <netinet/in.h>
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <sys/types.h>
+#include <libcli.h>
+
+#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__ */
--- /dev/null
+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 <bod@optus.net> 2.1.21-1
+- 2.1.21 release, see /usr/share/doc/l2tpns-2.1.21/Changes
--- /dev/null
+// L2TPNS Linked List Stuff
+
+char const *cvs_id_ll = "$Id: ll.c,v 1.6 2004/11/18 08:12:55 bodea Exp $";
+
+#include <stdio.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <malloc.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <unistd.h>
+#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;
+}
+
--- /dev/null
+#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__ */
--- /dev/null
+/*
+ * This is an OpenSSL-compatible implementation of the RSA Data Security,
+ * Inc. MD5 Message-Digest Algorithm.
+ *
+ * Written by Solar Designer <solar at openwall.com> 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 <string.h>
+
+#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
--- /dev/null
+/*
+ * This is an OpenSSL-compatible implementation of the RSA Data Security,
+ * Inc. MD5 Message-Digest Algorithm.
+ *
+ * Written by Solar Designer <solar at openwall.com> in 2001, and placed
+ * in the public domain. See md5.c for more information.
+ */
+
+#ifdef HAVE_OPENSSL
+#include <openssl/md5.h>
+#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
--- /dev/null
+/* l2tpns plugin control */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <netdb.h>
+#include <signal.h>
+
+#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;
+}
--- /dev/null
+#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__ */
--- /dev/null
+// 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 <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#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);
+}
--- /dev/null
+// 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 <time.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <malloc.h>
+#include <string.h>
+#include <fcntl.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <netinet/in.h>
+#include <errno.h>
+#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));
+}
--- /dev/null
+#! /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;
+ }
+}
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+#include <string.h>
+#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;
+}
--- /dev/null
+#include <string.h>
+#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;
+}
--- /dev/null
+#include <string.h>
+#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;
+}
--- /dev/null
+#include <string.h>
+#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;
+}
--- /dev/null
+// L2TPNS: token bucket filters
+
+char const *cvs_id_tbf = "$Id: tbf.c,v 1.13 2005/07/31 10:04:10 bodea Exp $";
+
+#include <string.h>
+#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;
+}
--- /dev/null
+#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__ */
--- /dev/null
+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.
--- /dev/null
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/if.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <linux/ip.h>
+#include <linux/udp.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <signal.h>
+#include <getopt.h>
+
+#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);
+}
+
--- /dev/null
+#include <arpa/inet.h>
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/if.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <linux/ip.h>
+#include <linux/udp.h>
+#include <unistd.h>
+#include <signal.h>
+#include <getopt.h>
+#include <sys/mman.h>
+
+#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);
+}
+
+// }}}
+
--- /dev/null
+#! /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;
--- /dev/null
+/* RADIUS authentication load test */
+
+#define _SVID_SOURCE
+#define _POSIX_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <sys/time.h>
+#include <time.h>
+#include <fcntl.h>
+#include <sys/select.h>
+#include <signal.h>
+#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);
+}
--- /dev/null
+#include <string.h>
+#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;
+}
--- /dev/null
+/* Misc util functions */
+
+char const *cvs_id_util = "$Id: util.c,v 1.14 2006/04/05 01:45:57 bodea Exp $";
+
+#include <unistd.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <sys/mman.h>
+
+#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);
+}
--- /dev/null
+#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__ */