finish incorporating ipv6 patches
[l2tpns.git] / l2tpns.c
index e0a44c0..3099b97 100644 (file)
--- a/l2tpns.c
+++ b/l2tpns.c
@@ -1,10 +1,10 @@
 // L2TP Network Server
 // Adrian Kennard 2002
-// Copyright (c) 2003, 2004 Optus Internet Engineering
+// 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.71 2004/12/16 08:54:16 bodea Exp $";
+char const *cvs_id_l2tpns = "$Id: l2tpns.c,v 1.80 2005/01/25 04:19:05 bodea Exp $";
 
 #include <arpa/inet.h>
 #include <assert.h>
@@ -19,6 +19,7 @@ char const *cvs_id_l2tpns = "$Id: l2tpns.c,v 1.71 2004/12/16 08:54:16 bodea Exp
 #include <sys/mman.h>
 #include <netdb.h>
 #include <netinet/in.h>
+#include <netinet/ip6.h>
 #include <stdarg.h>
 #include <stdlib.h>
 #include <stdio.h>
@@ -62,8 +63,11 @@ int clifd = -1;                      // Socket listening for CLI 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
+static int rand_fd = -1;       // Random data source
 time_t basetime = 0;           // base clock
 char hostname[1000] = "";      // us.
+static int tunidx;             // ifr_ifindex of tun device
 static uint32_t sessionid = 0; // session id for radius accounting
 static int syslog_log = 0;     // are we logging to syslog
 static FILE *log_stream = NULL;        // file handle for direct logging (i.e. direct into file, not via syslog).
@@ -74,6 +78,10 @@ struct cli_session_actions *cli_session_actions = NULL;      // Pending session chang
 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;
@@ -94,18 +102,20 @@ 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", l2tpsecret, STRING),
-       CONFIG("primary_dns", default_dns1, IP),
-       CONFIG("secondary_dns", default_dns2, IP),
+       CONFIG("primary_dns", default_dns1, IPv4),
+       CONFIG("secondary_dns", default_dns2, IPv4),
        CONFIG("save_state", save_state, BOOL),
-       CONFIG("primary_radius", radiusserver[0], IP),
-       CONFIG("secondary_radius", radiusserver[1], IP),
+       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_secret", radiussecret, STRING),
-       CONFIG("bind_address", bind_address, IP),
-       CONFIG("peer_address", peer_address, IP),
+       CONFIG("radius_authtypes", radius_authtypes_s, STRING),
+       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),
@@ -117,10 +127,12 @@ config_descriptt config_values[] = {
        CONFIG("scheduler_fifo", scheduler_fifo, BOOL),
        CONFIG("lock_pages", lock_pages, BOOL),
        CONFIG("icmp_rate", icmp_rate, INT),
-       CONFIG("cluster_address", cluster_address, IP),
+       CONFIG("packet_limit", max_packets, INT),
+       CONFIG("cluster_address", cluster_address, IPv4),
        CONFIG("cluster_interface", cluster_interface, STRING),
        CONFIG("cluster_hb_interval", cluster_hb_interval, INT),
        CONFIG("cluster_hb_timeout", cluster_hb_timeout, INT),
+       CONFIG("ipv6_prefix", ipv6_prefix, IPv6),
        { NULL, 0, 0, 0 },
 };
 
@@ -147,7 +159,7 @@ static sessionidt shut_acct_n = 0;
 
 tunnelt *tunnel = NULL;                        // Array of tunnel structures.
 sessiont *session = NULL;              // Array of session structures.
-sessioncountt *sess_count = NULL;      // Array of partial per-session traffic counters.
+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.
@@ -159,6 +171,7 @@ struct Tringbuffer *ringbuffer = NULL;
 
 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);
