- Write pid file if filename is set
[l2tpns.git] / l2tpns.c
index 32fce1b..f27c7c2 100644 (file)
--- a/l2tpns.c
+++ b/l2tpns.c
@@ -4,7 +4,7 @@
 // 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.9 2004/06/28 02:43:13 fred_nerk Exp $";
+char const *cvs_id_l2tpns = "$Id: l2tpns.c,v 1.19 2004/08/02 03:38:01 fred_nerk Exp $";
 
 #include <arpa/inet.h>
 #include <assert.h>
@@ -22,7 +22,6 @@ char const *cvs_id_l2tpns = "$Id: l2tpns.c,v 1.9 2004/06/28 02:43:13 fred_nerk E
 #include <stdarg.h>
 #include <stdlib.h>
 #include <stdio.h>
-#define __USE_GNU
 #include <string.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
@@ -51,7 +50,7 @@ char const *cvs_id_l2tpns = "$Id: l2tpns.c,v 1.9 2004/06/28 02:43:13 fred_nerk E
 
 // Globals
 struct configt *config = NULL; // all configuration
-int tapfd = -1;                        // tap interface file handle
+int tunfd = -1;                        // tun interface file handle. (network device)
 int udpfd = -1;                        // UDP file handle
 int controlfd = -1;            // Control signal handle
 int snoopfd = -1;              // UDP file handle for sending out intercept data
@@ -59,24 +58,25 @@ int *radfds = NULL;         // RADIUS requests file handles
 int ifrfd = -1;                        // File descriptor for routing, etc
 time_t basetime = 0;           // base clock
 char hostname[1000] = "";      // us.
-u16 tapmac[3];                 // MAC of tap interface
-int tapidx;                    // ifr_ifindex of tap device
+int tunidx;                    // ifr_ifindex of tun device
 u32 sessionid = 0;             // session id for radius accounting
 int syslog_log = 0;            // are we logging to syslog
-FILE *log_stream = NULL;
-struct sockaddr_in snoop_addr = {0};
-extern int cluster_sockfd;
-u32 last_sid = 0;
-int clifd = 0;
-sessionidt *cli_session_kill = NULL;
-tunnelidt *cli_tunnel_kill = NULL;
-static void *ip_hash[256];
-u32 udp_tx = 0, udp_rx = 0, udp_rx_pkt = 0;
+FILE *log_stream = NULL;       // file handle for direct logging (i.e. direct into file, not via syslog).
+extern int cluster_sockfd;     // Intra-cluster communications socket.
+u32 last_sid = 0;              // Last used PPP SID. Can I kill this?? -- mo
+int clifd = 0;                 // Socket listening for CLI connections.
+
+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.
+
+u32 udp_tx = 0, udp_rx = 0, udp_rx_pkt = 0;    // Global traffic counters.
 u32 eth_tx = 0, eth_rx = 0, eth_rx_pkt = 0;
-u32 ip_pool_size = 1;
-time_t time_now = 0;
-char time_now_string[64] = {0};
-char main_quit = 0;
+u32 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.
+char time_now_string[64] = {0};        // Current time as a string.
+char main_quit = 0;            // True if we're in the process of exiting.
 char *_program_name = NULL;
 linked_list *loaded_plugins;
 linked_list *plugins[MAX_PLUGIN_TYPES];
