Add a Cisco-Avpair with intercept details to RADIUS Start/Stop records
[l2tpns.git] / l2tpns.c
index a02be1d..a55854a 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.73 2004/12/17 00:28:00 bodea Exp $";
+char const *cvs_id_l2tpns = "$Id: l2tpns.c,v 1.73.2.6 2005/04/01 06:37:19 bodea Exp $";
 
 #include <arpa/inet.h>
 #include <assert.h>
@@ -117,6 +117,7 @@ config_descriptt config_values[] = {
        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, IP),
        CONFIG("cluster_interface", cluster_interface, STRING),
        CONFIG("cluster_hb_interval", cluster_hb_interval, INT),
@@ -147,7 +148,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.
@@ -478,7 +479,7 @@ sessionidt sessionbyip(in_addr_t ip)
        int s = lookup_ipmap(ip);
        CSTAT(call_sessionbyip);
 
-       if (s > 0 && s < MAXSESSION && session[s].tunnel)
+       if (s > 0 && s < MAXSESSION && session[s].opened)
                return (sessionidt) s;
 
        return 0;
@@ -578,8 +579,11 @@ sessionidt sessionbyuser(char *username)
        int s;
        CSTAT(call_sessionbyuser);
 
-       for (s = 1; s < MAXSESSION ; ++s)
+       for (s = 1; s <= config->cluster_highest_sessionid ; ++s)
        {
+               if (!session[s].opened)
+                       continue;
+
                if (session[s].walled_garden)
                        continue;               // Skip walled garden users.
 
@@ -621,17 +625,16 @@ void send_garp(in_addr_t ip)
        sendarp(ifr.ifr_ifindex, mac, ip);
 }
 
-// Find session by username, 0 for not found
 static sessiont *sessiontbysessionidt(sessionidt s)
 {
-       if (!s || s > MAXSESSION) return NULL;
+       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;
+       if (s < session || val >= MAXSESSION) return 0;
        return val;
 }
 
@@ -711,7 +714,7 @@ 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];
@@ -721,13 +724,13 @@ static void processipout(uint8_t * buf, int len)
        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 +768,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 +844,7 @@ 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..
 }
 
 //
@@ -852,7 +894,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)
@@ -972,7 +1014,7 @@ static void controladd(controlt * c, tunnelidt t, sessionidt s)
 //
 void throttle_session(sessionidt s, int rate_in, int rate_out)
 {
-       if (!session[s].tunnel)
+       if (!session[s].opened)
                return; // No-one home.
 
        if (!*session[s].user)
@@ -1010,7 +1052,7 @@ void throttle_session(sessionidt s, int rate_in, int rate_out)
 // add/remove filters from session (-1 = no change)
 void filter_session(sessionidt s, int filter_in, int filter_out)
 {
-       if (!session[s].tunnel)
+       if (!session[s].opened)
                return; // No-one home.
 
        if (!*session[s].user)
@@ -1053,9 +1095,9 @@ void sessionshutdown(sessionidt s, char *reason)
 
        CSTAT(call_sessionshutdown);
 
-       if (!session[s].tunnel)
+       if (!session[s].opened)
        {
-               LOG(3, s, session[s].tunnel, "Called sessionshutdown on a session with no tunnel.\n");
+               LOG(3, s, session[s].tunnel, "Called sessionshutdown on an unopened session.\n");
                return;                   // not a live session
        }
 
@@ -1066,7 +1108,7 @@ void sessionshutdown(sessionidt s, char *reason)
                run_plugins(PLUGIN_KILL_SESSION, &data);
        }
 
-       if (session[s].opened && !walled_garden && !session[s].die)
+       if (session[s].ip && !walled_garden && !session[s].die)
        {
                // RADIUS Stop message
                uint16_t r = session[s].radius;
@@ -1127,7 +1169,7 @@ void sessionshutdown(sessionidt s, char *reason)
        }
 
        if (!session[s].die)
-               session[s].die = now() + 150; // Clean up in 15 seconds
+               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--;
@@ -1178,12 +1220,21 @@ void sendipcp(tunnelidt t, sessionidt s)
 }
 
 // kill a session now
-static void sessionkill(sessionidt s, char *reason)
+void sessionkill(sessionidt s, char *reason)
 {
 
        CSTAT(call_sessionkill);
 
-       session[s].die = now();
+       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);  // close radius/routes, etc.
        if (session[s].radius)
                radiusclear(session[s].radius, s); // cant send clean accounting data, session is killed
@@ -1225,7 +1276,7 @@ static void tunnelkill(tunnelidt t, char *reason)
                controlfree = c;
        }
        // kill sessions
-       for (s = 1; s < MAXSESSION; s++)
+       for (s = 1; s <= config->cluster_highest_sessionid ; ++s)
                if (session[s].tunnel == t)
                        sessionkill(s, reason);
 
@@ -1252,12 +1303,12 @@ static void tunnelshutdown(tunnelidt t, char *reason)
        LOG(1, 0, t, "Shutting down tunnel %d (%s)\n", t, reason);
 
        // close session
-       for (s = 1; s < MAXSESSION; s++)
+       for (s = 1; s <= config->cluster_highest_sessionid ; ++s)
                if (session[s].tunnel == t)
                        sessionshutdown(s, reason);
 
        tunnel[t].state = TUNNELDIE;