@@ -203,7 +216,6 @@ clockt backoff(uint8_t try)
 void _log(int level, sessionidt s, tunnelidt t, const char *format, ...)
 {
        static char message[65536] = {0};
-       static char message2[65536] = {0};
        va_list ap;
 
 #ifdef RINGBUFFER
@@ -227,18 +239,13 @@ void _log(int level, sessionidt s, tunnelidt t, const char *format, ...)
        if (config->debug < level) return;
 
        va_start(ap, format);
+       vsnprintf(message, sizeof(message), format, ap);
+
        if (log_stream)
-       {
-               vsnprintf(message2, 65535, format, ap);
-               snprintf(message, 65535, "%s %02d/%02d %s", time_now_string, t, s, message2);
-               fprintf(log_stream, "%s", message);
-       }
+               fprintf(log_stream, "%s %02d/%02d %s", time_now_string, t, s, message);
        else if (syslog_log)
-       {
-               vsnprintf(message2, 65535, format, ap);
-               snprintf(message, 65535, "%02d/%02d %s", t, s, message2);
-               syslog(level + 2, message); // We don't need LOG_EMERG or LOG_ALERT
-       }
+               syslog(level + 2, "%02d/%02d %s", t, s, message); // We don't need LOG_EMERG or LOG_ALERT
+
        va_end(ap);
 }
 
@@ -293,6 +300,72 @@ void _log_hex(int level, const char *title, const char *data, int maxsize)
        }
 }
 
+// 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));
+               }
+       }
+
+       // no source: seed prng
+       {
+               unsigned seed = time_now ^ getpid();
+               LOG(4, 0, 0, "Seeding the pseudo random generator: %u\n", seed);
+               srand(seed);
+       }
+}
+
+// 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(0);
+                       }
+
+                       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
 //
@@ -357,11 +430,61 @@ static void routeset(sessionidt s, in_addr_t ip, in_addr_t mask, in_addr_t gw, i
        }
 }
 
+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;
@@ -407,6 +530,42 @@ static void inittun(void)
                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] > 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 port
@@ -473,10 +632,36 @@ static int lookup_ipmap(in_addr_t ip)
        return (int) (intptr_t) d[(size_t) *a];
 }
 
+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(call_sessionbyip);
+       CSTAT(sessionbyip);
 
        if (s > 0 && s < MAXSESSION && session[s].tunnel)
                return (sessionidt) s;
@@ -484,6 +669,25 @@ sessionidt sessionbyip(in_addr_t ip)
        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] == ip.s6_addr16[2] == 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].tunnel)
+               return s;
+
+       return 0;
+}
+
 //
 // Take an IP address in HOST byte order and
 // add it to the sessionid by IP cache.
@@ -523,6 +727,42 @@ 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.
 //