@@ -94,12 +94,15 @@ int bgp_configured = 0;
 struct config_descriptt config_values[] = {
        CONFIG("debug", debug, INT),
        CONFIG("log_file", log_filename, STRING),
+       CONFIG("pid_file", pid_file, STRING),
        CONFIG("l2tp_secret", l2tpsecret, STRING),
        CONFIG("primary_dns", default_dns1, IP),
        CONFIG("secondary_dns", default_dns2, IP),
        CONFIG("save_state", save_state, BOOL),
        CONFIG("primary_radius", radiusserver[0], IP),
        CONFIG("secondary_radius", radiusserver[1], IP),
+       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),
@@ -111,6 +114,7 @@ struct config_descriptt config_values[] = {
        CONFIG("cleanup_interval", cleanup_interval, INT),
        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("cluster_address", cluster_address, IP),
        CONFIG("cluster_interface", cluster_interface, STRING),
@@ -143,11 +147,11 @@ char *plugin_functions[] = {
 
 #define max_plugin_functions (sizeof(plugin_functions) / sizeof(char *))
 
-tunnelt *tunnel = NULL;                // 1000 * 45 = 45000 = 45k
-sessiont *session = NULL;      // 5000 * 213 = 1065000 = 1 Mb
-sessioncountt *sess_count = NULL;
-radiust *radius = NULL;
-ippoolt *ip_address_pool = NULL;
+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.
+radiust *radius = NULL;                        // Array of radius structures.
+ippoolt *ip_address_pool = NULL;       // Array of dynamic IP addresses.
 controlt *controlfree = 0;
 struct Tstats *_statistics = NULL;
 #ifdef RINGBUFFER
@@ -169,7 +173,7 @@ void update_config();
 static void cache_ipmap(ipt ip, int s);
 static void uncache_ipmap(ipt ip);
 
-// return internal time (10ths since run)
+// return internal time (10ths since process startup)
 clockt now(void)
 {
        struct timeval t;
@@ -178,12 +182,18 @@ clockt now(void)
 }
 
 // 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(u8 try)
 {
        if (try > 5) try = 5;                  // max backoff
        return now() + 10 * (1 << try);
 }
 
+
+//
+// Log a debug message.
+//
 void _log(int level, ipt address, sessionidt s, tunnelidt t, const char *format, ...)
 {
        static char message[65536] = {0};
@@ -297,7 +307,7 @@ void routeset(sessionidt s, ipt ip, ipt mask, ipt gw, u8 add)
        ip &= mask;             // Force the ip to be the first one in the route.
 
        memset(&r, 0, sizeof(r));
-       r.rt_dev = config->tapdevice;
+       r.rt_dev = config->tundevice;
        r.rt_dst.sa_family = AF_INET;
        *(u32 *) & (((struct sockaddr_in *) &r.rt_dst)->sin_addr.s_addr) = htonl(ip);
        r.rt_gateway.sa_family = AF_INET;
@@ -344,31 +354,32 @@ void routeset(sessionidt s, ipt ip, ipt mask, ipt gw, u8 add)
        }
 }
 
-// Set up TAP interface
-void inittap(void)
+//
+// Set up TUN interface
+void inittun(void)
 {
        struct ifreq ifr;
        struct sockaddr_in sin = {0};
        memset(&ifr, 0, sizeof(ifr));
        ifr.ifr_flags = IFF_TUN;
 
-       tapfd = open(TAPDEVICE, O_RDWR);
-       if (tapfd < 0)
+       tunfd = open(TUNDEVICE, O_RDWR);
+       if (tunfd < 0)
        {                          // fatal
-               log(0, 0, 0, 0, "Can't open %s: %s\n", TAPDEVICE, strerror(errno));
+               log(0, 0, 0, 0, "Can't open %s: %s\n", TUNDEVICE, strerror(errno));
                exit(1);
        }
        {
-               int flags = fcntl(tapfd, F_GETFL, 0);
-               fcntl(tapfd, F_SETFL, flags | O_NONBLOCK);
+               int flags = fcntl(tunfd, F_GETFL, 0);
+               fcntl(tunfd, F_SETFL, flags | O_NONBLOCK);
        }
-       if (ioctl(tapfd, TUNSETIFF, (void *) &ifr) < 0)
+       if (ioctl(tunfd, TUNSETIFF, (void *) &ifr) < 0)
        {
-               log(0, 0, 0, 0, "Can't set tap interface: %s\n", strerror(errno));
+               log(0, 0, 0, 0, "Can't set tun interface: %s\n", strerror(errno));
                exit(1);
        }
-       assert(strlen(ifr.ifr_name) < sizeof(config->tapdevice));
-       strncpy(config->tapdevice, ifr.ifr_name, sizeof(config->tapdevice) - 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;
@@ -393,18 +404,12 @@ void inittap(void)
                log(0, 0, 0, 0, "Error setting tun flags: %s\n", strerror(errno));
                exit(1);
        }
-       if (ioctl(ifrfd, SIOCGIFHWADDR, (void *) &ifr) < 0)
-       {
-               log(0, 0, 0, 0, "Error setting tun hardware address: %s\n", strerror(errno));
-               exit(1);
-       }
-       memcpy(&tapmac, 2 + (u8 *) & ifr.ifr_hwaddr, 6);
        if (ioctl(ifrfd, SIOCGIFINDEX, (void *) &ifr) < 0)
        {
                log(0, 0, 0, 0, "Error setting tun ifindex: %s\n", strerror(errno));
                exit(1);
        }
-       tapidx = ifr.ifr_ifindex;
+       tunidx = ifr.ifr_ifindex;
 }
 
 // set up UDP port
@@ -418,7 +423,7 @@ void initudp(void)
        addr.sin_family = AF_INET;
        addr.sin_port = htons(L2TPPORT);
        addr.sin_addr.s_addr = config->bind_address;
-       udpfd = socket(AF_INET, SOCK_DGRAM, UDP);
+       udpfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        setsockopt(udpfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
        {
                int flags = fcntl(udpfd, F_GETFL, 0);
@@ -429,8 +434,7 @@ void initudp(void)
                log(0, 0, 0, 0, "Error in UDP bind: %s\n", strerror(errno));
                exit(1);
        }
-       snoopfd = socket(AF_INET, SOCK_DGRAM, UDP);
-       snoop_addr.sin_family = AF_INET;
+       snoopfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
 
        // Control
        memset(&addr, 0, sizeof(addr));
@@ -625,82 +629,6 @@ sessionidt sessionidtbysessiont(sessiont *s)
        return val;
 }
 
-// Handle ARP requests
-void processarp(u8 * buf, int len)
-{
-       ipt ip;
-       sessionidt s;
-
-       CSTAT(call_processarp);
-       STAT(arp_recv);
-
-       if (len != 46)
-       {
-               log(0, 0, 0, 0, "Unexpected length ARP %d bytes\n", len);
-               STAT(arp_errors);
-               return;
-       }
-       if (*(u16 *) (buf + 16) != htons(PKTARP))
-       {
-               log(0, 0, 0, 0, "Unexpected ARP type %04X\n", ntohs(*(u16 *) (buf + 16)));
-               STAT(arp_errors);
-               return;
-       }
-       if (*(u16 *) (buf + 18) != htons(0x0001))
-       {
-               log(0, 0, 0, 0, "Unexpected ARP hard type %04X\n", ntohs(*(u16 *) (buf + 18)));
-               STAT(arp_errors);
-               return;
-       }
-       if (*(u16 *) (buf + 20) != htons(PKTIP))
-       {
-               log(0, 0, 0, 0, "Unexpected ARP prot type %04X\n", ntohs(*(u16 *) (buf + 20)));
-               STAT(arp_errors);
-               return;
-       }
-       if (buf[22] != 6)
-       {
-               log(0, 0, 0, 0, "Unexpected ARP hard len %d\n", buf[22]);
-               STAT(arp_errors);
-               return;
-       }
-       if (buf[23] != 4)
-       {
-               log(0, 0, 0, 0, "Unexpected ARP prot len %d\n", buf[23]);
-               STAT(arp_errors);
-               return;
-       }
-       if (*(u16 *) (buf + 24) != htons(0x0001))
-       {
-               log(0, 0, 0, 0, "Unexpected ARP op %04X\n", ntohs(*(u16 *) (buf + 24)));
-               STAT(arp_errors);
-               return;
-       }
-       ip = *(u32 *) (buf + 42);
-       // look up session
-       s = sessionbyip(ip);
-       if (s)
-       {
-               log(3, ntohl(ip), s, session[s].tunnel, "ARP reply for %s\n", inet_toa(ip));
-               memcpy(buf + 4, buf + 10, 6); // set destination as source
-               *(u16 *) (buf + 10) = htons(tapmac[0]); // set source address
-               *(u16 *) (buf + 12) = htons(tapmac[1]);
-               *(u16 *) (buf + 14) = htons(tapmac[2]);
-               *(u16 *) (buf + 24) = htons(0x0002); // ARP reply
-               memcpy(buf + 26, buf + 10, 6); // sender ethernet
-               memcpy(buf + 36, buf + 4, 6); // target ethernet
-               *(u32 *) (buf + 42) = *(u32 *) (buf + 32); // target IP
-               *(u32 *) (buf + 32) = ip; // sender IP
-               write(tapfd, buf, len);
-               STAT(arp_replies);
-       }
-       else
-       {
-               log(3, ntohl(ip), 0, 0, "ARP request for unknown IP %s\n", inet_toa(ip));
-               STAT(arp_discarded);
-       }
-}
-
 // actually send a control message for a specific tunnel
 void tunnelsend(u8 * buf, u16 l, tunnelidt t)
 {
@@ -764,7 +692,7 @@ void tunnelsend(u8 * buf, u16 l, tunnelidt t)
 //
 int tun_write(u8 * data, int size)
 {
-       return write(tapfd, data, size);
+       return write(tunfd, data, size);
 }
 
 // process outgoing (to tunnel) IP
@@ -896,12 +824,12 @@ void send_ipout(sessionidt s, u8 *buf, int len)
        t = session[s].tunnel;
        sp = &session[s];
 
-       // Snooping this session, send it to intercept box
+       log(5, session[s].ip, s, t, "Ethernet -> Tunnel (%d bytes)\n", len);
+
+       // Snooping this session.
        if (sp->snoop_ip && sp->snoop_port)
                snoop_send_packet(buf, len, sp->snoop_ip, sp->snoop_port);
 
-       log(5, session[s].ip, s, t, "Ethernet -> Tunnel (%d bytes)\n", len);
-
        // Add on L2TP header
        {
                u8 *p = makeppp(b, sizeof(b),  buf, len, t, s, PPPIP);
@@ -1091,7 +1019,7 @@ void sessionshutdown(sessionidt s, char *reason)
                return;                   // not a live session
        }
 
-       if (!session[s].die)
+       if (!dead)
                log(2, 0, s, session[s].tunnel, "Shutting down session %d: %s\n", s, reason);
 
        session[s].die = now() + 150; // Clean up in 15 seconds
@@ -1161,14 +1089,17 @@ void sendipcp(tunnelidt t, sessionidt s)
 
        if (!r)
                r = radiusnew(s);
+
        if (radius[r].state != RADIUSIPCP)
        {
                radius[r].state = RADIUSIPCP;
                radius[r].try = 0;
        }
+
        radius[r].retry = backoff(radius[r].try++);
        if (radius[r].try > 10)
        {
+               radiusclear(r, s);      // Clear radius session.
                sessionshutdown(s, "No reply on IPCP");
                return;
        }
@@ -1184,7 +1115,7 @@ void sendipcp(tunnelidt t, sessionidt s)
        *(u16 *) (q + 2) = htons(10);
        q[4] = 3;
        q[5] = 6;
-       *(u32 *) (q + 6) = config->bind_address; // send my IP
+       *(u32 *) (q + 6) = config->bind_address ? config->bind_address : my_address; // send my IP
        tunnelsend(buf, 10 + (q - buf), t); // send it
        session[s].flags &= ~SF_IPCP_ACKED;     // Clear flag.
 }
@@ -1195,9 +1126,11 @@ void sessionkill(sessionidt s, char *reason)
 
        CSTAT(call_sessionkill);
 
+       session[s].die = now();
        sessionshutdown(s, reason);  // close radius/routes, etc.
        if (session[s].radius)
-               radiusclear(session[s].radius, 0); // cant send clean accounting data, session is killed
+               radiusclear(session[s].radius, s); // cant send clean accounting data, session is killed
+
        log(2, 0, s, session[s].tunnel, "Kill session %d (%s): %s\n", s, session[s].user, reason);
 
        throttle_session(s, 0);         // Force session to be un-throttle. Free'ing TBF structures.
@@ -1206,6 +1139,7 @@ void sessionkill(sessionidt s, char *reason)
        session[s].tunnel = T_FREE;     // Mark it as free.
        session[s].next = sessionfree;
        sessionfree = s;
+       cli_session_actions[s].action = 0;
        cluster_send_session(s);
 }
 
