Imported Upstream version 2.1.21 upstream/2.1.21
authorBenjamin Cama <benoar@dolka.fr>
Thu, 7 Jul 2011 10:45:05 +0000 (12:45 +0200)
committerBenjamin Cama <benoar@dolka.fr>
Thu, 7 Jul 2011 10:45:05 +0000 (12:45 +0200)
57 files changed:
COPYING [new file with mode: 0644]
Changes [new file with mode: 0644]
Docs/l2tpns.8 [new file with mode: 0644]
Docs/manual.html [new file with mode: 0644]
Docs/nsctl.8 [new file with mode: 0644]
Docs/startup-config.5 [new file with mode: 0644]
INSTALL [new file with mode: 0644]
INTERNALS [new file with mode: 0644]
Makefile [new file with mode: 0644]
THANKS [new file with mode: 0644]
arp.c [new file with mode: 0644]
autosnoop.c [new file with mode: 0644]
autothrottle.c [new file with mode: 0644]
bgp.c [new file with mode: 0644]
bgp.h [new file with mode: 0644]
cli.c [new file with mode: 0644]
cluster.c [new file with mode: 0644]
cluster.h [new file with mode: 0644]
constants.c [new file with mode: 0644]
constants.h [new file with mode: 0644]
control.c [new file with mode: 0644]
control.h [new file with mode: 0644]
etc/ip_pool.default [new file with mode: 0644]
etc/l2tpns.logrotate [new file with mode: 0644]
etc/startup-config.default [new file with mode: 0644]
etc/users.default [new file with mode: 0644]
fake_epoll.h [new file with mode: 0644]
garden.c [new file with mode: 0644]
icmp.c [new file with mode: 0644]
l2tpns.c [new file with mode: 0644]
l2tpns.h [new file with mode: 0644]
l2tpns.spec [new file with mode: 0644]
ll.c [new file with mode: 0644]
ll.h [new file with mode: 0644]
md5.c [new file with mode: 0644]
md5.h [new file with mode: 0644]
nsctl.c [new file with mode: 0644]
plugin.h [new file with mode: 0644]
ppp.c [new file with mode: 0644]
radius.c [new file with mode: 0644]
scripts/l2tpns-capture [new file with mode: 0644]
scripts/l2tpns-monitor [new file with mode: 0644]
scripts/l2tpns.script [new file with mode: 0644]
sessionctl.c [new file with mode: 0644]
setrxspeed.c [new file with mode: 0644]
snoopctl.c [new file with mode: 0644]
stripdomain.c [new file with mode: 0644]
tbf.c [new file with mode: 0644]
tbf.h [new file with mode: 0644]
test/README [new file with mode: 0644]
test/bounce.c [new file with mode: 0644]
test/generateload.c [new file with mode: 0644]
test/ping-sweep [new file with mode: 0644]
test/radius.c [new file with mode: 0644]
throttlectl.c [new file with mode: 0644]
util.c [new file with mode: 0644]
util.h [new file with mode: 0644]

diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..d60c31a
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\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.
diff --git a/Changes b/Changes
new file mode 100644 (file)
index 0000000..abe7854
--- /dev/null
+++ b/Changes
@@ -0,0 +1,479 @@
+* 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
diff --git a/Docs/l2tpns.8 b/Docs/l2tpns.8
new file mode 100644 (file)
index 0000000..a5c14b6
--- /dev/null
@@ -0,0 +1,70 @@
+.\" -*- nroff -*-
+.de Id
+.ds Dt \\$4 \\$5
+..
+.Id $Id: l2tpns.8,v 1.4 2005/06/12 06:09:35 bodea Exp $
+.TH L2TPNS 8 "\*(Dt" L2TPNS "System Management Commands"
+.SH NAME
+l2tpns \- Layer 2 tunneling protocol network server (LNS)
+.SH SYNOPSIS
+.B l2tpns
+.RB [ \-d ]
+.RB [ \-v ]
+.RB [ \-c
+.IR file ]
+.RB [ \-h
+.IR hostname ]
+.SH DESCRIPTION
+.B l2tpns
+is a daemon for terminating layer 2 tunneling protocol (L2TP: RFC
+2661) sessions.
+.PP
+Once running,
+.B l2tpns
+may be controlled by telnetting to port 23 on the machine running the
+daemon and with the
+.B nsctl
+utility.
+.SH OPTIONS
+.TP
+.B \-d
+Detach from terminal and fork into the background. By default l2tpns
+will stay in the foreground.
+.TP
+.B \-v
+Increase verbosity for debugging. Can be used multiple times.
+.TP
+.BI "\-c " file
+Specify configuration file.
+.TP
+.BI "\-h " hostname
+Force hostname to
+.IR hostname .
+.SH FILES
+.TP
+.I /etc/l2tpns/startup-config
+The default configuration file.
+.TP
+.I /etc/l2tpns/ip_pool
+IP address pool configuration.
+.TP
+.I /etc/l2tpns/users
+Username/password configuration for access to admin interface.
+.SH SIGNALS
+.TP
+.B SIGHUP
+Reload the config from disk and re-open log file.
+.TP
+.BR SIGTERM ", " SIGINT
+Stop process.  Tunnels and sessions are not terminated.  This signal
+should be used to stop l2tpns on a cluster node where there are other
+machines to continue handling traffic.
+.TP
+.B SIGQUIT
+Shut down tunnels and sessions, exit process when complete.
+.SH SEE ALSO
+.BR startup-config (5),
+.BR nsctl (8)
+.SH AUTHOR
+This manual page was written by Jonathan McDowell <noodles@earth.li>,
+for the Debian GNU/Linux system (but may be used by others).
diff --git a/Docs/manual.html b/Docs/manual.html
new file mode 100644 (file)
index 0000000..5c830f2
--- /dev/null
@@ -0,0 +1,1074 @@
+<!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 &sect;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>&gt;</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>
diff --git a/Docs/nsctl.8 b/Docs/nsctl.8
new file mode 100644 (file)
index 0000000..b7613f6
--- /dev/null
@@ -0,0 +1,69 @@
+.\" -*- nroff -*-
+.de Id
+.ds Dt \\$4 \\$5
+..
+.Id $Id: nsctl.8,v 1.2 2004/11/17 15:08:19 bodea Exp $
+.TH NSCTL 8 "\*(Dt" L2TPNS "System Management Commands"
+.SH NAME
+nsctl \- manage running l2tpns instance
+.SH SYNOPSIS
+.B nsctl
+.RB [ \-d ]
+.RB [ \-h
+.IR host [: port ]]
+.RB [ \-t
+.IR timeout ]
+.I command
+.RI [ arg " ...]"
+.SH DESCRIPTION
+.B nsctl
+sends commands to a running
+.B l2tpns
+process.  It provides both for the loading or unloading of plugins and
+also the management of sessions via functions provided by those plugins.
+.SH OPTIONS
+.TP
+.B \-d
+Enable debugging output.
+.TP
+.B \-h \fIhost\fR[:\fIport\fR]
+The host running
+.B l2tpns
+that should receive the message.  By default the message is sent to
+UDP port 1702 on
+.BR localhost .
+.TP
+.B \-t \fItimeout\fR
+Timeout in seconds to wait for a response from the server.
+.SH COMMANDS
+The first argument specifies the command to send to
+.B l2tpns .
+The following commands are as defined:
+.TP
+.BI "load_plugin " plugin
+Load the named
+.IR plugin .
+.TP
+.BI "unload_plugin " plugin
+Unload the named
+.IR plugin .
+.TP
+.B help
+Each loaded plugin is queried for what commands it supports and the
+synopsis for each is output.
+.PP
+Any other value of
+.I command
+(and
+.I args
+if any)
+are sent to
+.B l2tpns
+as-is, to be passed to each plugin which registers a
+.B plugin_control
+function in turn (in which it may be acted upon).
+.SH SEE ALSO
+.BR l2tpns (8)
+.SH AUTHOR
+This manual page was written by Jonathan McDowell <noodles@the.earth.li>,
+for the Debian GNU/Linux system (but may be used by others).
diff --git a/Docs/startup-config.5 b/Docs/startup-config.5
new file mode 100644 (file)
index 0000000..a74c87f
--- /dev/null
@@ -0,0 +1,363 @@
+.\" -*- nroff -*-
+.de Id
+.ds Dt \\$4 \\$5
+..
+.Id $Id: startup-config.5,v 1.15 2005/09/16 05:04:31 bodea Exp $
+.TH STARTUP-CONFIG 5 "\*(Dt" L2TPNS "File Formats and Conventions"
+.SH NAME
+startup\-config \- configuration file for l2tpns
+.SH SYNOPSIS
+/etc/l2tpns/startup-config
+.SH DESCRIPTION
+.B startup-config
+is the configuration file for
+.BR l2tpns .
+.PP
+The format is plain text, in the same format as accepted by the
+configuration mode of
+.BR l2tpns 's
+telnet administrative interface.  Comments are indicated by either the
+character
+.B #
+or
+.BR ! .
+.SS SETTINGS
+Settings are specified with
+.IP
+.BI "set " "variable value"
+.PP
+The following
+.IR variable s
+may be set:
+.RS
+.TP
+.B debug
+Set the level of debugging messages written to the log file.  The
+value should be between 0 and 5, with 0 being no debugging, and 5
+being the highest.
+.TP
+.B log_file
+This will be where all logging and debugging information is written
+to.  This may be either a filename, such as
+.BR /var/log/l2tpns ,
+or the string
+.BR syslog : \fIfacility\fR ,
+where
+.I facility
+is any one of the syslog logging facilities, such as
+.BR local5 .
+.TP
+.B pid_file
+If set, the process id will be written to the specified file.  The
+value must be an absolute path.
+.TP
+.B random_device
+Path to random data source (default
+.BR /dev/urandom ).
+Use "" to use the rand() library function.
+.TP
+.B l2tp_secret
+The secret used by
+.B l2tpns
+for authenticating tunnel request.  Must be the same as the LAC, or
+authentication will fail.  Only actually be used if the LAC requests
+authentication.
+.TP
+.B l2tp_mtu
+MTU of interface for L2TP traffic (default: 1500).  Used to set link
+MRU and adjust TCP MSS.
+.TP
+.B ppp_restart_time
+Restart timer for PPP protocol negotiation in seconds (default: 3).
+.TP
+.B ppp_max_configure
+Number of configure requests to send before giving up (default: 10).
+.TP
+.B ppp_max_failure
+Number of Configure-Nak requests to send before sending a
+Configure-Reject (default: 5).
+.TP
+.BR primary_dns , " secondary_dns"
+Whenever a PPP connection is established, DNS servers will be sent to the
+user, both a primary and a secondary.  If either is set to 0.0.0.0, then that
+one will not be sent.
+.TP
+.BR primary_radius , " secondary_radius"
+Sets the RADIUS servers used for both authentication and accounting. 
+If the primary server does not respond, then the secondary RADIUS
+server will be tried.
+.TP
+.BR primary_radius_port , " secondary_radius_port"
+Sets the authentication ports for the primary and secondary RADIUS
+servers.  The accounting port is one more than the authentication
+port.  If no ports are given, authentication defaults to 1645, and
+accounting to 1646.
+.TP
+.B radius_accounting
+If set to true, then RADIUS accounting packets will be sent.  A
+.B Start
+record will be sent when the session is successfully authenticated,
+and a
+.B Stop
+record when the session is closed.
+.TP
+.B radius_interim
+If
+.B radius_accounting
+is on, defines the interval between sending of RADIUS interim
+accounting records (in seconds).
+.TP
+.B radius_secret
+Secret to be used in RADIUS packets.
+.TP
+.B radius_authtypes
+A comma separated list of supported RADIUS authentication methods
+("pap" or "chap"), in order of preference (default "pap").
+.TP
+.B radius_dae_port
+Port for DAE RADIUS (Packet of Death/Disconnect, Change of Authorization)
+requests (default: 3799).
+.TP
+.B allow_duplicate_users
+Allow multiple logins with the same username.  If false (the default),
+any prior session with the same username will be dropped when a new
+session is established.
+.TP
+.B bind_address
+When the tun interface is created, it is assigned the address
+specified here.  If no address is given, 1.1.1.1 is used.  Packets
+containing user traffic should be routed via this address if given,
+otherwise the primary address of the machine.
+.TP
+.B peer_address
+Address to send to clients as the default gateway.
+.TP
+.B send_garp
+Determines whether or not to send a gratuitous ARP for the
+.B bind_address
+when the server is ready to handle traffic (default: true).  This
+setting is ignored if BGP is configured.
+.TP
+.B throttle_speed
+Sets the default speed (in kbits/s) which sessions will be limited to.
+.TP
+.B throttle_buckets
+Number of token buckets to allocate for throttling.  Each throttled
+session requires two buckets (in and out).
+.TP
+.B accounting_dir
+If set to a directory, then every 5 minutes the current usage for
+every connected use will be dumped to a file in this directory.
+.TP
+.B setuid
+After starting up and binding the interface, change UID to this.  This
+doesn't work properly.
+.TP
+.B dump_speed
+If set to true, then the current bandwidth utilization will be logged
+every second.  Even if this is disabled, you can see this information
+by running the
+.B uptime
+command on the CLI.
+.TP
+.B multi_read_count
+Number of packets to read off each of the UDP and TUN fds when
+returned as readable by select (default: 10).  Avoids incurring the
+unnecessary system call overhead of select on busy servers.
+.TP
+.B scheduler_fifo
+Sets the scheduling policy for the
+.B l2tpns
+process to
+.BR SCHED_FIFO .
+This causes the kernel to immediately preempt any currently running
+.B SCHED_OTHER
+(normal) process in favour of
+.B l2tpns
+when it becomes runnable. 
+.br
+Ignored on uniprocessor systems.
+.TP
+.B lock_pages
+Keep all pages mapped by the
+.B l2tpns
+process in memory.
+.TP
+.B icmp_rate
+Maximum number of host unreachable ICMP packets to send per second.
+.TP
+.B packet_limit
+Maximum number of packets of downstream traffic to be handled each
+tenth of a second per session.  If zero, no limit is applied (default: 
+0).  Intended as a DoS prevention mechanism and not a general
+throttling control (packets are dropped, not queued).
+.TP
+.B cluster_address
+Multicast cluster address (default: 239.192.13.13).
+.TP
+.B cluster_interface
+Interface for cluster packets (default: eth0).
+.TP
+.B cluster_mcast_ttl
+TTL for multicast packets (default: 1).
+.TP
+.B cluster_hb_interval
+Interval in tenths of a second between cluster heartbeat/pings.
+.TP
+.B cluster_hb_timeout
+Cluster heartbeat timeout in tenths of a second.  A new master will be
+elected when this interval has been passed without seeing a heartbeat
+from the master.
+.TP
+.B cluster_master_min_adv
+Determines the minumum number of up to date slaves required before the
+master will drop routes (default: 1).
+.TP
+.B ipv6_prefix
+Enable negotiation of IPv6.  This forms the the first 64 bits of the
+client allocated address.  The remaining 64 come from the allocated
+IPv4 address and 4 bytes of 0s.
+.RE
+.SS BGP ROUTING
+The routing configuration section is entered by the command
+.IP
+.BI "router bgp " as
+.PP
+where
+.I as
+specifies the local AS number.
+.PP
+Subsequent lines prefixed with
+.BI "neighbour " peer
+define the attributes of BGP neighhbours.  Valid commands are:
+.IP
+.BI "neighbour " peer " remote-as " as
+.br
+.BI "neighbour " peer " timers " "keepalive hold"
+.PP
+Where
+.I peer
+specifies the BGP neighbour as either a hostname or IP address,
+.I as
+is the remote AS number and
+.IR keepalive ,
+.I hold
+are the timer values in seconds.
+.SS NAMED ACCESS LISTS
+Named access lists may be defined with either of
+.IP
+.BI "ip access\-list standard " name
+.br
+.BI "ip access\-list extended " name
+.PP
+Subsequent lines starting with
+.B permit
+or
+.B deny
+define the body of the access\-list.
+.PP
+.B Standard Access Lists
+.RS 4n
+Standard access lists are defined with:
+.IP
+.RB { permit | deny }
+.IR source " [" dest ]
+.PP
+Where
+.I source
+and
+.I dest
+specify IP matches using one of:
+.IP
+.I address
+.I wildard
+.br
+.B host
+.I address
+.br
+.B any
+.PP
+.I address
+and
+.I wildard
+are in dotted-quad notation, bits in the
+.I wildard
+indicate which address bits in
+.I address
+are relevant to the match (0 = exact match; 1 = don't care).
+.PP
+The shorthand
+.RB ' host
+.IR address '
+is equivalent to
+.RI ' address
+.BR 0.0.0.0 ';
+.RB ' any '
+to
+.RB ' 0.0.0.0
+.BR 255.255.255.255 '.
+.RE
+.PP
+.B Extended Access Lists
+.RS 4n
+Extended access lists are defined with:
+.IP
+.RB { permit | deny }
+.I proto
+.IR source " [" ports "] " dest " [" ports "] [" flags ]
+.PP
+Where
+.I proto
+is one of
+.BR ip ,
+.B tcp
+or
+.BR udp ,
+and
+.I source
+and
+.I dest
+are as described above for standard lists.
+.PP
+For TCP and UDP matches, source and destination may be optionally
+followed by a
+.I ports
+specification:
+.IP
+.RB { eq | neq | gt | lt }
+.I port
+.br
+.B
+range
+.I from to
+.PP
+.I flags
+may be one of:
+.RS
+.HP
+.RB { match\-any | match\-all }
+.RB { + | - }{ fin | syn | rst | psh | ack | urg }
+\&...
+.br
+Match packets with any or all of the tcp flags set
+.RB ( + )
+or clear
+.RB ( - ).
+.HP
+.B established
+.br
+Match "established" TCP connections:  packets with
+.B RST
+or
+.B ACK
+set, and
+.B SYN
+clear.
+.HP
+.B fragments
+.br
+Match IP fragments.  May not be specified on rules with layer 4
+matches.
+.RE
+.SH SEE ALSO
+.BR l2tpns (8)
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..0b225c9
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,74 @@
+Brief Installation guide for L2TPNS
+
+1. Requirements
+
+  * libcli 1.8.5 or greater
+    You can get it from http://sourceforge.net/projects/libcli.
+
+  * A kernel with iptables support.
+
+
+2. Compile
+
+  * make
+
+
+3. Install
+
+  * make install.  This process:
+    - Installs the binaries into /usr/sbin (l2tpns and nsctl).
+    - Creates the config dir /etc/l2tpns installs default config files.
+    - Ensures that /dev/net/tun exists.
+
+  * Modify config file.  You probably need to change most of the config
+    options.
+
+  * Set up basic firewall rules.  The l2tpns process listens on a bunch of
+    ports:
+
+       23/tcp          command line interface
+       1701/udp        l2tp (on bind_address)
+       1702/udp        control port (nsctl)
+       3799/udp        RADIUS DAE port
+       32792/udp       clustering messages
+
+  * If you are using the garden plugin, setup the walled garden firewall
+    rules.  These should be in /etc/l2tpns/build-garden, which is run by the
+    plugin after creating/flushing the "garden" nat table.
+
+       iptables -t nat -A garden -p tcp -m tcp --dport 25 -j DNAT --to 192.168.1.1
+       iptables -t nat -A garden -p udp -m udp --dport 53 -j DNAT --to 192.168.1.1
+       iptables -t nat -A garden -p tcp -m tcp --dport 53 -j DNAT --to 192.168.1.1
+       iptables -t nat -A garden -p tcp -m tcp --dport 80 -j DNAT --to 192.168.1.1
+       iptables -t nat -A garden -p tcp -m tcp --dport 110 -j DNAT --to 192.168.1.1
+       iptables -t nat -A garden -p tcp -m tcp --dport 443 -j DNAT --to 192.168.1.1
+       iptables -t nat -A garden -p icmp -m icmp --icmp-type echo-request -j DNAT --to 192.168.1.1
+       iptables -t nat -A garden -p icmp -j ACCEPT
+       iptables -t nat -A garden -j DROP
+
+  * Set up IP address pools in /etc/l2tpns/ip_pool
+
+  * Set up routing.
+    - If you are running a single instance, you can simply statically route
+      the IP pools to the bind_address (l2tpns will send a gratuitous arp).
+
+    - For a cluster, configure the members as BGP neighbours on your router
+      and configure multi-path load-balancing (on Cisco use "maximum-paths").
+
+  * Make l2tpns run on startup.  In a clustered environment running from
+    inittab is recomended:
+
+       l2tp:2345:respawn:/home/l2tpns/src/l2tpns >/dev/null 2>&1
+
+  * Test it out.
+
+
+
+This software is quite stable and is being used in a production environment at
+a quite large ISP.  However, you may have problems setting it up, and if so, I
+would appreciate it if you would file useful bug reports on the Source Forge
+page:
+
+http://sourceforge.net/projects/l2tpns/
+
+-- David Parrish
diff --git a/INTERNALS b/INTERNALS
new file mode 100644 (file)
index 0000000..b7b0b8c
--- /dev/null
+++ b/INTERNALS
@@ -0,0 +1,265 @@
+Documentation on various internal structures.
+
+Most important structure use an anonymous shared mmap()
+so that child processes can watch them. (All the cli connections
+are handled in child processes).
+
+TODO: Re-investigate threads to see if we can use a thread to handle
+cli connections without killing forwarding performance.
+
+session[]
+       An array of session structures. This is one of the two
+       major data structures that are sync'ed across the cluster.
+
+       This array is statically allocated at startup time to a 
+       compile time size (currently 50k sessions). This sets a
+       hard limit on the number of sessions a cluster can handle.
+
+       There is one element per l2tp session. (I.e. each active user).
+
+       The zero'th session is always invalid.
+
+tunnel[]
+       An array of tunnel structures. This is the other major data structure
+       that's actively sync'ed across the cluster.
+
+       As per sessions, this is statically allocated at startup time
+       to a compile time size limit.
+
+       There is one element per l2tp tunnel. (normally one per BRAS
+       that this cluster talks to).
+
+       The zero'th tunnel is always invalid.
+
+ip_pool[]
+       
+       A table holding all the IP address in the pool. As addresses
+       are used, they are tagged with the username of the session,
+       and the session index.
+
+       When they are free'd the username tag ISN'T cleared. This is
+       to ensure that were possible we re-allocate the same IP
+       address back to the same user.
+
+radius[]
+       A table holding active radius session. Whenever a radius
+       conversation is needed (login, accounting et al), a radius
+       session is allocated.
+
+char **ip_hash
+
+       A mapping of IP address to session structure. This is a
+       tenary tree (each byte of the IP address is used in turn
+       to index that level of the tree).
+
+       If the value is postive, it's considered to be an index
+       into the session table. 
+
+       If it's negative, it's considered to be an index into
+       the ip_pool[] table.
+
+       If it's zero, then there is no associated value.
+
+config->cluster_iam_master 
+
+       If true, indicates that this node is the master for
+       the cluster. This has many consequences...
+       
+config->cluster_iam_uptodate
+       
+       On the slaves, this indicates if it's seen a full run
+       of sessions from the master, and thus it's safe to be
+       taking traffic. 
+
+       On the master, this indicates that all slaves are
+       up to date. If any of the slaves aren't up to date,
+       this variable is false, and indicates that we should
+       shift to more rapid heartbeats to bring the slave
+       back up to date.
+
+
+============================================================
+
+Clustering: How it works.
+
+       At a high level, the various members of the cluster elect
+a master. All other machines become slaves as soon as they hear
+a heartbeat from the master. Slaves handle normal packet forwarding.
+Whenever a slave get a 'state changing' packet (i.e. tunnel setup/teardown,
+session setup etc) it _doesn't_ handle it, but instead forwards it
+to the master.
+
+       'State changing' it defined to be "a packet that would cause
+a change in either a session or tunnel structure that isn't just
+updating the idle time or byte counters". In practise, this means
+almost all LCP, IPCP, and L2TP control packets.
+
+       The master then handles the packet normally, updating
+the session/tunnel structures. The changed structures are then
+flooded out to the slaves via a multicast packet.
+
+
+Heartbeat'ing:
+       The master sends out a multicast 'heartbeat' packet
+at least once every second. This packet contains a sequence number,
+and any changes to the session/tunnel structures that have
+been queued up. If there is room in the packet, it also sends
+out a number of extra session/tunnel structures.
+
+       The sending out of 'extra' structures means that the
+master will slowly walk the entire session and tunnel tables.
+This allows a new slave to catch-up on cluster state.
+
+
+       Each heartbeat has an in-order sequence number. If a
+slave receives a heartbeat with a sequence number other than 
+the one it was expecting, it drops the unexpected packet and
+unicasts C_LASTSEEN to tell the master the last heartbeast it
+had seen. The master normally than unicasts the missing packets
+to the slave. If the master doesn't have the old packet any more
+(i.e. it's outside the transmission window) then the master
+unicasts C_KILL to the slave asking it to die. (The slave should
+then restart, and catchup on state via the normal process).
+
+       If a slave goes for more than a few seconds without
+hearing from the master, it sends out a preemptive C_LASTSEEN.
+If the master still exists, this forces to the master to unicast
+the missed heartbeats. This is a work around for a temporary
+multicast problem. (i.e. if an IGMP probe is missed, the slave
+will temporarily stop seeing the multicast heartbeats. This
+work around prevents the slave from becoming master with 
+horrible consequences).
+
+Ping'ing:
+       All slaves send out a 'ping' once per second as a
+multicast packet. This 'ping' contains the slave's ip address,
+and most importantly, the number of seconds from epoch
+that the slave started up. (I.e. the value of time(2) at
+that the process started). (This is the 'basetime').
+Obviously, this is never zero.
+
+       There is a special case. The master can send a single
+ping on shutdown to indicate that it is dead and that an
+immediate election should be held. This special ping is
+send from the master with a 'basetime' of zero.
+
+Elections:
+
+       All machines start up as slaves.
+
+       Each slave listens for a heartbeat from the master.
+If a slave fails to hear a heartbeat for N seconds then it
+checks to see if it should become master.
+
+       A slave will become master if:
+               * It hasn't heard from a master for N seconds.
+               * It is the oldest of all it's peers (the other slaves).
+               * In the event of a tie, the machine with the
+                       lowest IP address will win.
+
+       A 'peer' is any other slave machine that's send out a
+       ping in the last N seconds. (i.e. we must have seen
+       a recent ping from that slave for it to be considered).
+
+       The upshot of this is that no special communication
+       takes place when a slave becomes a master.
+
+       On initial cluster startup, the process would be (for example)
+
+               * 3 machines startup simultaneously, all as slaves.
+               * each machine sends out a multicast 'ping' every second.
+               * 15 seconds later, the machine with the lowest IP
+                       address becomes master, and starts sending
+                       out heartbeats.
+               * The remaining two machine hear the heartbeat and
+                       set that machine as their master.
+
+Becoming master:
+       
+       When a slave become master, the only structure maintained up
+       to date are the tunnel and session structures. This means
+       the master will rebuild a number of mappings.
+
+       #0. All the session and table structures are marked as
+       defined. (Even if we weren't fully up to date, it's
+       too late now).
+
+       #1. All the token bucket filters are re-build from scratch
+       with the associated session to tbf pointers being re-built.
+
+TODO: These changed tbf pointers aren't flooded to the slave right away!
+Throttled session could take a couple of minutes to start working again
+on master failover!
+
+       #2. The ipcache to session hash is rebuilt. (This isn't
+       strictly needed, but it's a safety measure).
+
+       #3. The mapping from the ippool into the session table
+       (and vice versa) is re-built.
+
+
+Becoming slave:
+
+       At startup the entire session and table structures are
+       marked undefined.
+
+       As it seens updates from the master, the updated structures
+       are marked as defined.
+
+       When there are no undefined tunnel or session structures, the
+       slave marks itself as 'up-to-date' and starts advertising routes
+       (if BGP is enabled).
+
+STONITH:
+       
+       Currently, there is very minimal protection from split brain.
+In particular, there is no real STONITH protocol to stop two masters
+appearing in the event of a network problem.
+
+
+
+TODO:
+       Should slaves that have undefined sessions, and receive
+a packet from a non-existant session then forward it to the master??
+In normal practice, a slave with undefined session shouldn't be 
+handling packets, but ...
+
+       There is far too much walking of large arrays (in the master
+specifically).  Although this is mitigated somewhat by the
+cluster_high_{sess,tun}, this benefit is lost as that value gets
+closer to MAX{SESSION,TUNNEL}.  There are two issues here:
+       
+       * The tunnel, radius and tbf arrays should probably use a
+         mechanism like sessions, where grabbing a new one is a
+         single lookup rather than a walk.
+
+       * A list structure (simillarly rooted at [0].interesting) is
+         required to avoid having to walk tables periodically.  As a
+         back-stop the code in the master which *does* walk the
+         arrays can mark any entry it processes as "interesting" to
+         ensure it gets looked at even if a bug causes it to be
+         otherwiase overlooked.
+
+       Support for more than 64k sessions per cluster. There is
+currently a 64k session limit because each session gets an id that global
+over the cluster (as opposed to local to the tunnel). Obviously, the tunnel
+id needs to be used in conjunction with the session id to index into
+the session table. But how?
+
+       I think the best way is to use something like page tables.
+for a given <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.
+
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..ea7e8df
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,133 @@
+DESTDIR =
+bindir = /usr/sbin
+etcdir = /etc/l2tpns
+libdir = /usr/lib/l2tpns
+man5dir = /usr/share/man/man5
+man8dir = /usr/share/man/man8
+statedir = /var/lib/l2tpns
+
+DEFINES =
+DEFINES += -DLIBDIR='"$(libdir)"'
+DEFINES += -DETCDIR='"$(etcdir)"'
+
+OPTIM =
+OPTIM += -g
+OPTIM += -O3
+
+CC = gcc
+LD = gcc
+INCLUDES = -I.
+CPPFLAGS = $(INCLUDES) $(DEFINES)
+CFLAGS = -Wall -Wformat-security -Wno-format-zero-length $(OPTIM)
+LDFLAGS =
+LDLIBS =
+INSTALL = install -c -D -o root -g root
+
+l2tpns.LIBS = -lm -lcli -ldl
+
+OBJS = arp.o cli.o cluster.o constants.o control.o icmp.o l2tpns.o \
+    ll.o md5.o ppp.o radius.o tbf.o util.o
+
+PROGRAMS = l2tpns nsctl
+PLUGINS = autosnoop.so autothrottle.so garden.so sessionctl.so \
+    setrxspeed.so snoopctl.so stripdomain.so throttlectl.so
+
+DEFINES += -DSTATISTICS
+DEFINES += -DSTAT_CALLS
+DEFINES += -DRINGBUFFER
+
+ifneq (2.4, $(shell uname -r | perl -pe 's/^(\d+\.\d+).*/$$1/'))
+ DEFINES += -DHAVE_EPOLL
+endif
+
+DEFINES += -DBGP
+OBJS += bgp.o
+
+all: programs plugins
+programs: $(PROGRAMS)
+plugins: $(PLUGINS)
+
+clean:
+       rm -f *.o test/*.o $(PROGRAMS) $(PLUGINS) Makefile.tmp Makefile.bak
+
+depend:
+       (sed -n 'p; /^## Dependencies: (autogenerated) ##/q' Makefile && \
+           gcc -MM $(CPPFLAGS) $(OBJS:.o=.c) && \
+           gcc -MM $(CPPFLAGS) $(PLUGINS:.so=.c) | sed 's/\.o/.so/') >Makefile.tmp
+       mv Makefile Makefile.bak
+       mv Makefile.tmp Makefile
+
+l2tpns: $(OBJS)
+       $(LD) $(LDFLAGS) -o $@ $^ $(LDLIBS) $($@.LIBS)
+
+nsctl: nsctl.o control.o
+       $(LD) $(LDFLAGS) -o $@ $^ $(LDLIBS) $($@.LIBS)
+
+%.o: %.c
+       $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
+
+%.so: %.c
+       $(CC) -fPIC -shared $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $<
+
+install: all
+       $(INSTALL) -m 0755 l2tpns $(DESTDIR)$(bindir)/l2tpns
+       $(INSTALL) -m 0755 nsctl $(DESTDIR)$(bindir)/nsctl
+
+       $(INSTALL) -m 0644 Docs/startup-config.5 $(DESTDIR)$(man5dir)/startup-config.5
+       $(INSTALL) -m 0644 Docs/l2tpns.8 $(DESTDIR)$(man8dir)/l2tpns.8
+       $(INSTALL) -m 0644 Docs/nsctl.8 $(DESTDIR)$(man8dir)/nsctl.8
+
+       gzip --best --force $(DESTDIR)$(man5dir)/*.5 $(DESTDIR)$(man8dir)/*.8
+
+       @for config in startup-config users ip_pool; \
+       do \
+           suffix=; \
+           mode=0600; [ $$config = ip_pool ] && mode=0644; \
+           if [ -f $(DESTDIR)$(etcdir)/$$config ]; \
+           then \
+               cmp -s etc/$$config.default $(DESTDIR)$(etcdir)/$$config && continue; \
+               suffix=.default; \
+           fi; \
+           echo $(INSTALL) -m $$mode etc/$$config.default $(DESTDIR)$(etcdir)/$$config$$suffix; \
+           $(INSTALL) -m $$mode etc/$$config.default $(DESTDIR)$(etcdir)/$$config$$suffix; \
+       done
+
+       @for plugin in $(PLUGINS); \
+       do \
+               echo $(INSTALL) -m 0755 $$plugin $(DESTDIR)$(libdir)/$$plugin; \
+               $(INSTALL) -m 0755 $$plugin $(DESTDIR)$(libdir)/$$plugin; \
+       done
+
+       @if [ -z $(DESTDIR) ] && [ ! -e /dev/net/tun ]; \
+       then \
+               mkdir /dev/net; \
+               echo mknod /dev/net/tun c 10 200; \
+               mknod /dev/net/tun c 10 200; \
+       fi
+
+.PHONY: all clean depend install
+
+## Dependencies: (autogenerated) ##
+arp.o: arp.c l2tpns.h
+cli.o: cli.c l2tpns.h constants.h util.h cluster.h tbf.h ll.h bgp.h
+cluster.o: cluster.c l2tpns.h cluster.h util.h tbf.h bgp.h
+constants.o: constants.c constants.h
+control.o: control.c l2tpns.h control.h
+icmp.o: icmp.c l2tpns.h
+l2tpns.o: l2tpns.c md5.h l2tpns.h cluster.h plugin.h ll.h constants.h \
+  control.h util.h tbf.h bgp.h
+ll.o: ll.c ll.h
+md5.o: md5.c md5.h
+ppp.o: ppp.c l2tpns.h constants.h plugin.h util.h tbf.h cluster.h
+radius.o: radius.c md5.h constants.h l2tpns.h plugin.h util.h cluster.h
+tbf.o: tbf.c l2tpns.h util.h tbf.h
+util.o: util.c l2tpns.h bgp.h
+bgp.o: bgp.c l2tpns.h bgp.h util.h
+autosnoop.so: autosnoop.c l2tpns.h plugin.h
+autothrottle.so: autothrottle.c l2tpns.h plugin.h
+garden.so: garden.c l2tpns.h plugin.h control.h
+sessionctl.so: sessionctl.c l2tpns.h plugin.h control.h
+setrxspeed.so: setrxspeed.c l2tpns.h plugin.h
+snoopctl.so: snoopctl.c l2tpns.h plugin.h control.h
+stripdomain.so: stripdomain.c l2tpns.h plugin.h
+throttlectl.so: throttlectl.c l2tpns.h plugin.h control.h
diff --git a/THANKS b/THANKS
new file mode 100644 (file)
index 0000000..c39a07d
--- /dev/null
+++ b/THANKS
@@ -0,0 +1,29 @@
+A list of people who have contributed to the development of L2TPNS
+by reporting problems, suggesting various improvements or submitting
+actual code follows.
+
+Adrian Kennard             <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>
diff --git a/arp.c b/arp.c
new file mode 100644 (file)
index 0000000..5ffd1a4
--- /dev/null
+++ b/arp.c
@@ -0,0 +1,64 @@
+// L2TPNS: arp
+
+char const *cvs_id_arp = "$Id: arp.c,v 1.7 2005/07/31 10:04:09 bodea Exp $";
+
+#include <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);
+}
diff --git a/autosnoop.c b/autosnoop.c
new file mode 100644 (file)
index 0000000..fa3de8b
--- /dev/null
@@ -0,0 +1,75 @@
+#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;
+}
diff --git a/autothrottle.c b/autothrottle.c
new file mode 100644 (file)
index 0000000..cd42365
--- /dev/null
@@ -0,0 +1,158 @@
+#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;
+}
diff --git a/bgp.c b/bgp.c
new file mode 100644 (file)
index 0000000..6a868fa
--- /dev/null
+++ b/bgp.c
@@ -0,0 +1,1244 @@
+/*
+ * BGPv4
+ * Used to advertise routes for upstream (l2tp port, rather than gratiutious
+ * arp) and downstream--allowing routers to load-balance both.
+ *
+ * Implementation limitations:
+ * - We never listen for incoming connections (session always initiated by us).
+ * - Any routes advertised by the peer are accepted, but ignored.
+ * - No password support; neither RFC1771 (which no-one seems to do anyway)
+ *   nor RFC2385 (which requires a kernel patch on 2.4 kernels).
+ */
+
+char const *cvs_id_bgp = "$Id: bgp.c,v 1.12 2005/09/02 23:39:36 bodea Exp $";
+
+#include <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);
+}
diff --git a/bgp.h b/bgp.h
new file mode 100644 (file)
index 0000000..0a2e59c
--- /dev/null
+++ b/bgp.h
@@ -0,0 +1,205 @@
+/* BGPv4 (RFC1771) */
+/* $Id: bgp.h,v 1.5 2005/06/04 15:42:35 bodea Exp $ */
+
+#ifndef __BGP_H__
+#define __BGP_H__
+
+#define BGP_MAX_PACKET_SIZE    4096
+#define BGP_HOLD_TIME          180     /* seconds before peer times us out */
+#define BGP_KEEPALIVE_TIME     60      /* seconds between messages */
+#define BGP_STATE_TIME         60      /* state transition timeout in seconds */
+#define BGP_MAX_RETRY          42      /* maximum number of times to retry */
+#define BGP_RETRY_BACKOFF      60      /* number of seconds between retries,
+                                          cumulative */
+
+#define BGP_METRIC             1       /* multi_exit_disc */
+#define BGP_LOCAL_PREF         100     /* local preference value */
+
+struct bgp_header {
+    char marker[16];
+    uint16_t len;
+    uint8_t type;
+} __attribute__ ((packed));
+
+/* bgp_header.type */
+#define BGP_MSG_OPEN           1
+#define BGP_MSG_UPDATE         2
+#define BGP_MSG_NOTIFICATION   3
+#define BGP_MSG_KEEPALIVE      4
+
+struct bgp_packet {
+    struct bgp_header header;
+    char data[BGP_MAX_PACKET_SIZE - sizeof(struct bgp_header)]; /* variable */
+} __attribute__ ((packed));
+
+struct bgp_data_open {
+    uint8_t version;
+#define BGP_VERSION    4
+    uint16_t as;
+    uint16_t hold_time;
+    uint32_t identifier;
+    uint8_t opt_len;
+#define BGP_DATA_OPEN_SIZE     10 /* size of struct excluding opt_params */
+    char opt_params[sizeof(((struct bgp_packet *)0)->data) - BGP_DATA_OPEN_SIZE]; /* variable */
+} __attribute__ ((packed));
+
+struct bgp_ip_prefix {
+    uint8_t len;
+    uint32_t prefix; /* variable */
+} __attribute__ ((packed));
+
+#define BGP_IP_PREFIX_SIZE(p) (1 + ((p).len / 8) + ((p).len % 8 != 0))
+
+struct bgp_path_attr {
+    uint8_t flags;
+    uint8_t code;
+    union {
+       struct {
+           uint8_t len;
+           char value[29];             /* semi-random size, adequate for l2tpns */
+       } __attribute__ ((packed)) s;   /* short */
+       struct {
+           uint16_t len;
+           char value[28];
+       } __attribute__ ((packed)) e;   /* extended */
+    } data; /* variable */
+} __attribute__ ((packed));
+
+/* bgp_path_attr.flags (bitfields) */
+#define BGP_PATH_ATTR_FLAG_OPTIONAL    (1 << 7)
+#define BGP_PATH_ATTR_FLAG_TRANS       (1 << 6)
+#define BGP_PATH_ATTR_FLAG_PARTIAL     (1 << 5)
+#define BGP_PATH_ATTR_FLAG_EXTLEN      (1 << 4)
+
+/* bgp_path_attr.code, ...value */
+#define BGP_PATH_ATTR_CODE_ORIGIN              1       /* well-known, mandatory */
+#  define BGP_PATH_ATTR_CODE_ORIGIN_IGP                  0
+#  define BGP_PATH_ATTR_CODE_ORIGIN_EGP                  1
+#  define BGP_PATH_ATTR_CODE_ORIGIN_INCOMPLETE   2
+#define BGP_PATH_ATTR_CODE_AS_PATH             2       /* well-known, mandatory */
+#  define BGP_PATH_ATTR_CODE_AS_PATH_AS_SET      1
+#  define BGP_PATH_ATTR_CODE_AS_PATH_AS_SEQUENCE  2
+#define BGP_PATH_ATTR_CODE_NEXT_HOP            3       /* well-known, mandatory */
+#define BGP_PATH_ATTR_CODE_MULTI_EXIT_DISC     4       /* optional, non-transitive */
+#define BGP_PATH_ATTR_CODE_LOCAL_PREF          5       /* well-known, discretionary */
+#define BGP_PATH_ATTR_CODE_ATOMIC_AGGREGATE    6       /* well-known, discretionary */
+#define BGP_PATH_ATTR_CODE_AGGREGATOR          7       /* optional, transitive */
+#define BGP_PATH_ATTR_CODE_COMMUNITIES         8       /* optional, transitive (RFC1997) */
+
+#define BGP_PATH_ATTR_SIZE(p) ((((p).flags & BGP_PATH_ATTR_FLAG_EXTLEN) \
+    ? ((p).data.e.len + 1) : (p).data.s.len) + 3)
+
+/* well known COMMUNITIES */
+#define BGP_COMMUNITY_NO_EXPORT                        0xffffff01      /* don't advertise outside confederation */
+#define BGP_COMMUNITY_NO_ADVERTISE             0xffffff02      /* don't advertise to any peer */
+#define BGP_COMMUNITY_NO_EXPORT_SUBCONFED      0xffffff03      /* don't advertise to any other AS */
+
+struct bgp_data_notification {
+    uint8_t error_code;
+    uint8_t error_subcode;
+    char data[sizeof(((struct bgp_packet *)0)->data) - 2]; /* variable */
+} __attribute__ ((packed));
+
+/* bgp_data_notification.error_code, .error_subcode */
+#define BGP_ERR_HEADER                 1
+#  define BGP_ERR_HDR_NOT_SYNC           1
+#  define BGP_ERR_HDR_BAD_LEN            2
+#  define BGP_ERR_HDR_BAD_TYPE           3
+#define BGP_ERR_OPEN                   2
+#  define BGP_ERR_OPN_VERSION            1
+#  define BGP_ERR_OPN_BAD_AS             2
+#  define BGP_ERR_OPN_BAD_IDENT                  3
+#  define BGP_ERR_OPN_UNSUP_PARAM        4
+#  define BGP_ERR_OPN_AUTH_FAILURE       5
+#  define BGP_ERR_OPN_HOLD_TIME                  6
+#define BGP_ERR_UPDATE                 3
+#  define BGP_ERR_UPD_BAD_ATTR_LIST      1
+#  define BGP_ERR_UPD_UNKN_WK_ATTR       2
+#  define BGP_ERR_UPD_MISS_WK_ATTR       3
+#  define BGP_ERR_UPD_BAD_ATTR_FLAG      4
+#  define BGP_ERR_UPD_BAD_ATTR_LEN       5
+#  define BGP_ERR_UPD_BAD_ORIGIN         6
+#  define BGP_ERR_UPD_ROUTING_LOOP       7
+#  define BGP_ERR_UPD_BAD_NEXT_HOP       8
+#  define BGP_ERR_UPD_BAD_OPT_ATTR       9
+#  define BGP_ERR_UPD_BAD_NETWORK        10
+#  define BGP_ERR_UPD_BAD_AS_PATH        11
+#define BGP_ERR_HOLD_TIMER_EXP         4
+#define BGP_ERR_FSM                    5
+#define BGP_ERR_CEASE                  6
+
+enum bgp_state {
+    Disabled,                          /* initial, or failed */
+    Idle,                              /* trying to connect */
+    Connect,                           /* connect issued */
+    Active,                            /* connected, waiting to send OPEN */
+    OpenSent,                          /* OPEN sent, waiting for peer OPEN */
+    OpenConfirm,                       /* KEEPALIVE sent, waiting for peer KEEPALIVE */
+    Established,                       /* established */
+};
+
+struct bgp_route_list {
+    struct bgp_ip_prefix dest;
+    struct bgp_route_list *next;
+};
+
+struct bgp_buf {
+    struct bgp_packet packet;          /* BGP packet */
+    size_t done;                       /* bytes sent/recvd */
+};
+
+/* state */
+struct bgp_peer {
+    char name[32];                     /* peer name */
+    in_addr_t addr;                    /* peer address */
+    int as;                            /* AS number */
+    int sock;
+    enum bgp_state state;              /* FSM state */
+    enum bgp_state next_state;         /* next state after outbuf cleared */
+    time_t state_time;                 /* time of last state change */
+    time_t keepalive_time;             /* time to send next keepalive */
+    time_t retry_time;                 /* time for connection retry */
+    int retry_count;                   /* connection retry count */
+    int init_keepalive;                        /* initial keepalive time */
+    int init_hold;                     /* initial hold time */
+    int keepalive;                     /* negotiated keepalive time */
+    int hold;                          /* negotiated hold time */
+    time_t expire_time;                        /* time next peer packet expected */
+    int routing;                       /* propagate routes */
+    int update_routes;                 /* UPDATE required */
+    struct bgp_route_list *routes;     /* routes known by this peer */
+    struct bgp_buf *outbuf;            /* pending output */
+    struct bgp_buf *inbuf;             /* pending input */
+    int cli_flag;                      /* updates requested from CLI */
+    char *path_attrs;                  /* path attrs to send in UPDATE message */
+    int path_attr_len;                 /* length of path attrs */
+    uint32_t events;                   /* events to poll */
+    struct event_data edata;           /* poll data */
+};
+
+/* bgp_peer.cli_flag */
+#define BGP_CLI_SUSPEND                1
+#define BGP_CLI_ENABLE         2
+#define BGP_CLI_RESTART                3
+
+extern struct bgp_peer *bgp_peers;
+extern int bgp_configured;
+
+/* actions */
+int bgp_setup(int as);
+int bgp_start(struct bgp_peer *peer, char *name, int as, int keepalive,
+    int hold, int enable);
+
+void bgp_stop(struct bgp_peer *peer);
+void bgp_halt(struct bgp_peer *peer);
+int bgp_restart(struct bgp_peer *peer);
+int bgp_add_route(in_addr_t ip, in_addr_t mask);
+int bgp_del_route(in_addr_t ip, in_addr_t mask);
+void bgp_enable_routing(int enable);
+int bgp_set_poll(void);
+int bgp_process(uint32_t events[]);
+char const *bgp_state_str(enum bgp_state state);
+
+extern char const *cvs_id_bgp;
+
+#endif /* __BGP_H__ */
diff --git a/cli.c b/cli.c
new file mode 100644 (file)
index 0000000..bff3bf8
--- /dev/null
+++ b/cli.c
@@ -0,0 +1,3066 @@
+// L2TPNS Command Line Interface
+// vim: sw=8 ts=8
+
+char const *cvs_name = "$Name: release_2_1_21 $";
+char const *cvs_id_cli = "$Id: cli.c,v 1.71 2005/12/06 09:43:42 bodea Exp $";
+
+#include <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;
+}
diff --git a/cluster.c b/cluster.c
new file mode 100644 (file)
index 0000000..538da20
--- /dev/null
+++ b/cluster.c
@@ -0,0 +1,1870 @@
+// L2TPNS Clustering Stuff
+
+char const *cvs_id_cluster = "$Id: cluster.c,v 1.50.2.1 2006/12/02 14:09:14 bodea Exp $";
+
+#include <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);
+}
diff --git a/cluster.h b/cluster.h
new file mode 100644 (file)
index 0000000..fe95218
--- /dev/null
+++ b/cluster.h
@@ -0,0 +1,89 @@
+// L2TPNS Clustering Stuff
+// $Id: cluster.h,v 1.14 2005/07/31 10:04:10 bodea Exp $
+
+#ifndef __CLUSTER_H__
+#define __CLUSTER_H__
+
+
+#define C_HEARTBEAT            1
+#define C_ACK                  2
+#define C_PING                 3
+#define C_TUNNEL               4       // Tunnel structure.
+#define C_SESSION              5       // Session structure.
+#define C_GOODBYE              6
+#define C_LASTSEEN             7       // Tell master the last heartbeat that I handled.
+#define C_KILL                 8       // Tell a slave to die.
+#define C_FORWARD              9       // Forwarded packet..
+#define C_BYTES                        10      // Update byte counters.
+#define C_THROTTLE             11      // A packet for the master to throttle. (The TBF tells direction).
+#define C_CSESSION             12      // Compressed session structure.
+#define C_CTUNNEL              13      // Compressed tunnel structure.
+#define C_GARDEN               14      // Gardened packet
+#define C_MASTER               15      // Tell a slave the address of the master.
+#define C_FORWARD_DAE          16      // A DAE packet for the master to handle
+
+#define HB_VERSION             5       // Protocol version number..
+#define HB_MAX_SEQ             (1<<30) // Maximum sequence number. (MUST BE A POWER OF 2!)
+#define HB_HISTORY_SIZE                64      // How many old heartbeats we remember?? (Must be a factor of HB_MAX_SEQ)
+
+#define PING_INTERVAL          5       // 0.5 seconds. Needs to be short to keep session tables fresh.
+#define HB_TIMEOUT             (15*2*PING_INTERVAL) // 15 seconds without heartbeat triggers an election..
+
+#define CLUSTERPORT            32792
+#define CLUSTER_MAX_SIZE       32      // No more than 32 machines in a cluster!
+
+#define DEFAULT_MCAST_ADDR     "239.192.13.13"         // Need an assigned number!
+#define DEFAULT_MCAST_INTERFACE        "eth0"
+
+typedef struct {
+       uint32_t version;       // protocol version.
+       uint32_t seq;           // Sequence number for this heatbeat.
+       uint32_t basetime;      // What time I started
+       uint32_t clusterid;     // Id for this cluster?
+
+       uint32_t highsession;   // Id of the highest in-use session.
+       uint32_t freesession;   // Id of the first free session.
+       uint32_t hightunnel;    // Id of the highest used tunnel.
+       uint32_t size_sess;     // Size of the session structure.
+
+       uint32_t size_tunn;     // size of the tunnel structure.
+       uint32_t interval;      // ping/heartbeat interval
+       uint32_t timeout;       // heartbeat timeout
+
+       uint64_t table_version; // # state changes processed by cluster
+
+       char reserved[128 - 13*sizeof(uint32_t)];       // Pad out to 128 bytes.
+} heartt;
+
+typedef struct {               /* Used to update byte counters on the */
+                               /* master. */
+       uint32_t sid;
+       uint32_t pin;
+       uint32_t pout;
+       uint32_t cin;
+       uint32_t cout;
+} bytest;
+
+typedef struct {
+       in_addr_t addr;         // peer address
+       uint32_t ver;           // version of structure.
+       uint32_t undef;         // Number of undefined structures. 0 if up-to-date.
+       uint32_t basetime;      // start time of this peer.
+} pingt;
+
+int cluster_init(void);
+int processcluster(uint8_t *buf, int size, in_addr_t addr);
+int cluster_send_session(int sid);
+int cluster_send_tunnel(int tid);
+int master_forward_packet(uint8_t *data, int size, in_addr_t addr, int port);
+int master_forward_dae_packet(uint8_t *data, int size, in_addr_t addr, int port);
+int master_throttle_packet(int tid, uint8_t *data, int size);
+int master_garden_packet(sessionidt s, uint8_t *data, int size);
+void master_update_counts(void);
+void cluster_send_ping(time_t basetime);
+void cluster_heartbeat(void);
+void cluster_check_master(void);
+void cluster_check_slaves(void);
+int cmd_show_cluster(struct cli_def *cli, char *command, char **argv, int argc);
+
+#endif /* __CLUSTER_H__ */
diff --git a/constants.c b/constants.c
new file mode 100644 (file)
index 0000000..4efe717
--- /dev/null
@@ -0,0 +1,227 @@
+// L2TPNS: constants
+
+char const *cvs_id_constants = "$Id: constants.c,v 1.7 2005/07/31 10:04:10 bodea Exp $";
+
+#include <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
+)
diff --git a/constants.h b/constants.h
new file mode 100644 (file)
index 0000000..a9693c3
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef __CONSTANTS_H__
+#define __CONSTANTS_H__
+
+char const *l2tp_code(int type);
+char const *l2tp_avp_name(int avp);
+char const *l2tp_stopccn_result_code(int code);
+char const *l2tp_cdn_result_code(int code);
+char const *l2tp_error_code(int code);
+char const *ppp_phase(int code);
+char const *ppp_state(int code);
+char const *ppp_auth_type(int type);
+char const *ppp_code(int type);
+char const *ppp_lcp_option(int type);
+char const *radius_state(int state);
+char const *radius_code(int code);
+
+#endif /* __CONSTANTS_H__ */
diff --git a/control.c b/control.c
new file mode 100644 (file)
index 0000000..b87ee07
--- /dev/null
+++ b/control.c
@@ -0,0 +1,163 @@
+// L2TPNS: control
+
+char const *cvs_id_control = "$Id: control.c,v 1.5 2005/07/31 10:04:10 bodea Exp $";
+
+#include <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");
+}
diff --git a/control.h b/control.h
new file mode 100644 (file)
index 0000000..e1f7d54
--- /dev/null
+++ b/control.h
@@ -0,0 +1,54 @@
+#ifndef __CONTROL_H__
+#define __CONTROL_H__
+
+#define NSCTL_PORT             1702
+#define NSCTL_MAGIC            0x9013
+
+/* builtin commands */
+#define NSCTL_REQUEST          (1 << 4)
+#define NSCTL_REQ_LOAD         (NSCTL_REQUEST | 1)
+#define NSCTL_REQ_UNLOAD       (NSCTL_REQUEST | 2)
+#define NSCTL_REQ_HELP         (NSCTL_REQUEST | 3)
+
+/* general control message, passed to plugins */
+#define NSCTL_REQ_CONTROL      (NSCTL_REQUEST | 4)
+
+/* response messages */
+#define NSCTL_RESPONSE         (1 << 5)
+#define NSCTL_RES_OK           (NSCTL_RESPONSE | 1)
+#define NSCTL_RES_ERR          (NSCTL_RESPONSE | 2)
+
+/* unpack errors */
+#define NSCTL_ERR_SHORT                -1      // short packet
+#define NSCTL_ERR_LONG         -2      // packet exceeds max, or trailing cr*p
+#define NSCTL_ERR_MAGIC                -3      // invalid magic number
+#define NSCTL_ERR_TYPE         -4      // unrecognised type
+
+#define NSCTL_MAX_PKT_SZ       4096
+
+struct nsctl_packet {
+    uint16_t magic;
+    uint8_t type;
+    uint8_t argc;
+    char argv[NSCTL_MAX_PKT_SZ - 4];
+} __attribute__ ((packed));
+
+#define NSCTL_MAX_ARG_SZ       512
+
+struct nsctl_args {
+    uint8_t len;
+    char value[NSCTL_MAX_ARG_SZ - 1];
+} __attribute__ ((packed));
+
+/* parsed packet */
+struct nsctl {
+    uint8_t type;
+    uint8_t argc;
+    char *argv[0xff];
+};
+
+int pack_control(uint8_t *data, int len, uint8_t type, int argc, char *argv[]);
+int unpack_control(struct nsctl *packet, uint8_t *data, int len);
+void dump_control(struct nsctl *control, FILE *stream);
+
+#endif /* __CONTROL_H__ */
diff --git a/etc/ip_pool.default b/etc/ip_pool.default
new file mode 100644 (file)
index 0000000..9ae05e9
--- /dev/null
@@ -0,0 +1,2 @@
+10.10.10.0/24
+10.13.10.0/24
diff --git a/etc/l2tpns.logrotate b/etc/l2tpns.logrotate
new file mode 100644 (file)
index 0000000..d937c3a
--- /dev/null
@@ -0,0 +1,9 @@
+/var/log/l2tpns {
+       daily
+       missingok
+       rotate 14
+       compress
+       postrotate
+           /usr/bin/killall -HUP l2tpns
+       endscript
+}
diff --git a/etc/startup-config.default b/etc/startup-config.default
new file mode 100644 (file)
index 0000000..0c5051c
--- /dev/null
@@ -0,0 +1,117 @@
+# Debugging level
+set debug 3
+
+# Log file: comment out to use stderr, use "syslog:facility" for syslog
+set log_file "/var/log/l2tpns"
+
+# Write pid to this file
+set pid_file "/var/run/l2tpns.pid"
+
+# Shared secret with LAC
+set l2tp_secret "secret"
+
+# MTU of interface for L2TP traffic
+#set l2tp_mtu 1500
+
+# PPP counter and timer values
+#set ppp_restart_time 3
+#set ppp_max_configure 10
+#set ppp_max_failure 5
+
+# Only 2 DNS server entries are allowed
+set primary_dns 10.0.0.1
+set secondary_dns 10.0.0.2
+
+# Can have multiple radius server entries, but ony one radius secret
+set primary_radius 10.0.0.3
+#set primary_radius_port 1645
+#set secondary_radius 0.0.0.0
+#set secondary_radius_port 1645
+set radius_secret "secret"
+
+# Acceptable authentication types (pap, chap) in order of preference
+#set radius_authtypes "pap"
+
+# Turn on or off Radius Accounting
+#set radius_accounting no
+
+# Port for DAE RADIUS requests
+#set radius_dae_port 3799
+
+# Allow multiple logins for the same username
+#set allow_duplicate_users no
+
+# Write usage accounting files into specified directory
+set accounting_dir "/var/run/l2tpns/acct"
+
+# Listen address for L2TP
+#set bind_address 1.1.1.1
+
+# Send a gratiuitous ARP for bind address
+#set send_garp no
+
+# Gateway address given to clients
+#set peer_address 0.0.0.0
+
+# Default throttle rate in kb/s
+#set throttle_speed 0
+
+# Number of buckets to allocate for throttling
+#set throttle_buckets 3000
+
+# If set to anything other than 0, setuid when initialised.
+#set setuid 0
+
+# If set to true, dump current speed to stderr every second
+#set dump_speed no
+
+# Number of packets to read from tun/udp/cluster fd when select
+# returns readable
+#set multi_read_count 10
+
+# Set scheduling priority of process to SCHED_FIFO
+#set scheduler_fifo no
+
+# Lock pages into memory
+#set lock_pages no
+
+# Maximum number of host unreachable packets to send per second
+#set icmp_rate 0
+
+# Maximum number of downstream packets per 0.1s to handle for each
+# session (0 = ulimited)
+#set packet_limit 0
+
+# Cluster multicast address, interface
+#set cluster_address 239.192.13.13
+#set cluster_interface eth0
+
+# Cluster multicast TTL
+#set cluster_mcast_ttl 1
+
+# Cluster timers (1/10th second)
+#set cluster_hb_interval 5
+#set cluster_hb_timeout 150
+
+# Minimum number of slaves before master withdraws routes
+#set cluster_master_min_adv 1
+
+# Drop/kill sessions
+#load plugin "sessionctl"
+
+# Throttle/snoop based on RADIUS
+#load plugin "autothrottle"
+#load plugin "autosnoop"
+
+# Control throttle/snoop with nsctl
+#load plugin "throttlectl"
+#load plugin "snoopctl"
+
+# Punt RX speed if not supplied
+#load plugin "setrxspeed"
+
+# Remove domain from username
+#load plugin "stripdomain"
+
+# Walled garden
+#load plugin "garden"
diff --git a/etc/users.default b/etc/users.default
new file mode 100644 (file)
index 0000000..dd67351
--- /dev/null
@@ -0,0 +1 @@
+# List username:password combinations here for cli users
diff --git a/fake_epoll.h b/fake_epoll.h
new file mode 100644 (file)
index 0000000..d89f86a
--- /dev/null
@@ -0,0 +1,179 @@
+/* kludge up some limited epoll semantics using select for 2.4 kernels */
+/* $Id: fake_epoll.h,v 1.1 2005/06/04 15:42:35 bodea Exp $ */
+
+#ifndef __FAKE_EPOLL_H__
+#define __FAKE_EPOLL_H__
+
+#define EPOLLIN                0x01
+#define EPOLLOUT       0x04
+#define EPOLLERR       0x08
+#define EPOLLHUP       0x10
+
+#define EPOLL_CTL_ADD  1
+#define EPOLL_CTL_DEL  2
+#define EPOLL_CTL_MOD  3
+
+struct epoll_event {
+    uint32_t events;
+    union epoll_data {
+       void *ptr;
+       int fd;
+       uint32_t u32;
+       uint64_t u64;
+    } data;
+};
+
+int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
+
+#ifdef FAKE_EPOLL_IMPLEMENTATION
+
+#include <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__ */
diff --git a/garden.c b/garden.c
new file mode 100644 (file)
index 0000000..44b9597
--- /dev/null
+++ b/garden.c
@@ -0,0 +1,297 @@
+#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]);
+    }
+}
+
diff --git a/icmp.c b/icmp.c
new file mode 100644 (file)
index 0000000..f98e401
--- /dev/null
+++ b/icmp.c
@@ -0,0 +1,177 @@
+// L2TPNS: icmp
+
+char const *cvs_id_icmp = "$Id: icmp.c,v 1.10 2005/08/10 11:25:56 bodea Exp $";
+
+#include <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;
+}
diff --git a/l2tpns.c b/l2tpns.c
new file mode 100644 (file)
index 0000000..b4eca2e
--- /dev/null
+++ b/l2tpns.c
@@ -0,0 +1,5489 @@
+// L2TP Network Server
+// Adrian Kennard 2002
+// Copyright (c) 2003, 2004, 2005 Optus Internet Engineering
+// Copyright (c) 2002 FireBrick (Andrews & Arnold Ltd / Watchfront Ltd) - GPL licenced
+// vim: sw=8 ts=8
+
+char const *cvs_id_l2tpns = "$Id: l2tpns.c,v 1.161.2.1 2006/06/22 15:30:50 bodea Exp $";
+
+#include <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, &params)) == 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, &param);
+
+                       if (r == PLUGIN_RET_ERROR)
+                       {
+                               response.type = NSCTL_RES_ERR;
+                               response.argc = 1;
+                               response.argv[0] = param.additional
+                                       ? param.additional
+                                       : "error returned by plugin";
+                       }
+                       else if (r == PLUGIN_RET_NOTMASTER)
+                       {
+                               static char msg[] = "must be run on master: 000.000.000.000";
+
+                               response.type = NSCTL_RES_ERR;
+                               response.argc = 1;
+                               if (config->cluster_master_address)
+                               {
+                                       strcpy(msg + 23, fmtaddr(config->cluster_master_address, 0));
+                                       response.argv[0] = msg;
+                               }
+                               else
+                               {
+                                       response.argv[0] = "must be run on master: none elected";
+                               }
+                       }
+                       else if (!(param.response & NSCTL_RESPONSE))
+                       {
+                               response.type = NSCTL_RES_ERR;
+                               response.argc = 1;
+                               response.argv[0] = param.response
+                                       ? "unrecognised response value from plugin"
+                                       : "unhandled action";
+                       }
+                       else
+                       {
+                               response.type = param.response;
+                               response.argc = 0;
+                               if (param.additional)
+                               {
+                                       response.argc = 1;
+                                       response.argv[0] = param.additional;
+                               }
+                       }
+               }
+
+               break;
+
+       default:
+               response.type = NSCTL_RES_ERR;
+               response.argc = 1;
+               response.argv[0] = "error unpacking control packet";
+       }
+
+       buf = calloc(NSCTL_MAX_PKT_SZ, 1);
+       if (!buf)
+       {
+               LOG(2, 0, 0, "Failed to allocate nsctl response\n");
+               return;
+       }
+
+       r = pack_control(buf, NSCTL_MAX_PKT_SZ, response.type, response.argc, response.argv);
+       if (r > 0)
+       {
+               sendtofrom(controlfd, buf, r, 0, (const struct sockaddr *) addr, alen, local);
+               if (log_stream && config->debug >= 4)
+               {
+                       LOG(4, 0, 0, "Sent [%s] ", fmtaddr(addr->sin_addr.s_addr, 0));
+                       dump_control(&response, log_stream);
+               }
+       }
+       else
+               LOG(2, 0, 0, "Failed to pack nsctl response for %s (%d)\n",
+                       fmtaddr(addr->sin_addr.s_addr, 0), r);
+
+       free(buf);
+}
+
+static tunnelidt new_tunnel()
+{
+       tunnelidt i;
+       for (i = 1; i < MAXTUNNEL; i++)
+       {
+               if (tunnel[i].state == TUNNELFREE)
+               {
+                       LOG(4, 0, i, "Assigning tunnel ID %d\n", i);
+                       if (i > config->cluster_highest_tunnelid)
+                               config->cluster_highest_tunnelid = i;
+                       return i;
+               }
+       }
+       LOG(0, 0, 0, "Can't find a free tunnel! There shouldn't be this many in use!\n");
+       return 0;
+}
+
+//
+// We're becoming the master. Do any required setup..
+//
+// This is principally telling all the plugins that we're
+// now a master, and telling them about all the sessions
+// that are active too..
+//
+void become_master(void)
+{
+       int s, i;
+       static struct event_data d[RADIUS_FDS];
+       struct epoll_event e;
+
+       run_plugins(PLUGIN_BECOME_MASTER, NULL);
+
+       // running a bunch of iptables commands is slow and can cause
+       // the master to drop tunnels on takeover--kludge around the
+       // problem by forking for the moment (note: race)
+       if (!fork_and_close())
+       {
+               for (s = 1; s <= config->cluster_highest_sessionid ; ++s)
+               {
+                       if (!session[s].opened) // Not an in-use session.
+                               continue;
+
+                       run_plugins(PLUGIN_NEW_SESSION_MASTER, &session[s]);
+               }
+               exit(0);
+       }
+
+       // add radius fds
+       e.events = EPOLLIN;
+       for (i = 0; i < RADIUS_FDS; i++)
+       {
+               d[i].type = FD_TYPE_RADIUS;
+               d[i].index = i;
+               e.data.ptr = &d[i];
+
+               epoll_ctl(epollfd, EPOLL_CTL_ADD, radfds[i], &e);
+       }
+}
+
+int cmd_show_hist_idle(struct cli_def *cli, char *command, char **argv, int argc)
+{
+       int s, i;
+       int count = 0;
+       int buckets[64];
+
+       if (CLI_HELP_REQUESTED)
+               return CLI_HELP_NO_ARGS;
+
+       time(&time_now);
+       for (i = 0; i < 64;++i) buckets[i] = 0;
+
+       for (s = 1; s <= config->cluster_highest_sessionid ; ++s)
+       {
+               int idle;
+               if (!session[s].opened)
+                       continue;
+
+               idle = time_now - session[s].last_packet;
+               idle /= 5 ; // In multiples of 5 seconds.
+               if (idle < 0)
+                       idle = 0;
+               if (idle > 63)
+                       idle = 63;
+
+               ++count;
+               ++buckets[idle];
+       }
+
+       for (i = 0; i < 63; ++i)
+       {
+               cli_print(cli, "%3d seconds  : %7.2f%% (%6d)", i * 5, (double) buckets[i] * 100.0 / count , buckets[i]);
+       }
+       cli_print(cli, "lots of secs : %7.2f%% (%6d)", (double) buckets[63] * 100.0 / count , buckets[i]);
+       cli_print(cli, "%d total sessions open.", count);
+       return CLI_OK;
+}
+
+int cmd_show_hist_open(struct cli_def *cli, char *command, char **argv, int argc)
+{
+       int s, i;
+       int count = 0;
+       int buckets[64];
+
+       if (CLI_HELP_REQUESTED)
+               return CLI_HELP_NO_ARGS;
+
+       time(&time_now);
+       for (i = 0; i < 64;++i) buckets[i] = 0;
+
+       for (s = 1; s <= config->cluster_highest_sessionid ; ++s)
+       {
+               int open = 0, d;
+               if (!session[s].opened)
+                       continue;
+
+               d = time_now - session[s].opened;
+               if (d < 0)
+                       d = 0;
+               while (d > 1 && open < 32)
+               {
+                       ++open;
+                       d >>= 1; // half.
+               }
+               ++count;
+               ++buckets[open];
+       }
+
+       s = 1;
+       for (i = 0; i  < 30; ++i)
+       {
+               cli_print(cli, " < %8d seconds : %7.2f%% (%6d)", s, (double) buckets[i] * 100.0 / count , buckets[i]);
+               s <<= 1;
+       }
+       cli_print(cli, "%d total sessions open.", count);
+       return CLI_OK;
+}
+
+/* Unhide an avp.
+ *
+ * This unencodes the AVP using the L2TP secret and the previously
+ * stored random vector.  It overwrites the hidden data with the
+ * unhidden AVP subformat.
+ */
+static void unhide_value(uint8_t *value, size_t len, uint16_t type, uint8_t *vector, size_t vec_len)
+{
+       MD5_CTX ctx;
+       uint8_t digest[16];
+       uint8_t *last;
+       size_t d = 0;
+       uint16_t m = htons(type);
+
+       // Compute initial pad
+       MD5_Init(&ctx);
+       MD5_Update(&ctx, (unsigned char *) &m, 2);
+       MD5_Update(&ctx, config->l2tp_secret, strlen(config->l2tp_secret));
+       MD5_Update(&ctx, vector, vec_len);
+       MD5_Final(digest, &ctx);
+
+       // pointer to last decoded 16 octets
+       last = value;
+
+       while (len > 0)
+       {
+               // calculate a new pad based on the last decoded block
+               if (d >= sizeof(digest))
+               {
+                       MD5_Init(&ctx);
+                       MD5_Update(&ctx, config->l2tp_secret, strlen(config->l2tp_secret));
+                       MD5_Update(&ctx, last, sizeof(digest));
+                       MD5_Final(digest, &ctx);
+
+                       d = 0;
+                       last = value;
+               }
+
+               *value++ ^= digest[d++];
+               len--;
+       }
+}
+
+int find_filter(char const *name, size_t len)
+{
+       int free = -1;
+       int i;
+
+       for (i = 0; i < MAXFILTER; i++)
+       {
+               if (!*ip_filters[i].name)
+               {
+                       if (free < 0)
+                               free = i;
+
+                       continue;
+               }
+
+               if (strlen(ip_filters[i].name) != len)
+                       continue;
+
+               if (!strncmp(ip_filters[i].name, name, len))
+                       return i;
+       }
+                       
+       return free;
+}
+
+static int ip_filter_port(ip_filter_portt *p, uint16_t port)
+{
+       switch (p->op)
+       {
+       case FILTER_PORT_OP_EQ:    return port == p->port;
+       case FILTER_PORT_OP_NEQ:   return port != p->port;
+       case FILTER_PORT_OP_GT:    return port > p->port;
+       case FILTER_PORT_OP_LT:    return port < p->port;
+       case FILTER_PORT_OP_RANGE: return port >= p->port && port <= p->port2;
+       }
+
+       return 0;
+}
+
+static int ip_filter_flag(uint8_t op, uint8_t sflags, uint8_t cflags, uint8_t flags)
+{
+       switch (op)
+       {
+       case FILTER_FLAG_OP_ANY:
+               return (flags & sflags) || (~flags & cflags);
+
+       case FILTER_FLAG_OP_ALL:
+               return (flags & sflags) == sflags && (~flags & cflags) == cflags;
+
+       case FILTER_FLAG_OP_EST:
+               return (flags & (TCP_FLAG_ACK|TCP_FLAG_RST)) && (~flags & TCP_FLAG_SYN);
+       }
+
+       return 0;
+}
+
+int ip_filter(uint8_t *buf, int len, uint8_t filter)
+{
+       uint16_t frag_offset;
+       uint8_t proto;
+       in_addr_t src_ip;
+       in_addr_t dst_ip;
+       uint16_t src_port = 0;
+       uint16_t dst_port = 0;
+       uint8_t flags = 0;
+       ip_filter_rulet *rule;
+
+       if (len < 20) // up to end of destination address
+               return 0;
+
+       if ((*buf >> 4) != 4) // IPv4
+               return 0;
+
+       frag_offset = ntohs(*(uint16_t *) (buf + 6)) & 0x1fff;
+       proto = buf[9];
+       src_ip = *(in_addr_t *) (buf + 12);
+       dst_ip = *(in_addr_t *) (buf + 16);
+
+       if (frag_offset == 0 && (proto == IPPROTO_TCP || proto == IPPROTO_UDP))
+       {
+               int l = (buf[0] & 0xf) * 4; // length of IP header
+               if (len < l + 4) // ports
+                       return 0;
+
+               src_port = ntohs(*(uint16_t *) (buf + l));
+               dst_port = ntohs(*(uint16_t *) (buf + l + 2));
+               if (proto == IPPROTO_TCP)
+               {
+                       if (len < l + 14) // flags
+                               return 0;
+
+                       flags = buf[l + 13] & 0x3f;
+               }
+       }
+
+       for (rule = ip_filters[filter].rules; rule->action; rule++)
+       {
+               if (rule->proto != IPPROTO_IP && proto != rule->proto)
+                       continue;
+
+               if (rule->src_wild != INADDR_BROADCAST &&
+                   (src_ip & ~rule->src_wild) != (rule->src_ip & ~rule->src_wild))
+                       continue;
+
+               if (rule->dst_wild != INADDR_BROADCAST &&
+                   (dst_ip & ~rule->dst_wild) != (rule->dst_ip & ~rule->dst_wild))
+                       continue;
+
+               if (frag_offset)
+               {
+                       // layer 4 deny rules are skipped
+                       if (rule->action == FILTER_ACTION_DENY &&
+                           (rule->src_ports.op || rule->dst_ports.op || rule->tcp_flag_op))
+                               continue;
+               }
+               else
+               {
+                       if (rule->frag)
+                               continue;
+
+                       if (proto == IPPROTO_TCP || proto == IPPROTO_UDP)
+                       {
+                               if (rule->src_ports.op && !ip_filter_port(&rule->src_ports, src_port))
+                                       continue;
+
+                               if (rule->dst_ports.op && !ip_filter_port(&rule->dst_ports, dst_port))
+                                       continue;
+
+                               if (proto == IPPROTO_TCP && rule->tcp_flag_op &&
+                                   !ip_filter_flag(rule->tcp_flag_op, rule->tcp_sflags, rule->tcp_cflags, flags))
+                                       continue;
+                       }
+               }
+
+               // matched
+               rule->counter++;
+               return rule->action == FILTER_ACTION_PERMIT;
+       }
+
+       // default deny
+       return 0;
+}
diff --git a/l2tpns.h b/l2tpns.h
new file mode 100644 (file)
index 0000000..6c38bbf
--- /dev/null
+++ b/l2tpns.h
@@ -0,0 +1,859 @@
+// L2TPNS Global Stuff
+// $Id: l2tpns.h,v 1.113.2.3 2006/12/02 14:09:14 bodea Exp $
+
+#ifndef __L2TPNS_H__
+#define __L2TPNS_H__
+
+#include <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__ */
diff --git a/l2tpns.spec b/l2tpns.spec
new file mode 100644 (file)
index 0000000..4cd9ca8
--- /dev/null
@@ -0,0 +1,47 @@
+Summary: A high-speed clustered L2TP LNS
+Name: l2tpns
+Version: 2.1.21
+Release: 1
+License: GPL
+Group: System Environment/Daemons
+Source: http://optusnet.dl.sourceforge.net/sourceforge/l2tpns/l2tpns-%{version}.tar.gz
+URL: http://sourceforge.net/projects/l2tpns
+BuildRoot: %{_tmppath}/%{name}-%{version}-%(%__id -un)
+Prereq: /sbin/chkconfig
+BuildRequires: libcli >= 1.8.5
+Requires: libcli >= 1.8.5
+
+%description
+l2tpns is a layer 2 tunneling protocol network server (LNS).  It
+supports up to 65535 concurrent sessions per server/cluster plus ISP
+features such as rate limiting, walled garden, usage accounting, and
+more.
+
+%prep
+%setup -q
+
+%build
+make
+
+%install
+rm -rf %{buildroot}
+mkdir -p %{buildroot}
+make install DESTDIR=%{buildroot}
+
+%clean
+rm -rf %{buildroot}
+
+%files
+%defattr(-,root,root)
+%doc Changes INSTALL INTERNALS COPYING THANKS Docs/manual.html
+%dir /etc/l2tpns
+%config(noreplace) /etc/l2tpns/users
+%config(noreplace) /etc/l2tpns/startup-config
+%config(noreplace) /etc/l2tpns/ip_pool
+%attr(755,root,root) /usr/sbin/*
+%attr(755,root,root) /usr/lib/l2tpns
+%attr(644,root,root) /usr/share/man/man[58]/*
+
+%changelog
+* Fri Dec 1 2006 Brendan O'Dea <bod@optus.net> 2.1.21-1
+- 2.1.21 release, see /usr/share/doc/l2tpns-2.1.21/Changes
diff --git a/ll.c b/ll.c
new file mode 100644 (file)
index 0000000..f59d73a
--- /dev/null
+++ b/ll.c
@@ -0,0 +1,141 @@
+// L2TPNS Linked List Stuff
+
+char const *cvs_id_ll = "$Id: ll.c,v 1.6 2004/11/18 08:12:55 bodea Exp $";
+
+#include <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;
+}
+
diff --git a/ll.h b/ll.h
new file mode 100644 (file)
index 0000000..f4d2d88
--- /dev/null
+++ b/ll.h
@@ -0,0 +1,28 @@
+#ifndef __LL_H__
+#define __LL_H__
+
+typedef struct s_li
+{
+    void *data;
+    struct s_li *next;
+} li;
+
+typedef struct s_ll
+{
+    li *head;
+    li *end;
+    li *current;
+} linked_list;
+
+linked_list *ll_init();
+void ll_done(linked_list *l);
+li *ll_push(linked_list *l, void *data);
+void ll_delete(linked_list *l, void *data);
+void *ll_pop(linked_list *l);
+void ll_iterate(linked_list *l, int(*func)(void *));
+void ll_reset(linked_list *l);
+void *ll_next(linked_list *l);
+int ll_size(linked_list *l);
+int ll_contains(linked_list *l, void *search);
+
+#endif /* __LL_H__ */
diff --git a/md5.c b/md5.c
new file mode 100644 (file)
index 0000000..9abecfb
--- /dev/null
+++ b/md5.c
@@ -0,0 +1,274 @@
+/*
+ * This is an OpenSSL-compatible implementation of the RSA Data Security,
+ * Inc. MD5 Message-Digest Algorithm.
+ *
+ * Written by Solar Designer <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
diff --git a/md5.h b/md5.h
new file mode 100644 (file)
index 0000000..1ff5045
--- /dev/null
+++ b/md5.h
@@ -0,0 +1,28 @@
+/*
+ * This is an OpenSSL-compatible implementation of the RSA Data Security,
+ * Inc. MD5 Message-Digest Algorithm.
+ *
+ * Written by Solar Designer <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
diff --git a/nsctl.c b/nsctl.c
new file mode 100644 (file)
index 0000000..59ad0c6
--- /dev/null
+++ b/nsctl.c
@@ -0,0 +1,238 @@
+/* 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;
+}
diff --git a/plugin.h b/plugin.h
new file mode 100644 (file)
index 0000000..9a4b67e
--- /dev/null
+++ b/plugin.h
@@ -0,0 +1,129 @@
+#ifndef __PLUGIN_H__
+#define __PLUGIN_H__
+
+#define PLUGIN_API_VERSION     7
+#define MAX_PLUGIN_TYPES       30
+
+enum
+{
+    PLUGIN_PRE_AUTH = 1,
+    PLUGIN_POST_AUTH,
+    PLUGIN_PACKET_RX,
+    PLUGIN_PACKET_TX,
+    PLUGIN_TIMER,
+    PLUGIN_NEW_SESSION,
+    PLUGIN_KILL_SESSION,
+    PLUGIN_CONTROL,
+    PLUGIN_RADIUS_RESPONSE,
+    PLUGIN_RADIUS_RESET,
+    PLUGIN_RADIUS_ACCOUNT,
+    PLUGIN_BECOME_MASTER,
+    PLUGIN_NEW_SESSION_MASTER,
+};
+
+#define PLUGIN_RET_ERROR       0
+#define PLUGIN_RET_OK          1
+#define PLUGIN_RET_STOP                2 
+#define PLUGIN_RET_NOTMASTER   3
+
+struct pluginfuncs
+{
+    void (*log)(int level, sessionidt s, tunnelidt t, const char *format, ...);
+    void (*log_hex)(int level, const char *title, const uint8_t *data, int maxsize);
+    char *(*fmtaddr)(in_addr_t addr, int n);
+    sessionidt (*get_session_by_username)(char *username);
+    sessiont *(*get_session_by_id)(sessionidt s);
+    sessionidt (*get_id_by_session)(sessiont *s);
+    uint16_t (*radiusnew)(sessionidt s);
+    void (*radiussend)(uint16_t r, uint8_t state);
+    void *(*getconfig)(char *key, enum config_typet type);
+    void (*sessionshutdown)(sessionidt s, char const *reason, int result, int error, int term_cause);
+    void (*sessionkill)(sessionidt s, char *reason);
+    void (*throttle)(sessionidt s, int rate_in, int rate_out);
+    int (*session_changed)(int sid);
+};
+
+struct param_pre_auth
+{
+    tunnelt *t;
+    sessiont *s;
+    char *username;
+    char *password;
+    int protocol;
+    int continue_auth;
+};
+
+struct param_post_auth
+{
+    tunnelt *t;
+    sessiont *s;
+    char *username;
+    short auth_allowed;
+    int protocol;
+};
+
+struct param_packet_rx
+{
+    tunnelt *t;
+    sessiont *s;
+    char *buf;
+    int len;
+};
+
+struct param_packet_tx
+{
+    tunnelt *t;
+    sessiont *s;
+    char *buf;
+    int len;
+};
+
+struct param_timer
+{
+    time_t time_now;
+};
+
+struct param_control
+{
+    int iam_master;
+    int argc;
+    char **argv;
+    // output
+    int response;
+    char *additional;
+};
+
+struct param_new_session
+{
+    tunnelt *t;
+    sessiont *s;
+};
+
+struct param_kill_session
+{
+    tunnelt *t;
+    sessiont *s;
+};
+
+struct param_radius_response
+{
+    tunnelt *t;
+    sessiont *s;
+    char *key;
+    char *value;
+};
+
+struct param_radius_reset
+{
+    tunnelt *t;
+    sessiont *s;
+};
+
+struct param_radius_account
+{
+    tunnelt *t;
+    sessiont *s;
+    uint8_t **packet;
+};
+
+#endif /* __PLUGIN_H__ */
diff --git a/ppp.c b/ppp.c
new file mode 100644 (file)
index 0000000..d903496
--- /dev/null
+++ b/ppp.c
@@ -0,0 +1,2000 @@
+// L2TPNS PPP Stuff
+
+char const *cvs_id_ppp = "$Id: ppp.c,v 1.99.2.1 2006/05/26 07:33:52 bodea Exp $";
+
+#include <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);
+}
diff --git a/radius.c b/radius.c
new file mode 100644 (file)
index 0000000..8a39894
--- /dev/null
+++ b/radius.c
@@ -0,0 +1,1093 @@
+// L2TPNS Radius Stuff
+
+char const *cvs_id_radius = "$Id: radius.c,v 1.49.2.2 2006/08/02 14:17:20 bodea Exp $";
+
+#include <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));
+}
diff --git a/scripts/l2tpns-capture b/scripts/l2tpns-capture
new file mode 100644 (file)
index 0000000..6b31daf
--- /dev/null
@@ -0,0 +1,68 @@
+#! /usr/bin/perl -w
+
+#
+# Accept intercept data from l2tpns, write to a file in pcap format
+# (http://wiki.ethereal.com/Development/LibpcapFileFormat) suffixed
+# with timestamp.  Killing the process with SIGHUP causes a new file
+# to be opened.
+#
+
+use strict;
+use IO::File;
+use IO::Socket;
+use Time::HiRes 'gettimeofday';
+
+(my $cmd = $0) =~ s!.*/!!;
+
+die "Usage: $cmd PREFIX PORT\n" unless @ARGV == 2 and $ARGV[1] =~ /^\d+$/;
+
+my ($prefix, $port) = @ARGV;
+my $sock = IO::Socket::INET->new(
+    LocalPort  => $port,
+    Proto      => 'udp',
+    Type       => SOCK_DGRAM,
+) or die "$cmd: can't bind to port $port ($!)\n";
+
+my $restart = 0;
+$SIG{HUP} = sub { $restart++ };
+
+my $header = pack LSSlLLL =>
+    0xa1b2c3d4,        # magic no
+    2,         # version maj
+    4,         # version min
+    0,         # timezone offset (GMT)
+    0,         # timestamp accuracy
+    65536,     # snaplen
+    12;                # link type (RAW_IP)
+
+my $cap;
+my $buf;
+my $file;
+for (;;)
+{
+    unless ($cap)
+    {
+       $file = $prefix . time;
+       $cap = IO::File->new("> $file")
+           or die "$0: can't create capture file $file ($!)\n";
+
+       $cap->print($header)
+           or die "$0: error writing to $file ($!)\n";
+    }
+
+    while ($sock->recv($buf, 1600))
+    {
+       $cap->print(
+           # packet header: sec, usec, included size, original size
+           (pack LLLL => (gettimeofday), (length $buf) x 2),
+           $buf
+       ) or die "$0: error writing to $file ($!)\n";
+    }
+
+    if ($restart)
+    {
+       $restart = 0;
+       $cap->close;
+       undef $cap;
+    }
+}
diff --git a/scripts/l2tpns-monitor b/scripts/l2tpns-monitor
new file mode 100644 (file)
index 0000000..d17695e
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/sh
+stopfile=/tmp/l2tpns.stop
+first=`date +%s`
+min_first_time=3
+restart_delay=5
+prog=${0##*/}
+
+while :
+do
+       echo "`date`: Starting l2tpns $@"
+       start=`date +%s`
+       /usr/sbin/l2tpns ${1+"$@"}
+       RETVAL=$?
+       stop=`date +%s`
+       t=$(($stop - $start));
+       first=$(($stop - $first));
+       echo "`date`: l2tpns exited after $t seconds, status $RETVAL"
+       if [ $first -lt $min_first_time ]; then
+               echo "`date`: l2tpns exited immediately, $prog exiting"
+               exit $RETVAL
+       fi
+       if [ -f $stopfile ]; then
+               ls -l $stopfile
+               echo "`date`: stop file found, $prog exiting"
+               exit
+       fi
+       sleep $restart_delay
+done >>/var/log/$prog 2>&1     &       # execute in background
diff --git a/scripts/l2tpns.script b/scripts/l2tpns.script
new file mode 100644 (file)
index 0000000..ba4cdf4
--- /dev/null
@@ -0,0 +1,93 @@
+#!/bin/bash
+#
+# Startup script for l2tpns
+#
+# chkconfig: 2345 83 25
+# description: l2tpns.
+# processname: l2tpns
+# pidfile: /var/run/l2tpns.pid
+# config: /etc/l2tpns
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+if [ -f /etc/sysconfig/lt2pns ]; then
+        . /etc/sysconfig/lt2pns
+fi
+
+# Path to the l2tpns-monitor script, server binary, and short-form for messages.
+l2tpns_monitor=/usr/sbin/l2tpns-monitor
+l2tpns=/usr/sbin/l2tpns
+prog=${l2tpns##*/}
+RETVAL=0
+
+start() {
+        echo -n $"Starting $prog: "
+       rm -f /tmp/l2tpns.stop
+        daemon --check=$prog $l2tpns_monitor $OPTIONS
+        RETVAL=$?
+       echo
+       sleep 5
+       pid=`pidofproc $l2tpns_monitor`
+       if [ -z "$pid" ] || [ "$pid" -eq 0 ]; then
+               echo -n "Error starting $prog"
+               echo_failure
+               echo
+               return 99
+       fi
+        [ $RETVAL = 0 ] && touch /var/lock/subsys/l2tpns
+        return $RETVAL
+}
+stop() {
+       echo -n $"Stopping $prog: "
+       echo >/tmp/l2tpns.stop
+       killproc $l2tpns
+       RETVAL=$?
+       echo
+       [ $RETVAL = 0 ] && rm -f /var/lock/subsys/l2tpns /var/run/l2tpns.pid
+}
+reload() {
+       echo -n $"Reloading $prog: "
+       killproc $l2tpns -HUP
+       RETVAL=$?
+       echo
+}
+
+# See how we were called.
+case "$1" in
+  start)
+       start
+       ;;
+  stop)
+       stop
+       ;;
+  status)
+        status $l2tpns
+       RETVAL=$?
+       ;;
+  restart)
+       stop
+       sleep 5
+       start
+       ;;
+  condrestart)
+       if [ -f /var/run/l2tpns.pid ] ; then
+               stop
+               start
+       fi
+       ;;
+  reload)
+        reload
+       ;;
+  coldrestart)
+       stop
+       sleep 10
+       rm -f /tmp/l2tpns.dump
+       start
+       ;;
+  *)
+       echo $"Usage: $prog {start|stop|restart|condrestart|reload|status|coldrestart}"
+       exit 1
+esac
+
+exit $RETVAL
diff --git a/sessionctl.c b/sessionctl.c
new file mode 100644 (file)
index 0000000..5971d4c
--- /dev/null
@@ -0,0 +1,74 @@
+#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;
+}
diff --git a/setrxspeed.c b/setrxspeed.c
new file mode 100644 (file)
index 0000000..2e9e663
--- /dev/null
@@ -0,0 +1,41 @@
+#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;
+}
diff --git a/snoopctl.c b/snoopctl.c
new file mode 100644 (file)
index 0000000..6c79087
--- /dev/null
@@ -0,0 +1,122 @@
+#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;
+}
diff --git a/stripdomain.c b/stripdomain.c
new file mode 100644 (file)
index 0000000..748efc9
--- /dev/null
@@ -0,0 +1,31 @@
+#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;
+}
diff --git a/tbf.c b/tbf.c
new file mode 100644 (file)
index 0000000..223c40d
--- /dev/null
+++ b/tbf.c
@@ -0,0 +1,354 @@
+// L2TPNS: token bucket filters
+
+char const *cvs_id_tbf = "$Id: tbf.c,v 1.13 2005/07/31 10:04:10 bodea Exp $";
+
+#include <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;
+}
diff --git a/tbf.h b/tbf.h
new file mode 100644 (file)
index 0000000..925e4f7
--- /dev/null
+++ b/tbf.h
@@ -0,0 +1,45 @@
+#ifndef __TBF_H__
+#define __TBF_H__
+
+// Need a time interval.
+
+#define TBF_MAX_QUEUE   2       // Maximum of 2 queued packet per
+#define TBF_MAX_SIZE    3000    // Maxiumum queued packet size is 2048.
+
+#define TBF_MAX_CREDIT  6000    // Maximum 6000 bytes of credit.
+#define TBF_RATE        360     // 360 bytes per 1/10th of a second.
+
+typedef struct {
+       int             credit;
+       int             lasttime;
+       int             queued;
+       int             oldest;         // Position of packet in the ring buffer.
+       sessionidt      sid;            // associated session ID.
+       int             max_credit;     // Maximum amount of credit available (burst size).
+       int             rate;           // How many bytes of credit per second we get? (sustained rate)
+       void            (*send)(sessionidt s, uint8_t *, int); // Routine to actually send out the data.
+       int             prev;           // Timer chain position.
+       int             next;           // Timer chain position.
+
+       uint32_t        b_queued;       // Total bytes sent through this TBF
+       uint32_t        b_sent;         // Total bytes sucessfully made it to the network.
+       uint32_t        p_queued;       // ditto packets.
+       uint32_t        p_sent;         // ditto packets.
+       uint32_t        b_dropped;      // Total bytes dropped.
+       uint32_t        p_dropped;      // Total packets dropped.
+       uint32_t        p_delayed;      // Total packets not sent immediately.
+
+       int             sizes[TBF_MAX_QUEUE];
+       uint8_t         packets[TBF_MAX_QUEUE][TBF_MAX_SIZE];
+} tbft;
+
+void init_tbf(int num_tbfs);
+int tbf_run_timer(void);
+int tbf_queue_packet(int tbf_id, uint8_t * data, int size);
+int new_tbf(int sid, int max_credit, int rate, void (*f)(sessionidt, uint8_t *, int));
+int free_tbf(int tid);
+void fsck_tbfs(void);
+
+int cmd_show_tbf(struct cli_def *cli, char *command, char **argv, int argc);
+
+#endif /* __TBF_H__ */
diff --git a/test/README b/test/README
new file mode 100644 (file)
index 0000000..356aa58
--- /dev/null
@@ -0,0 +1,13 @@
+generateload, bounce
+
+    L2TP load test.  "generateload" simulates a LAC, each session
+    sends UDP packets to a specified IP address, which should be
+    running "bounce" to return the packets to LNS.
+
+radius
+
+    RADIUS authentication load test.
+
+ping-sweep
+
+    Send pings of varying sizes to a target host.
diff --git a/test/bounce.c b/test/bounce.c
new file mode 100644 (file)
index 0000000..5443b67
--- /dev/null
@@ -0,0 +1,96 @@
+#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);
+}
+
diff --git a/test/generateload.c b/test/generateload.c
new file mode 100644 (file)
index 0000000..0b32199
--- /dev/null
@@ -0,0 +1,1288 @@
+#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);
+}
+
+// }}}
+
diff --git a/test/ping-sweep b/test/ping-sweep
new file mode 100644 (file)
index 0000000..2e8b483
--- /dev/null
@@ -0,0 +1,121 @@
+#! /usr/bin/perl -w
+
+# Ping test: run through packet sizes (default: 56-3000)
+
+use strict;
+use Socket;
+
+use constant TRIES                     => 4;
+use constant TIMEOUT                   => 3; # 3s
+use constant MAXPACK                   => 16*1024;
+
+use constant ICMP_TYPE_ECHOREPLY       => 0; # ICMP packet types
+use constant ICMP_TYPE_ECHO            => 8;
+use constant ICMP_CODE                 => 0; # No ICMP code for ECHO and ECHOREPLY
+
+use constant SOL_IP                    => 0;
+use constant IP_MTU_DISCOVER           => 10;
+use constant IP_PMTUDISC_DONT          => 0;
+use constant IP_PMTUDISC_WANT          => 1;
+use constant IP_PMTUDISC_DO            => 2;
+
+my $verbose = shift if @ARGV and $ARGV[0] =~ /^--?v(erbose)?$/;
+my ($host, $min, $max) = @ARGV;
+
+die "Usage: $0 [-v] HOST [MIN [MAX]]\n" unless $host;
+my $addr = inet_aton $host or die "$0: invalid host $host\n";
+my $sin = sockaddr_in 0, $addr;
+
+$min =   56 if @ARGV < 2;
+$max = 3000 if @ARGV < 3;
+$max = $min if $min > $max;
+
+my $icmp = getprotobyname 'icmp' or die "$0: can't get ICMP proto ($!)\n";
+socket my $sock, PF_INET, SOCK_RAW, $icmp
+    or die "$0: can't create ICMP socket ($!)\n";
+
+setsockopt $sock, SOL_IP, IP_MTU_DISCOVER, IP_PMTUDISC_DONT
+    or die "$0: can't disable PMTU discovery ($!)\n";
+
+{
+    my $seq = 0;
+    sub icmp_out
+    {
+       my $len = shift;
+
+       # fill data with the *$len*$len*$len*...
+       my $d = sprintf '*%d', $len;
+       my $data = $d x (int ($len / length $d) + 1);
+
+       my $s = 0 + $seq++;
+       $seq %= 65536;
+
+       my $pack = pack "C C n n n a$len" =>
+           ICMP_TYPE_ECHO,     # icmp_type
+           ICMP_CODE,          # icmp_code
+           0,                  # icmp_cksum
+           $$,                 # icmp_id
+           $s,                 # icmp_seq
+           $data;              # payload
+
+       my $cksum = 0;
+       $cksum += $_ for unpack 'n*' => $pack . "\x00";
+       my $wrap;
+       $cksum = ($cksum & 0xffff) + $wrap while ($wrap = ($cksum >> 16));
+
+       substr $pack, 2, 2, pack n => ~$cksum;
+       ($s, $pack);
+    }
+}
+
+sub icmp_in
+{
+    my ($pack, $seq) = @_;
+    return unless length $pack >= 28;
+    my ($type, $code, $cksum, $id, $s) = unpack 'C C n n n' => substr $pack, 20;
+    return $type == ICMP_TYPE_ECHOREPLY
+       and $code == ICMP_CODE
+       and $id == $$
+       and $s == $seq;
+}
+
+$|++ if $verbose;
+
+for (my $size = $min; $size <= $max; $size++)
+{
+    my ($seq, $pack) = icmp_out $size;
+
+    print "$size: " if $verbose;
+    my $res = 0;
+
+    for (my $t = 0; $t < TRIES; $t++)
+    {
+       send $sock, $pack, 0, $sin
+           or die "$0: sendto failed ($!)\n";
+
+       my $rin = '';
+       (vec $rin, fileno $sock, 1) = 1;
+       select $rin, undef, undef, TIMEOUT or next;
+       
+       my $peer = recv $sock, my $buf, MAXPACK, 0
+           or die "$0: recvfrom failed ($!)\n";
+
+       next unless (sockaddr_in $peer)[1] eq $addr
+           and icmp_in $buf, $seq;
+
+       # OK
+       $res++;
+       last;
+    }
+
+    if ($verbose)
+    {
+       print +($res ? 'OK' : 'FAIL'), "\n";
+    }
+    else
+    {
+       print "$size\n" unless $res;
+    }
+}
+
+1;
diff --git a/test/radius.c b/test/radius.c
new file mode 100644 (file)
index 0000000..e31ce60
--- /dev/null
@@ -0,0 +1,723 @@
+/* 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);
+}
diff --git a/throttlectl.c b/throttlectl.c
new file mode 100644 (file)
index 0000000..0f0b055
--- /dev/null
@@ -0,0 +1,135 @@
+#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;
+}
diff --git a/util.c b/util.c
new file mode 100644 (file)
index 0000000..f3303b4
--- /dev/null
+++ b/util.c
@@ -0,0 +1,175 @@
+/* Misc util functions */
+
+char const *cvs_id_util = "$Id: util.c,v 1.14 2006/04/05 01:45:57 bodea Exp $";
+
+#include <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, &params))
+       {
+           LOG(0, 0, 0, "Error setting scheduler to OTHER after fork: %s\n", strerror(errno));
+           LOG(0, 0, 0, "This is probably really really bad.\n");
+       }
+    }
+
+    signal(SIGPIPE, SIG_DFL);
+    signal(SIGCHLD, SIG_DFL);
+    signal(SIGHUP,  SIG_DFL);
+    signal(SIGUSR1, SIG_DFL);
+    signal(SIGQUIT, SIG_DFL);
+    signal(SIGKILL, SIG_DFL);
+    signal(SIGTERM, SIG_DFL);
+
+    // Close sockets
+    if (clifd != -1)          close(clifd);
+    if (cluster_sockfd != -1) close(cluster_sockfd);
+    if (tunfd != -1)          close(tunfd);
+    if (udpfd != -1)          close(udpfd);
+    if (controlfd != -1)      close(controlfd);
+    if (daefd != -1)          close(daefd);
+    if (snoopfd != -1)        close(snoopfd);
+    if (ifrfd != -1)          close(ifrfd);
+    if (ifr6fd != -1)         close(ifr6fd);
+    if (rand_fd != -1)        close(rand_fd);
+    if (epollfd != -1)        close(epollfd);
+
+    for (i = 0; radfds && i < RADIUS_FDS; i++)
+       close(radfds[i]);
+
+#ifdef BGP
+    for (i = 0; i < BGP_NUM_PEERS; i++)
+       if (bgp_peers[i].sock != -1)
+           close(bgp_peers[i].sock);
+#endif /* BGP */
+
+    return pid;
+}
+
+ssize_t recvfromto(int s, void *buf, size_t len, int flags,
+    struct sockaddr *from, socklen_t *fromlen, struct in_addr *toaddr)
+{
+    ssize_t r;
+    struct msghdr msg;
+    struct cmsghdr *cmsg;
+    struct iovec vec;
+    char cbuf[128];
+
+    memset(&msg, 0, sizeof(msg));
+    msg.msg_name = from;
+    msg.msg_namelen = *fromlen;
+
+    vec.iov_base = buf;
+    vec.iov_len = len;
+    msg.msg_iov = &vec;
+    msg.msg_iovlen = 1;
+    msg.msg_flags = 0;
+
+    msg.msg_control = cbuf;
+    msg.msg_controllen = sizeof(cbuf);
+
+    if ((r = recvmsg(s, &msg, flags)) < 0)
+       return r;
+
+    if (fromlen)
+       *fromlen = msg.msg_namelen;
+
+    memset(toaddr, 0, sizeof(*toaddr));
+    for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg))
+    {
+       if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_PKTINFO)
+       {
+           struct in_pktinfo *i = (struct in_pktinfo *) CMSG_DATA(cmsg);
+           memcpy(toaddr, &i->ipi_addr, sizeof(*toaddr));
+           break;
+       }
+    }
+
+    return r;
+}
+
+ssize_t sendtofrom(int s, void const *buf, size_t len, int flags,
+    struct sockaddr const *to, socklen_t tolen, struct in_addr const *from)
+{
+    struct msghdr msg;
+    struct cmsghdr *cmsg;
+    struct iovec vec;
+    struct in_pktinfo pktinfo;
+    char cbuf[CMSG_SPACE(sizeof(pktinfo))];
+
+    memset(&msg, 0, sizeof(msg));
+    msg.msg_name = (struct sockaddr *) to;
+    msg.msg_namelen = tolen;
+
+    vec.iov_base = (void *) buf;
+    vec.iov_len = len;
+    msg.msg_iov = &vec;
+    msg.msg_iovlen = 1;
+    msg.msg_flags = 0;
+
+    msg.msg_control = cbuf;
+    msg.msg_controllen = sizeof(cbuf);
+
+    cmsg = CMSG_FIRSTHDR(&msg);
+    cmsg->cmsg_level = SOL_IP;
+    cmsg->cmsg_type = IP_PKTINFO;
+    cmsg->cmsg_len = CMSG_LEN(sizeof(pktinfo));
+
+    memset(&pktinfo, 0, sizeof(pktinfo));
+    memcpy(&pktinfo.ipi_spec_dst, from, sizeof(*from));
+    memcpy(CMSG_DATA(cmsg), &pktinfo, sizeof(pktinfo));
+
+    return sendmsg(s, &msg, flags);
+}
diff --git a/util.h b/util.h
new file mode 100644 (file)
index 0000000..ee066f6
--- /dev/null
+++ b/util.h
@@ -0,0 +1,13 @@
+#ifndef __UTIL_H__
+#define __UTIL_H__
+
+char *fmtaddr(in_addr_t addr, int n);
+void *shared_malloc(unsigned int size);
+pid_t fork_and_close(void);
+ssize_t sendtofrom(int s, void const *buf, size_t len, int flags,
+    struct sockaddr const *to, socklen_t tolen, struct in_addr const *from);
+
+ssize_t recvfromto(int s, void *buf, size_t len, int flags,
+    struct sockaddr *from, socklen_t *fromlen, struct in_addr *toaddr);
+
+#endif /* __UTIL_H__ */