@@ -576,7 +816,7 @@ int cmd_show_ipcache(struct cli_def *cli, char *command, char **argv, int argc)
 sessionidt sessionbyuser(char *username)
 {
        int s;
-       CSTAT(call_sessionbyuser);
+       CSTAT(sessionbyuser);
 
        for (s = 1; s < MAXSESSION ; ++s)
        {
@@ -640,7 +880,7 @@ void tunnelsend(uint8_t * buf, uint16_t l, tunnelidt t)
 {
        struct sockaddr_in addr;
 
-       CSTAT(call_tunnelsend);
+       CSTAT(tunnelsend);
 
        if (!t)
        {
@@ -711,23 +951,23 @@ static void processipout(uint8_t * buf, int len)
        tunnelidt t;
        in_addr_t ip;
 
-       char * data = buf;      // Keep a copy of the originals.
+       char *data = buf;       // Keep a copy of the originals.
        int size = len;
 
        uint8_t b[MAXETHER + 20];
 
-       CSTAT(call_processipout);
+       CSTAT(processipout);
 
        if (len < MIN_IP_SIZE)
        {
                LOG(1, 0, 0, "Short IP, %d bytes\n", len);
-               STAT(tunnel_tx_errors);
+               STAT(tun_rx_errors);
                return;
        }
        if (len >= MAXETHER)
        {
                LOG(1, 0, 0, "Oversize IP packet %d bytes\n", len);
-               STAT(tunnel_tx_errors);
+               STAT(tun_rx_errors);
                return;
        }
 
@@ -765,6 +1005,45 @@ static void processipout(uint8_t * buf, int len)
        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;
@@ -802,7 +1081,116 @@ static void processipout(uint8_t * buf, int len)
        sp->total_cout += len; // byte count
        sp->pout++;
        udp_tx += len;
-       sess_count[s].cout += len;      // To send to master..
+       sess_local[s].cout += len;      // To send to master..
+}
+
+// process outgoing (to tunnel) IPv6
+//
+void processipv6out(uint8_t * buf, int len)
+{
+       sessionidt s;
+       sessiont *sp;
+       tunnelidt t;
+       in_addr_t ip;
+       struct in6_addr ip6;
+
+       char *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, t, s, 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);
+
+       sp->cout += len; // byte count
+       sp->total_cout += len; // byte count
+       sp->pout++;
+       udp_tx += len;
+       sess_local[s].cout += len;      // To send to master..
 }
 
 //
@@ -852,7 +1240,7 @@ static void send_ipout(sessionidt s, uint8_t *buf, int len)
        sp->total_cout += len; // byte count
        sp->pout++;
        udp_tx += len;
-       sess_count[s].cout += len;      // To send to master..
+       sess_local[s].cout += len;      // To send to master..
 }
 
 // add an AVP (16 bit)
@@ -1051,7 +1439,7 @@ void sessionshutdown(sessionidt s, char *reason)
        int walled_garden = session[s].walled_garden;
 
 
-       CSTAT(call_sessionshutdown);
+       CSTAT(sessionshutdown);
 
        if (!session[s].tunnel)
        {
@@ -1079,9 +1467,7 @@ void sessionshutdown(sessionidt s, char *reason)
                        }
                        else
                        {
-                               int n;
-                               for (n = 0; n < 15; n++)
-                                       radius[r].auth[n] = rand();
+                               random_data(radius[r].auth, sizeof(radius[r].auth));
                        }
                }
 
@@ -1114,6 +1500,10 @@ void sessionshutdown(sessionidt s, char *reason)
                }
                else
                        free_ip_address(s);
+
+               // unroute IPv6, if setup
+               if (session[s].flags & SF_IPV6_ROUTED)
+                       route6set(s, session[s].ipv6route, session[s].ipv6prefixlen, 0);
        }
 
        if (session[s].throttle_in || session[s].throttle_out) // Unthrottle if throttled.
@@ -1142,7 +1532,7 @@ void sendipcp(tunnelidt t, sessionidt s)
        uint16_t r = session[s].radius;
        uint8_t *q;
 
-       CSTAT(call_sendipcp);
+       CSTAT(sendipcp);
 
        if (!r)
                r = radiusnew(s);
@@ -1175,13 +1565,37 @@ void sendipcp(tunnelidt t, sessionidt s)
 
        tunnelsend(buf, 10 + (q - buf), t); // send it
        session[s].flags &= ~SF_IPCP_ACKED;     // Clear flag.