@@ -1235,10 +1169,9 @@ void tunnelkill(tunnelidt t, char *reason)
 
        // free tunnel
        tunnelclear(t);
-       cluster_send_tunnel(t);
        log(1, 0, 0, t, "Kill tunnel %d: %s\n", t, reason);
-       tunnel[t].die = 0;
-       tunnel[t].state = TUNNELFREE;
+       cli_tunnel_actions[s].action = 0;
+       cluster_send_tunnel(t);
 }
 
 // shut down a tunnel cleanly
@@ -1376,9 +1309,9 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr)
                                // Is this a duplicate of the first packet? (SCCRQ)
                                //
                        for ( i = 1; i <= config->cluster_highest_tunnelid ; ++i) {
-                               if (tunnel[t].state != TUNNELOPENING ||
-                                       tunnel[t].ip != ntohl(*(ipt *) & addr->sin_addr) ||
-                                       tunnel[t].port != ntohs(addr->sin_port) )
+                               if (tunnel[i].state != TUNNELOPENING ||
+                                       tunnel[i].ip != ntohl(*(ipt *) & addr->sin_addr) ||
+                                       tunnel[i].port != ntohs(addr->sin_port) )
                                        continue;
                                t = i;
                                break;
@@ -1542,13 +1475,10 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr)
                                                        log(4, ntohl(addr->sin_addr.s_addr), s, t, "   Error Code %d: %s\n",
                                                                errcode, errdesc);
                                                }