-       tunnel[t].die = now() + 700; // Clean up in 70 seconds
+       tunnel[t].die = TIME + 700; // Clean up in 70 seconds
        cluster_send_tunnel(t);
        // TBA - should we wait for sessions to stop?
        {                            // Send StopCCN
@@ -1796,7 +1847,8 @@ void processudp(uint8_t * buf, int len, struct sockaddr_in *addr)
                                        if (!sessionfree)
                                        {
                                                STAT(session_overflow);
-                                               tunnelshutdown(t, "No free sessions");
+                                               LOG(1, 0, t, "No free sessions");
+                                               return;
                                        }
                                        else
                                        {
@@ -1820,7 +1872,7 @@ void processudp(uint8_t * buf, int len, struct sockaddr_in *addr)
 
                                                c = controlnew(11); // sending ICRP
                                                session[s].id = sessionid++;
-                                               session[s].opened = time(NULL);
+                                               session[s].opened = time_now;
                                                session[s].tunnel = t;
                                                session[s].far = asession;
                                                session[s].last_packet = time_now;
@@ -1903,7 +1955,7 @@ void processudp(uint8_t * buf, int len, struct sockaddr_in *addr)
                        l -= 2;
                }
 
-               if (s && !session[s].tunnel)    // Is something wrong??
+               if (s && !session[s].opened)    // Is something wrong??
                {
                        if (!config->cluster_iam_master)
                        {
@@ -1912,10 +1964,7 @@ void processudp(uint8_t * buf, int len, struct sockaddr_in *addr)
                                return;
                        }
 
-
-                       LOG(1, s, t, "UDP packet contains session %d but no session[%d].tunnel "
-                                    "exists (LAC said tunnel = %d).  Dropping packet.\n", s, s, t);
-
+                       LOG(1, s, t, "UDP packet contains session which is not opened.  Dropping packet.\n");
                        STAT(tunnel_rx_errors);
                        return;
                }
@@ -2083,7 +2132,7 @@ static int regular_cleanups(void)
                if (s > config->cluster_highest_sessionid)
                        s = 1;
 
-               if (!session[s].tunnel) // Session isn't in use
+               if (!session[s].opened) // Session isn't in use
                        continue;
 
                if (!session[s].die && session[s].ip && !(session[s].flags & SF_IPCP_ACKED))
@@ -2637,9 +2686,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);
        }
 
@@ -2691,7 +2740,7 @@ memset(ip_filters, 0, sizeof(ip_filtert) * MAXFILTER);
        memset(ip_address_pool, 0, sizeof(ippoolt) * MAXIPPOOL);
 
                // Put all the sessions on the free list marked as undefined.
-       for (i = 1; i < MAXSESSION - 1; i++)
+       for (i = 1; i < MAXSESSION; i++)
        {
                session[i].next = i + 1;
                session[i].tunnel = T_UNDEF;    // mark it as not filled in.
@@ -2700,7 +2749,7 @@ memset(ip_filters, 0, sizeof(ip_filtert) * MAXFILTER);
        sessionfree = 1;
 
                // Mark all the tunnels as undefined (waiting to be filled in by a download).
-       for (i = 1; i < MAXTUNNEL- 1; i++)
+       for (i = 1; i < MAXTUNNEL; i++)
                tunnel[i].state = TUNNELUNDEF;  // mark it as not filled in.
 
        if (!*hostname)
@@ -2826,7 +2875,7 @@ void rebuild_address_pool(void)
        for (i = 0; i < MAXSESSION; ++i)
        {
                int ipid;
-               if (!session[i].ip || !session[i].tunnel)
+               if (!(session[i].opened && session[i].ip))
                        continue;
                ipid = - lookup_ipmap(htonl(session[i].ip));
 
@@ -3128,7 +3177,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;
@@ -3699,7 +3748,7 @@ int sessionsetup(tunnelidt t, sessionidt s)
 
        LOG(3, s, t, "Doing session setup for session\n");
 
-       if (!session[s].ip || session[s].ip == 0xFFFFFFFE)
+       if (!session[s].ip)
        {
                assign_ip_address(s);
                if (!session[s].ip)
@@ -4297,7 +4346,7 @@ void become_master(void)
        {
                for (s = 1; s <= config->cluster_highest_sessionid ; ++s)
                {
-                       if (!session[s].tunnel) // Not an in-use session.
+                       if (!session[s].opened) // Not an in-use session.
                                continue;
 
                        run_plugins(PLUGIN_NEW_SESSION_MASTER, &session[s]);
@@ -4329,7 +4378,7 @@ int cmd_show_hist_idle(struct cli_def *cli, char *command, char **argv, int argc
        for (s = 1; s <= config->cluster_highest_sessionid ; ++s)
        {
                int idle;
-               if (!session[s].tunnel)
+               if (!session[s].opened)
                        continue;
 
                idle = time_now - session[s].last_packet;
@@ -4367,7 +4416,7 @@ int cmd_show_hist_open(struct cli_def *cli, char *command, char **argv, int argc
        for (s = 1; s <= config->cluster_highest_sessionid ; ++s)
        {
                int open = 0, d;
-               if (!session[s].tunnel)
+               if (!session[s].opened)
                        continue;
 
                d = time_now - session[s].opened;