+
+       // If we have an IPv6 prefix length configured, assume we should
+       // try to negotiate an IPv6 session as well. Unless we've had a
+       // (N)ACK for IPV6CP.
+       if (config->ipv6_prefix.s6_addr[0] > 0 && 
+                       !(session[s].flags & SF_IPV6CP_ACKED) &&
+                       !(session[s].flags & SF_IPV6_NACKED))
+       {
+               q = makeppp(buf,sizeof(buf), 0, 0, t, s, PPPIPV6CP);
+               if (!q) return;
+
+               *q = ConfigReq;
+               q[1] = r << RADIUS_SHIFT;               // ID, don't care, we
+                                                       // only send one type
+                                                       // of request
+               *(uint16_t *) (q + 2) = htons(14);
+               q[4] = 1;
+               q[5] = 10;
+               *(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
+       }
 }
 
 // kill a session now
 static void sessionkill(sessionidt s, char *reason)
 {
 
-       CSTAT(call_sessionkill);
+       CSTAT(sessionkill);
 
        session[s].die = now();
        sessionshutdown(s, reason);  // close radius/routes, etc.
@@ -1211,7 +1625,7 @@ static void tunnelkill(tunnelidt t, char *reason)
        sessionidt s;
        controlt *c;
 
-       CSTAT(call_tunnelkill);
+       CSTAT(tunnelkill);
 
        tunnel[t].state = TUNNELDIE;
 
@@ -1241,7 +1655,7 @@ static void tunnelshutdown(tunnelidt t, char *reason)
 {
        sessionidt s;
 
-       CSTAT(call_tunnelshutdown);
+       CSTAT(tunnelshutdown);
 
        if (!tunnel[t].last || !tunnel[t].far || tunnel[t].state == TUNNELFREE)
        {
@@ -1276,7 +1690,7 @@ void processudp(uint8_t * buf, int len, struct sockaddr_in *addr)
        uint8_t *p = buf + 2;
 
 
-       CSTAT(call_processudp);
+       CSTAT(processudp);
 
        udp_rx += len;
        udp_rx_pkt++;
@@ -1523,12 +1937,12 @@ void processudp(uint8_t * buf, int len, struct sockaddr_in *addr)
                                b += 2;
                                n -= 6;
 
-                               LOG(4, s, t, "   AVP %d (%s) len %d\n", mtype, avpnames[mtype], n);
+                               LOG(4, s, t, "   AVP %d (%s) len %d\n", mtype, avp_name(mtype), n);
                                switch (mtype)
                                {
                                case 0:     // message type
                                        message = ntohs(*(uint16_t *) b);
-                                       LOG(4, s, t, "   Message type = %d (%s)\n", *b, l2tp_message_types[message]);
+                                       LOG(4, s, t, "   Message type = %d (%s)\n", *b, l2tp_message_type(message));
                                        mandatorymessage = flags;
                                        break;
                                case 1:     // result code
@@ -1537,23 +1951,18 @@ void processudp(uint8_t * buf, int len, struct sockaddr_in *addr)
                                                const char* resdesc = "(unknown)";
                                                if (message == 4)
                                                { /* StopCCN */
-                                                       if (rescode <= MAX_STOPCCN_RESULT_CODE)
-                                                               resdesc = stopccn_result_codes[rescode];
+                                                       resdesc = stopccn_result_code(rescode);
                                                }
                                                else if (message == 14)
                                                { /* CDN */
-                                                       if (rescode <= MAX_CDN_RESULT_CODE)
-                                                               resdesc = cdn_result_codes[rescode];
+                                                       resdesc = cdn_result_code(rescode);
                                                }
 
                                                LOG(4, s, t, "   Result Code %d: %s\n", rescode, resdesc);
                                                if (n >= 4)
                                                {
                                                        uint16_t errcode = ntohs(*(uint16_t *)(b + 2));
-                                                       const char* errdesc = "(unknown)";
-                                                       if (errcode <= MAX_ERROR_CODE)
-                                                               errdesc = error_codes[errcode];
-                                                       LOG(4, s, t, "   Error Code %d: %s\n", errcode, errdesc);
+                                                       LOG(4, s, t, "   Error Code %d: %s\n", errcode, error_code(errcode));
                                                }
                                                if (n > 4)
                                                        LOG(4, s, t, "   Error String: %.*s\n", n-4, b+4);
@@ -1681,9 +2090,9 @@ void processudp(uint8_t * buf, int len, struct sockaddr_in *addr)
                                        }
                                case 29:    // Proxy Authentication Type
                                        {
-                                               uint16_t authtype = ntohs(*(uint16_t *)b);
-                                               LOG(4, s, t, "   Proxy Auth Type %d (%s)\n", authtype, authtypes[authtype]);
-                                               requestchap = (authtype == 2);
+                                               uint16_t atype = ntohs(*(uint16_t *)b);
+                                               LOG(4, s, t, "   Proxy Auth Type %d (%s)\n", atype, auth_type(atype));
+                                               requestchap = (atype == 2);
                                                break;
                                        }
                                case 30:    // Proxy Authentication Name
@@ -1827,12 +2236,9 @@ void processudp(uint8_t * buf, int len, struct sockaddr_in *addr)
                                                LOG(3, s, t, "New session (%d/%d)\n", tunnel[t].far, session[s].far);
                                                control16(c, 14, s, 1); // assigned session
                                                controladd(c, t, s); // send the reply
-                                               {
-                                                       // Generate a random challenge
-                                                       int n;
-                                                       for (n = 0; n < 15; n++)
-                                                               radius[r].auth[n] = rand();
-                                               }
+
+                                               // Generate a random challenge
+                                               random_data(radius[r].auth, sizeof(radius[r].auth));
                                                strncpy(radius[r].calling, calling, sizeof(radius[r].calling) - 1);
                                                strncpy(session[s].called, called, sizeof(session[s].called) - 1);
                                                strncpy(session[s].calling, calling, sizeof(session[s].calling) - 1);
@@ -1944,6 +2350,19 @@ void processudp(uint8_t * buf, int len, struct sockaddr_in *addr)
                        if (!config->cluster_iam_master) { master_forward_packet(buf, len, addr->sin_addr.s_addr, addr->sin_port); return; }
                        processipcp(t, s, p, l);
                }
+               else if (prot == PPPIPV6CP)
+               {
+                       if (config->ipv6_prefix.s6_addr[0] > 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(t, s, p, l);
+                       }
+                       else
+                       {
+                               LOG(1, s, t, "IPv6 not configured; ignoring IPv6CP\n");
+                       }
+               }
                else if (prot == PPPCCP)
                {
                        session[s].last_packet = time_now;
@@ -1967,6 +2386,28 @@ void processudp(uint8_t * buf, int len, struct sockaddr_in *addr)
 
                        processipin(t, s, p, l);
                }
+               else if (prot == PPPIPV6)
+               {
+                       if (!config->ipv6_prefix.s6_addr[0] > 0)
+                       {
+                               LOG(1, s, t, "IPv6 not configured; yet received IPv6 packet. Ignoring.\n");
+                               return;
+                       }
+                       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(t, s, p, l);
+               }
                else
                {
                        STAT(tunnel_rx_errors);
@@ -1982,7 +2423,7 @@ static void processtun(uint8_t * buf, int len)
        STAT(tun_rx_packets);
        INC_STAT(tun_rx_bytes, len);
 
-       CSTAT(call_processtun);
+       CSTAT(processtun);
 
        eth_rx_pkt++;
        eth_rx += len;
@@ -1993,8 +2434,12 @@ static void processtun(uint8_t * buf, int len)
                return;
        }
 
-       if (*(uint16_t *) (buf + 2) == htons(PKTIP)) // IP
+       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] > 0)
+               processipv6out(buf, len);
+
        // Else discard.
 }
 