-                                               if (n > 4) {
-                                                       /* %*s doesn't work?? */
-                                                       char *buf = (char *)strndup(b+4, n-4);
-                                                       log(4, ntohl(addr->sin_addr.s_addr), s, t, "   Error String: %s\n",
-                                                               buf);
-                                                       free(buf);
-                                               }
+                                               if (n > 4)
+                                                       log(4, ntohl(addr->sin_addr.s_addr), s, t, "   Error String: %.*s\n",
+                                                               n-4, b+4);
+
                                                break;
                                        }
                                        break;
@@ -1585,8 +1515,8 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr)
                                        // TBA - to send to RADIUS
                                        break;
                                case 8:     // vendor name
-                                       memset(tunnel[t].vendor, 0, 128);
-                                       memcpy(tunnel[t].vendor, b, (n >= 127) ? 127 : n);
+                                       memset(tunnel[t].vendor, 0, sizeof(tunnel[t].vendor));
+                                       memcpy(tunnel[t].vendor, b, (n >= sizeof(tunnel[t].vendor) - 1) ? sizeof(tunnel[t].vendor) - 1 : n);
                                        log(4, ntohl(addr->sin_addr.s_addr), s, t, "   Vendor name = \"%s\"\n", tunnel[t].vendor);
                                        break;
                                case 9:     // assigned tunnel
@@ -1630,6 +1560,8 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr)
                                        memcpy(calling, b, (n >= MAXTEL) ? (MAXTEL-1) : n);
                                        log(4, ntohl(addr->sin_addr.s_addr), s, t, "   Calling <%s>\n", calling);
                                        break;
+                               case 23:    // subtype
+                                       break;
                                case 24:    // tx connect speed
                                        if (n == 4)
                                        {
@@ -1819,7 +1751,7 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr)
                                                if (!(r = radiusnew(s)))
                                                {
                                                        log(1, ntohl(addr->sin_addr.s_addr), s, t, "No free RADIUS sessions for ICRQ\n");
-//                                                     sessionkill(s, "no free RADIUS sesions");
+                                                       sessionkill(s, "no free RADIUS sesions");
                                                        return;
                                                }
 
@@ -1993,26 +1925,25 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr)
        }
 }
 
-// read and process packet on tap
-void processtap(u8 * buf, int len)
+// read and process packet on tun
+void processtun(u8 * buf, int len)
 {
-       log_hex(5, "Receive TAP Data", buf, len);
-       STAT(tap_rx_packets);
-       INC_STAT(tap_rx_bytes, len);
+       log_hex(5, "Receive TUN Data", buf, len);
+       STAT(tun_rx_packets);
+       INC_STAT(tun_rx_bytes, len);
 
-       CSTAT(call_processtap);
+       CSTAT(call_processtun);
 
        eth_rx_pkt++;
        eth_rx += len;
        if (len < 22)
        {
-               log(1, 0, 0, 0, "Short tap packet %d bytes\n", len);
-               STAT(tap_rx_errors);
+               log(1, 0, 0, 0, "Short tun packet %d bytes\n", len);
+               STAT(tun_rx_errors);
                return;
        }
-       if (*(u16 *) (buf + 2) == htons(PKTARP)) // ARP
-               processarp(buf, len);
-       else if (*(u16 *) (buf + 2) == htons(PKTIP)) // IP
+
+       if (*(u16 *) (buf + 2) == htons(PKTIP)) // IP
                processipout(buf, len);
        // Else discard.
 }
@@ -2030,6 +1961,7 @@ int regular_cleanups(void)
        int count=0,i;
        u16 r;
        static clockt next_acct = 0;
+       int a;
 
        log(3, 0, 0, 0, "Begin regular cleanup\n");
 
@@ -2044,7 +1976,7 @@ int regular_cleanups(void)
                } else
                        radius[r].retry = backoff(radius[r].try+1);     // Is this really needed? --mo
        }
-       for (t = 1; t < config->cluster_highest_tunnelid; t++)
+       for (t = 1; t <= config->cluster_highest_tunnelid; t++)
        {
                // check for expired tunnels
                if (tunnel[t].die && tunnel[t].die <= TIME)
@@ -2079,35 +2011,23 @@ int regular_cleanups(void)
                        controladd(c, t, 0); // send the message
                        log(3, tunnel[t].ip, 0, t, "Sending HELLO message\n");
                }
-       }
 
-       // Check for sessions that have been killed from the CLI
-       if (cli_session_kill[0])
-       {
-               int i;
-               for (i = 0; i < MAXSESSION && cli_session_kill[i]; i++)
-               {
-                       log(2, 0, cli_session_kill[i], 0, "Dropping session by CLI\n");
-                       sessionshutdown(cli_session_kill[i], "Requested by administrator");
-                       cli_session_kill[i] = 0;
-               }
-       }
-       // Check for tunnels that have been killed from the CLI
-       if (cli_tunnel_kill[0])
-       {
-               int i;
-               for (i = 1; i < MAXTUNNEL && cli_tunnel_kill[i]; i++)
+               // Check for tunnel changes requested from the CLI
+               if ((a = cli_tunnel_actions[t].action))
                {
-                       log(2, 0, cli_tunnel_kill[i], 0, "Dropping tunnel by CLI\n");
-                       tunnelshutdown(cli_tunnel_kill[i], "Requested by administrator");
-                       cli_tunnel_kill[i] = 0;
+                       cli_tunnel_actions[t].action = 0;
+                       if (a & CLI_TUN_KILL)
+                       {
+                               log(2, tunnel[t].ip, 0, t, "Dropping tunnel by CLI\n");
+                               tunnelshutdown(t, "Requested by administrator");
+                       }
                }
+
        }
 
        count = 0;
        for (i = 1; i <= config->cluster_highest_sessionid; i++)
        {
-
                s++;
                if (s > config->cluster_highest_sessionid)
                        s = 1;
@@ -2161,7 +2081,60 @@ int regular_cleanups(void)
                        if (++count >= MAX_ACTIONS) break;
                        continue;
                }