@@ -2346,6 +2791,8 @@ static void mainloop(void)
                n = select(n + 1, &r, 0, 0, &to);
 #endif /* BGP */
 
+               STAT(select_called);
+
                TIME = now();
                if (n < 0)
                {
@@ -2464,9 +2911,16 @@ static void mainloop(void)
                                }
                        }
 
+                       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);
+                       }
                }
 
                        // Runs on every machine (master and slaves).
@@ -2616,6 +3070,7 @@ static void initdata(int optdebug, char *optconfig)
        config->debug = optdebug;
        config->num_tbfs = MAXTBFS;
        config->rl_rate = 28; // 28kbps
+       strcpy(config->random_device, RANDOMDEVICE);
 
        if (!(tunnel = shared_malloc(sizeof(tunnelt) * MAXTUNNEL)))
        {
@@ -2628,9 +3083,9 @@ static void initdata(int optdebug, char *optconfig)
                exit(1);
        }
 
-       if (!(sess_count = shared_malloc(sizeof(sessioncountt) * MAXSESSION)))
+       if (!(sess_local = shared_malloc(sizeof(sessionlocalt) * MAXSESSION)))
        {
-               LOG(0, 0, 0, "Error doing malloc for sessions_count: %s\n", strerror(errno));
+               LOG(0, 0, 0, "Error doing malloc for sess_local: %s\n", strerror(errno));
                exit(1);
        }
 