+
+               // 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, 0, s, session[s].tunnel, "Dropping session by CLI\n");
+                               sessionshutdown(s, "Requested by administrator");
+                               a = 0; // dead, no need to check for other actions
+                       }
+
+                       if (a & CLI_SESS_SNOOP)
+                       {
+                               log(2, 0, s, session[s].tunnel, "Snooping session by CLI (to %s:%d)\n",
+                                   inet_toa(cli_session_actions[s].snoop_ip), 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;
+                               send++;
+                       }
+
+                       if (a & CLI_SESS_NOSNOOP)
+                       {
+                               log(2, 0, s, session[s].tunnel, "Unsnooping session by CLI\n");
+                               session[s].snoop_ip = 0;
+                               session[s].snoop_port = 0;
+                               send++;
+                       }
+
+                       if (a & CLI_SESS_THROTTLE)
+                       {
+                               log(2, 0, s, session[s].tunnel, "Throttling session by CLI (to %d)\n",
+                                   cli_session_actions[s].throttle);
+
+                               throttle_session(s, cli_session_actions[s].throttle);
+                       }
+
+                       if (a & CLI_SESS_NOTHROTTLE)
+                       {
+                               log(2, 0, s, session[s].tunnel, "Un-throttling session by CLI\n");
+                               throttle_session(s, 0);
+                       }
+
+                       if (send)
+                               cluster_send_session(s);
+
+                       if (++count >= MAX_ACTIONS) break;
+                       continue;
+               }
        }
+
        if (config->accounting_dir && next_acct <= TIME)
        {
                // Dump accounting data
@@ -2213,27 +2186,27 @@ int still_busy(void)
        return 0;
 }
 
-// main loop - gets packets on tap or udp and processes them
+// main loop - gets packets on tun or udp and processes them
 void mainloop(void)
 {
        fd_set cr;
        int cn, i;
        u8 buf[65536];
        struct timeval to;
-       time_t next_cluster_ping = 0;   // default 1 second pings.
-       clockt next_clean = time_now + config->cleanup_interval;
+       clockt next_cluster_ping = 0;   // send initial ping immediately
+       time_t next_clean = time_now + config->cleanup_interval;
 
-       log(4, 0, 0, 0, "Beginning of main loop. udpfd=%d, tapfd=%d, cluster_sockfd=%d, controlfd=%d\n",
-                       udpfd, tapfd, cluster_sockfd, controlfd);
+       log(4, 0, 0, 0, "Beginning of main loop. udpfd=%d, tunfd=%d, cluster_sockfd=%d, controlfd=%d\n",
+                       udpfd, tunfd, cluster_sockfd, controlfd);
 
        FD_ZERO(&cr);
        FD_SET(udpfd, &cr);
-       FD_SET(tapfd, &cr);
+       FD_SET(tunfd, &cr);
        FD_SET(controlfd, &cr);
        FD_SET(clifd, &cr);
        if (cluster_sockfd) FD_SET(cluster_sockfd, &cr);
        cn = udpfd;
-       if (cn < tapfd) cn = tapfd;
+       if (cn < tunfd) cn = tunfd;
        if (cn < controlfd) cn = controlfd;
        if (cn < clifd) cn = clifd;
        if (cn < cluster_sockfd) cn = cluster_sockfd;
@@ -2314,13 +2287,13 @@ void mainloop(void)
                                                break;
                                }
                        }
-                       if (FD_ISSET(tapfd, &r))
+                       if (FD_ISSET(tunfd, &r))
                        {
                                int c, n;
                                for (c = 0; c < config->multi_read_count; c++)
                                {
-                                       if ((n = read(tapfd, buf, sizeof(buf))) > 0)
-                                               processtap(buf, n);
+                                       if ((n = read(tunfd, buf, sizeof(buf))) > 0)
+                                               processtun(buf, n);
                                        else
                                                break;
                                }
@@ -2355,16 +2328,22 @@ void mainloop(void)
                }
 
                        // Runs on every machine (master and slaves).
-               if (cluster_sockfd && next_cluster_ping <= time_now)
+               if (cluster_sockfd && next_cluster_ping <= TIME)
                {
                        // Check to see which of the cluster is still alive..
-                       next_cluster_ping = time_now + 1;
-                       cluster_send_ping(basetime);
 
-                       cluster_check_master();
+                       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.
 
-                       cluster_heartbeat(config->cluster_highest_sessionid, sessionfree, config->cluster_highest_tunnelid);    // Only does anything if we're a master.
                        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;
                }
 
                        // Run token bucket filtering queue..
@@ -2401,11 +2380,8 @@ void mainloop(void)
        }
 
                // Are we the master and shutting down??
-       if (config->cluster_iam_master) {
-
-               cluster_heartbeat(config->cluster_highest_sessionid, sessionfree,
-                       config->cluster_highest_tunnelid);      // Flush any queued changes..
-       }
+       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.
@@ -2464,33 +2440,33 @@ void initdata(void)
        ip_address_pool = mmap(NULL, sizeof(ippoolt) * MAXIPPOOL, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, 0, 0);
        if (ip_address_pool == MAP_FAILED)
        {
-               log(0, 0, 0, 0, "Error doing mmap for radius: %s\n", strerror(errno));
+               log(0, 0, 0, 0, "Error doing mmap for ip_address_pool: %s\n", strerror(errno));
                exit(1);
        }
 #ifdef RINGBUFFER
        ringbuffer = mmap(NULL, sizeof(struct Tringbuffer), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, 0, 0);
        if (ringbuffer == MAP_FAILED)
        {
-               log(0, 0, 0, 0, "Error doing mmap for radius: %s\n", strerror(errno));
+               log(0, 0, 0, 0, "Error doing mmap for ringbuffer: %s\n", strerror(errno));
                exit(1);
        }
        memset(ringbuffer, 0, sizeof(struct Tringbuffer));
 #endif
 
-       cli_session_kill = mmap(NULL, sizeof(sessionidt) * MAXSESSION, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, 0, 0);
-       if (cli_session_kill == MAP_FAILED)
+       cli_session_actions = mmap(NULL, sizeof(struct cli_session_actions) * MAXSESSION, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, 0, 0);
+       if (cli_session_actions == MAP_FAILED)
        {
-               log(0, 0, 0, 0, "Error doing mmap for cli session kill: %s\n", strerror(errno));
+               log(0, 0, 0, 0, "Error doing mmap for cli session actions: %s\n", strerror(errno));
                exit(1);
        }
-       memset(cli_session_kill, 0, sizeof(sessionidt) * MAXSESSION);
-       cli_tunnel_kill = mmap(NULL, sizeof(tunnelidt) * MAXSESSION, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, 0, 0);
-       if (cli_tunnel_kill == MAP_FAILED)
+       memset(cli_session_actions, 0, sizeof(struct cli_session_actions) * MAXSESSION);
+       cli_tunnel_actions = mmap(NULL, sizeof(struct cli_tunnel_actions) * MAXSESSION, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, 0, 0);
+       if (cli_tunnel_actions == MAP_FAILED)
        {
-               log(0, 0, 0, 0, "Error doing mmap for cli tunnel kill: %s\n", strerror(errno));
+               log(0, 0, 0, 0, "Error doing mmap for cli tunnel actions: %s\n", strerror(errno));
                exit(1);
        }
-       memset(cli_tunnel_kill, 0, sizeof(tunnelidt) * MAXSESSION);
+       memset(cli_tunnel_actions, 0, sizeof(struct cli_tunnel_actions) * MAXSESSION);
 
        memset(tunnel, 0, sizeof(tunnelt) * MAXTUNNEL);
        memset(session, 0, sizeof(sessiont) * MAXSESSION);
@@ -2512,8 +2488,10 @@ void initdata(void)
 
        if (!*hostname)
        {
+               char *p;
                // Grab my hostname unless it's been specified
                gethostname(hostname, sizeof(hostname));
+               if ((p = strchr(hostname, '.'))) *p = 0;
        }
        _statistics->start_time = _statistics->last_reset = time(NULL);
 