@@ -2646,12 +3101,12 @@ static void initdata(int optdebug, char *optconfig)
                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 (!(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);
 
 #ifdef RINGBUFFER
        if (!(ringbuffer = shared_malloc(sizeof(struct Tringbuffer))))
@@ -2721,7 +3176,7 @@ static int assign_ip_address(sessionidt s)
        char reuse = 0;
 
 
-       CSTAT(call_assign_ip_address);
+       CSTAT(assign_ip_address);
 
        for (i = 1; i < ip_pool_size; i++)
        {
@@ -2774,7 +3229,7 @@ static void free_ip_address(sessionidt s)
        int i = session[s].ip_pool_index;
 
 
-       CSTAT(call_free_ip_address);
+       CSTAT(free_ip_address);
 
        if (!session[s].ip)
                return; // what the?
@@ -3045,7 +3500,7 @@ static void dump_acct_info(int all)
        FILE *f = NULL;
 
 
-       CSTAT(call_dump_acct_info);
+       CSTAT(dump_acct_info);
 
        if (shut_acct_n)
        {
@@ -3119,7 +3574,7 @@ int main(int argc, char *argv[])
        init_tbf(config->num_tbfs);
 
        LOG(0, 0, 0, "L2TPNS version " VERSION "\n");
-       LOG(0, 0, 0, "Copyright (c) 2003, 2004 Optus Internet Engineering\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;
@@ -3524,6 +3979,7 @@ static int facility_value(char *name)
 static void update_config()
 {
        int i;
+       char *p;
        static int timeout = 0;
        static int interval = 0;
 
@@ -3535,6 +3991,7 @@ static void update_config()
                fclose(log_stream);
                log_stream = NULL;
        }
+
        if (*config->log_filename)
        {
                if (strstr(config->log_filename, "syslog:") == config->log_filename)
@@ -3566,7 +4023,6 @@ static void update_config()
                setbuf(log_stream, NULL);
        }
 
-
        // Update radius
        config->numradiusservers = 0;
        for (i = 0; i < MAXRADSERVER; i++)
@@ -3591,6 +4047,59 @@ static void update_config()
 
        config->num_radfds = 2 << RADIUS_SHIFT;
 
+       // parse radius_authtypes_s
+       config->radius_authtypes = config->radius_authprefer = 0;
+       p = config->radius_authtypes_s;
+       while (*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\"", p);
+
+               config->radius_authtypes |= type;
+               if (!config->radius_authprefer)
+                       config->radius_authprefer = type;
+       }
+
+       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");
+       }
+
+       // re-initialise the random number source
+       initrandom(config->random_device);
+
        // Update plugins
        for (i = 0; i < MAXPLUGINS; i++)
        {
@@ -3608,6 +4117,7 @@ static void update_config()
                        remove_plugin(config->old_plugins[i]);
                }
        }
+
        memcpy(config->old_plugins, config->plugins, sizeof(config->plugins));
        if (!config->cleanup_interval) config->cleanup_interval = 10;
        if (!config->multi_read_count) config->multi_read_count = 10;
@@ -3686,7 +4196,7 @@ int sessionsetup(tunnelidt t, sessionidt s)
        sessionidt i;
        int r;
 
-       CSTAT(call_sessionsetup);
+       CSTAT(sessionsetup);
 
        LOG(3, s, t, "Doing session setup for session\n");
 
@@ -3739,6 +4249,7 @@ int sessionsetup(tunnelidt t, sessionidt s)
                // 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);
@@ -3861,6 +4372,10 @@ int load_session(sessionidt s, sessiont *new)
                }
        }
 
+       // check v6 routing
+       if (new->flags & SF_IPV6_ROUTED && !(session[s].flags & SF_IPV6_ROUTED))
+                   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]))
        {