@@ -2539,7 +2517,7 @@ int assign_ip_address(sessionidt s)
 {
        u32 i;
        int best = -1;
-       clockt best_time = time_now;
+       time_t best_time = time_now;
        char *u = session[s].user;
        char reuse = 0;
 
@@ -2793,9 +2771,11 @@ void initippool()
 
 void snoop_send_packet(char *packet, u16 size, ipt destination, u16 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);
 
@@ -2862,6 +2842,7 @@ void dump_acct_info()
 int main(int argc, char *argv[])
 {
        int o;
+       int optdebug = 0;
 
        _program_name = strdup(argv[0]);
 
@@ -2878,10 +2859,10 @@ int main(int argc, char *argv[])
                                if (fork()) exit(0);
                                break;
                        case 'v':
-                               config->debug++;
+                               optdebug++;
                                break;
                        case 'h':
-                               strncpy(hostname, optarg, 999);
+                               snprintf(hostname, sizeof(hostname), "%s", optarg);
                                break;
                        case '?':
                        default:
@@ -2899,15 +2880,18 @@ int main(int argc, char *argv[])
 
        // Start the timer routine off
        time(&time_now);
-       strftime(time_now_string, 64, "%Y-%m-%d %H:%M:%S", localtime(&time_now));
+       strftime(time_now_string, sizeof(time_now_string), "%Y-%m-%d %H:%M:%S", localtime(&time_now));
        signal(SIGALRM, sigalrm_handler);
        siginterrupt(SIGALRM, 0);
 
        initiptables();
        initplugins();
        initdata();
+
+       config->debug = optdebug;
+
        init_tbf();
-       init_cli();
+       init_cli(hostname);
        read_config_file();
 
        log(0, 0, 0, 0, "L2TPNS version " VERSION "\n");
@@ -2966,8 +2950,8 @@ int main(int argc, char *argv[])
                    config->bgp_peer_as[1], 0);
 #endif /* BGP */
 
-       inittap();
-       log(1, 0, 0, 0, "Set up on interface %s\n", config->tapdevice);
+       inittun();
+       log(1, 0, 0, 0, "Set up on interface %s\n", config->tundevice);
 
        initudp();
        initrad();
@@ -2981,6 +2965,15 @@ int main(int argc, char *argv[])
        signal(SIGQUIT, sigquit_handler);
        signal(SIGCHLD, sigchild_handler);
 
+       // Prevent us from getting paged out
+       if (config->lock_pages)
+       {
+               if (!mlockall(MCL_CURRENT))
+                       log(1, 0, 0, 0, "Locking pages into memory\n");
+               else
+                       log(0, 0, 0, 0, "Can't lock pages: %s\n", strerror(errno));
+       }
+
        alarm(1);
 
        // Drop privileges here
@@ -3003,6 +2996,10 @@ int main(int argc, char *argv[])
        /* 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);
@@ -3042,7 +3039,7 @@ void sigalrm_handler(int junk)
 
        // Update the internal time counter
        time(&time_now);
-       strftime(time_now_string, 64, "%Y-%m-%d %H:%M:%S", localtime(&time_now));
+       strftime(time_now_string, sizeof(time_now_string), "%Y-%m-%d %H:%M:%S", localtime(&time_now));
        alarm(1);
 
        {
@@ -3291,6 +3288,8 @@ static int facility_value(char *name)
 void update_config()
 {
        int i;
+       static int timeout = 0;
+       static int interval = 0;
 
        // Update logging
        closelog();
@@ -3335,7 +3334,21 @@ void update_config()
        // Update radius
        config->numradiusservers = 0;
        for (i = 0; i < MAXRADSERVER; i++)
-               if (config->radiusserver[i]) config->numradiusservers++;
+               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)
        {
@@ -3373,6 +3386,42 @@ void update_config()
        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,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, 0, "Can't write to PID file %s: %s\n", config->pid_file, strerror(errno));
+               }
+       }
+
        config->reload_config = 0;
 }
 
@@ -3545,8 +3594,6 @@ int load_session(sessionidt s, sessiont *new)
                        routeset(s, new->route[i].ip, new->route[i].mask, new->ip, 1);
        }
 
-
-
        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;
@@ -3556,6 +3603,7 @@ int load_session(sessionidt s, sessiont *new)
                // Do fixups into address pool.
        if (new->ip_pool_index != -1)
                fix_address_pool(s);
+
        return 1;
 }
 
@@ -3725,7 +3773,7 @@ void processcontrol(u8 * buf, int len, struct sockaddr_in *addr)
 
        if (log_stream && config->debug >= 4)
        {
-       log(4, ntohl(addr->sin_addr.s_addr), 0, 0, "Received ");
+               log(4, ntohl(addr->sin_addr.s_addr), 0, 0, "Received ");
                dump_packet(buf, log_stream);
        }
 
@@ -3808,7 +3856,7 @@ void become_master(void)
        int s;
        run_plugins(PLUGIN_BECOME_MASTER, NULL);
 
-       for (s = 0; s < config->cluster_highest_sessionid ; ++s) {
+       for (s = 1; s <= config->cluster_highest_sessionid ; ++s) {
                if (!session[s].tunnel) // Not an in-use session.
                        continue;
 
@@ -3830,7 +3878,7 @@ int cmd_show_hist_idle(struct cli_def *cli, char *command, char **argv, int argc
        time(&time_now);
        for (i = 0; i < 64;++i) buckets[i] = 0;
 
-       for (s = 0; s < config->cluster_highest_sessionid ; ++s) {
+       for (s = 1; s <= config->cluster_highest_sessionid ; ++s) {
                int idle;
                if (!session[s].tunnel)
                        continue;
@@ -3866,7 +3914,7 @@ int cmd_show_hist_open(struct cli_def *cli, char *command, char **argv, int argc
        time(&time_now);
        for (i = 0; i < 64;++i) buckets[i] = 0;
 
-       for (s = 0; s < config->cluster_highest_sessionid ; ++s) {
+       for (s = 1; s <= config->cluster_highest_sessionid ; ++s) {
                int open = 0, d;
                if (!session[s].tunnel)
                        continue;