update filter refcounts
[l2tpns.git] / l2tpns.c
index 93f5285..16e5a29 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.48 2004/11/11 03:07:42 bodea Exp $";
+char const *cvs_id_l2tpns = "$Id: l2tpns.c,v 1.58 2004/11/28 02:53:11 bodea Exp $";
 
 #include <arpa/inet.h>
 #include <assert.h>
@@ -54,42 +54,43 @@ char const *cvs_id_l2tpns = "$Id: l2tpns.c,v 1.48 2004/11/11 03:07:42 bodea Exp
 #endif /* BGP */
 
 // Globals
-struct configt *config = NULL; // all configuration
+configt *config = NULL;                // all configuration
 int tunfd = -1;                        // tun interface file handle. (network device)
 int udpfd = -1;                        // UDP file handle
 int controlfd = -1;            // Control signal handle
+int clifd = -1;                        // Socket listening for CLI connections.
 int 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
 time_t basetime = 0;           // base clock
 char hostname[1000] = "";      // us.
-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;       // file handle for direct logging (i.e. direct into file, not via syslog).
+static u32 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).
 extern int cluster_sockfd;     // Intra-cluster communications socket.
 u32 last_id = 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;          // 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;
+// Traffic counters.
+static u32 udp_rx = 0, udp_rx_pkt = 0, udp_tx = 0;
+static u32 eth_rx = 0, eth_rx_pkt = 0;
+u32 eth_tx = 0;
+
+static 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.
+static char time_now_string[64] = {0}; // Current time as a string.
+static char main_quit = 0;             // True if we're in the process of exiting.
 linked_list *loaded_plugins;
 linked_list *plugins[MAX_PLUGIN_TYPES];
 
 #define membersize(STRUCT, MEMBER) sizeof(((STRUCT *)0)->MEMBER)
-#define CONFIG(NAME, MEMBER, TYPE) { NAME, offsetof(struct configt, MEMBER), membersize(struct configt, MEMBER), TYPE }
+#define CONFIG(NAME, MEMBER, TYPE) { NAME, offsetof(configt, MEMBER), membersize(configt, MEMBER), TYPE }
 
-struct config_descriptt config_values[] = {
+config_descriptt config_values[] = {
        CONFIG("debug", debug, INT),
        CONFIG("log_file", log_filename, STRING),
        CONFIG("pid_file", pid_file, STRING),
@@ -123,7 +124,7 @@ struct config_descriptt config_values[] = {
        { NULL, 0, 0, 0 },
 };
 
-char *plugin_functions[] = {
+static char *plugin_functions[] = {
        NULL,
        "plugin_pre_auth",
        "plugin_post_auth",
@@ -145,30 +146,37 @@ 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;
+ip_filtert *ip_filters = NULL; // Array of named filters.
+static controlt *controlfree = 0;
 struct Tstats *_statistics = NULL;
 #ifdef RINGBUFFER
 struct Tringbuffer *ringbuffer = NULL;
 #endif
 
-void sigalrm_handler(int);
-void sighup_handler(int);
-void sigterm_handler(int);
-void sigquit_handler(int);
-void sigchild_handler(int);
-void read_config_file();
-void read_state();
-void dump_state();
-void tunnel_clean();
-tunnelidt new_tunnel();
-void update_config();
-int unhide_avp(u8 *avp, tunnelidt t, sessionidt s, u16 length);
-
 static void cache_ipmap(ipt ip, int s);
 static void uncache_ipmap(ipt ip);
+static void free_ip_address(sessionidt s);
+static void dump_acct_info(void);
+static void sighup_handler(int sig);
+static void sigalrm_handler(int sig);
+static void sigterm_handler(int sig);
+static void sigquit_handler(int sig);
+static void sigchild_handler(int sig);
+static void read_state(void);
+static void dump_state(void);
+static void build_chap_response(char *challenge, u8 id, u16 challenge_length, char **challenge_response);
+static void update_config(void);
+static void read_config_file(void);
+static void initplugins(void);
+static int add_plugin(char *plugin_name);
+static int remove_plugin(char *plugin_name);
+static void plugins_done(void);
+static void processcontrol(u8 * buf, int len, struct sockaddr_in *addr, int alen);
+static tunnelidt new_tunnel(void);
+static int unhide_avp(u8 *avp, tunnelidt t, sessionidt s, u16 length);
 
 // return internal time (10ths since process startup)
-clockt now(void)
+static clockt now(void)
 {
        struct timeval t;
        gettimeofday(&t, 0);
@@ -286,12 +294,12 @@ void _log_hex(int level, const char *title, const char *data, int maxsize)
 // Add a route
 //
 // This adds it to the routing table, advertises it
-// via iBGP if enabled, and stuffs it into the
+// via BGP if enabled, and stuffs it into the
 // 'sessionbyip' cache.
 //
 // 'ip' and 'mask' must be in _host_ order.
 //
-void routeset(sessionidt s, ipt ip, ipt mask, ipt gw, u8 add)
+static void routeset(sessionidt s, ipt ip, ipt mask, ipt gw, u8 add)
 {
        struct rtentry r;
        int i;
@@ -331,7 +339,7 @@ void routeset(sessionidt s, ipt ip, ipt mask, ipt gw, u8 add)
 #endif /* BGP */
 
                // Add/Remove the IPs to the 'sessionbyip' cache.
-               // Note that we add the zero address in the case of 
+               // Note that we add the zero address in the case of
                // a network route. Roll on CIDR.
 
                // Note that 's == 0' implies this is the address pool.
@@ -350,7 +358,7 @@ void routeset(sessionidt s, ipt ip, ipt mask, ipt gw, u8 add)
 
 //
 // Set up TUN interface
-void inittun(void)
+static void inittun(void)
 {
        struct ifreq ifr;
        struct sockaddr_in sin = {0};
@@ -398,16 +406,10 @@ void inittun(void)
                LOG(0, 0, 0, 0, "Error setting tun flags: %s\n", strerror(errno));
                exit(1);
        }
-       if (ioctl(ifrfd, SIOCGIFINDEX, (void *) &ifr) < 0)
-       {
-               LOG(0, 0, 0, 0, "Error setting tun ifindex: %s\n", strerror(errno));
-               exit(1);
-       }
-       tunidx = ifr.ifr_ifindex;
 }
 
 // set up UDP port
-void initudp(void)
+static void initudp(void)
 {
        int on = 1;
        struct sockaddr_in addr;
@@ -433,8 +435,8 @@ void initudp(void)
        // Control
        memset(&addr, 0, sizeof(addr));
        addr.sin_family = AF_INET;
-       addr.sin_port = htons(1702);
-       controlfd = socket(AF_INET, SOCK_DGRAM, 17);
+       addr.sin_port = htons(NSCTL_PORT);
+       controlfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        setsockopt(controlfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
        if (bind(controlfd, (void *) &addr, sizeof(addr)) < 0)
        {
@@ -458,7 +460,7 @@ void initudp(void)
 // IP address.
 //
 
-int lookup_ipmap(ipt ip)
+static int lookup_ipmap(ipt ip)
 {
        u8 *a = (u8 *)&ip;
        char **d = (char **) ip_hash;
@@ -619,13 +621,13 @@ void send_garp(ipt ip)
 }
 
 // Find session by username, 0 for not found
-sessiont *sessiontbysessionidt(sessionidt s)
+static sessiont *sessiontbysessionidt(sessionidt s)
 {
        if (!s || s > MAXSESSION) return NULL;
        return &session[s];
 }
 
-sessionidt sessionidtbysessiont(sessiont *s)
+static sessionidt sessionidtbysessiont(sessiont *s)
 {
        sessionidt val = s-session;
        if (s < session || val > MAXSESSION) return 0;
@@ -656,6 +658,7 @@ void tunnelsend(u8 * buf, u16 l, tunnelidt t)
                STAT(tunnel_tx_errors);
                return;
        }
+
        memset(&addr, 0, sizeof(addr));
        addr.sin_family = AF_INET;
        *(u32 *) & addr.sin_addr = htonl(tunnel[t].ip);
@@ -700,7 +703,7 @@ int tun_write(u8 * data, int size)
 
 // process outgoing (to tunnel) IP
 //
-void processipout(u8 * buf, int len)
+static void processipout(u8 * buf, int len)
 {
        sessionidt s;
        sessiont *sp;
@@ -782,11 +785,7 @@ void processipout(u8 * buf, int len)
        // Add on L2TP header
        {
                u8 *p = makeppp(b, sizeof(b), buf, len, t, s, PPPIP);
-               if (!p)
-               {
-                       LOG(3, session[s].ip, s, t, "failed to send packet in processipout.\n");
-                       return;
-               }
+               if (!p) return;
                tunnelsend(b, len + (p-b), t); // send it...
        }
 
@@ -805,7 +804,7 @@ void processipout(u8 * buf, int len)
 // Helper routine for the TBF filters.
 // Used to send queued data in to the user!
 //
-void send_ipout(sessionidt s, u8 *buf, int len)
+static void send_ipout(sessionidt s, u8 *buf, int len)
 {
        sessiont *sp;
        tunnelidt t;
@@ -836,11 +835,7 @@ void send_ipout(sessionidt s, u8 *buf, int len)
        // Add on L2TP header
        {
                u8 *p = makeppp(b, sizeof(b),  buf, len, t, s, PPPIP);
-               if (!p)
-               {
-                       LOG(3, session[s].ip, s, t, "failed to send packet in send_ipout.\n");
-                       return;
-               }
+               if (!p) return;
                tunnelsend(b, len + (p-b), t); // send it...
        }
 
@@ -856,7 +851,7 @@ void send_ipout(sessionidt s, u8 *buf, int len)
 }
 
 // add an AVP (16 bit)
-void control16(controlt * c, u16 avp, u16 val, u8 m)
+static void control16(controlt * c, u16 avp, u16 val, u8 m)
 {
        u16 l = (m ? 0x8008 : 0x0008);
        *(u16 *) (c->buf + c->length + 0) = htons(l);
@@ -867,7 +862,7 @@ void control16(controlt * c, u16 avp, u16 val, u8 m)
 }
 
 // add an AVP (32 bit)
-void control32(controlt * c, u16 avp, u32 val, u8 m)
+static void control32(controlt * c, u16 avp, u32 val, u8 m)
 {
        u16 l = (m ? 0x800A : 0x000A);
        *(u16 *) (c->buf + c->length + 0) = htons(l);
@@ -878,7 +873,7 @@ void control32(controlt * c, u16 avp, u32 val, u8 m)
 }
 
 // add an AVP (32 bit)
-void controls(controlt * c, u16 avp, char *val, u8 m)
+static void controls(controlt * c, u16 avp, char *val, u8 m)
 {
        u16 l = ((m ? 0x8000 : 0) + strlen(val) + 6);
        *(u16 *) (c->buf + c->length + 0) = htons(l);
@@ -889,7 +884,7 @@ void controls(controlt * c, u16 avp, char *val, u8 m)
 }
 
 // add a binary AVP
-void controlb(controlt * c, u16 avp, char *val, unsigned int len, u8 m)
+static void controlb(controlt * c, u16 avp, char *val, unsigned int len, u8 m)
 {
        u16 l = ((m ? 0x8000 : 0) + len + 6);
        *(u16 *) (c->buf + c->length + 0) = htons(l);
@@ -900,7 +895,7 @@ void controlb(controlt * c, u16 avp, char *val, unsigned int len, u8 m)
 }
 
 // new control connection
-controlt *controlnew(u16 mtype)
+static controlt *controlnew(u16 mtype)
 {
        controlt *c;
        if (!controlfree)
@@ -920,7 +915,7 @@ controlt *controlnew(u16 mtype)
 
 // send zero block if nothing is waiting
 // (ZLB send).
-void controlnull(tunnelidt t)
+static void controlnull(tunnelidt t)
 {
        u8 buf[12];
        if (tunnel[t].controlc) // Messages queued; They will carry the ack.
@@ -936,7 +931,7 @@ void controlnull(tunnelidt t)
 }
 
 // add a control message to a tunnel, and send if within window
-void controladd(controlt * c, tunnelidt t, sessionidt s)
+static void controladd(controlt * c, tunnelidt t, sessionidt s)
 {
        *(u16 *) (c->buf + 2) = htons(c->length); // length
        *(u16 *) (c->buf + 4) = htons(tunnel[t].far); // tunnel
@@ -948,8 +943,10 @@ void controladd(controlt * c, tunnelidt t, sessionidt s)
                tunnel[t].controle->next = c;
        else
                tunnel[t].controls = c;
+
        tunnel[t].controle = c;
        tunnel[t].controlc++;
+
        // send now if space in window
        if (tunnel[t].controlc <= tunnel[t].window)
        {
@@ -1005,10 +1002,47 @@ 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)
+               return; // No-one home.
+
+       if (!*session[s].user)
+               return; // User not logged in
+
+       // paranoia
+       if (filter_in > MAXFILTER) filter_in = -1;
+       if (filter_out > MAXFILTER) filter_out = -1;
+       if (session[s].filter_in > MAXFILTER) session[s].filter_in = 0;
+       if (session[s].filter_out > MAXFILTER) session[s].filter_out = 0;
+
+       if (filter_in >= 0)
+       {
+               if (session[s].filter_in)
+                       ip_filters[session[s].filter_in - 1].used--;
+
+               if (filter_in > 0)
+                       ip_filters[filter_in - 1].used++;
+
+               session[s].filter_in = filter_in;
+       }
+
+       if (filter_out >= 0)
+       {
+               if (session[s].filter_out)
+                       ip_filters[session[s].filter_out - 1].used--;
+
+               if (filter_out > 0)
+                       ip_filters[filter_out - 1].used++;
+
+               session[s].filter_out = filter_out;
+       }
+}
+
 // start tidy shutdown of session
 void sessionshutdown(sessionidt s, char *reason)
 {
-       int dead = session[s].die;
        int walled_garden = session[s].walled_garden;
 
 
@@ -1020,18 +1054,15 @@ void sessionshutdown(sessionidt s, char *reason)
                return;                   // not a live session
        }
 
-       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
-
+       if (!session[s].die)
        {
                struct param_kill_session data = { &tunnel[session[s].tunnel], &session[s] };
+               LOG(2, 0, s, session[s].tunnel, "Shutting down session %d: %s\n", s, reason);
                run_plugins(PLUGIN_KILL_SESSION, &data);
        }
 
        // RADIUS Stop message
-       if (session[s].opened && !walled_garden && !dead)
+       if (session[s].opened && !walled_garden && !session[s].die)
        {
                u16 r = session[s].radius;
                if (!r)
@@ -1055,15 +1086,20 @@ void sessionshutdown(sessionidt s, char *reason)
        if (session[s].ip)
        {                          // IP allocated, clear and unroute
                int r;
+               int routed = 0;
                for (r = 0; r < MAXROUTE && session[s].route[r].ip; r++)
                {
-                       routeset(s, session[s].route[r].ip, session[s].route[r].mask, session[s].ip, 0);
+                       if ((session[s].ip & session[s].route[r].mask) ==
+                           (session[s].route[r].ip & session[s].route[r].mask))
+                               routed++;
+
+                       routeset(s, session[s].route[r].ip, session[s].route[r].mask, 0, 0);
                        session[s].route[r].ip = 0;
                }
 
                if (session[s].ip_pool_index == -1) // static ip
                {
-                       routeset(s, session[s].ip, 0, 0, 0);    // Delete route.
+                       if (!routed) routeset(s, session[s].ip, 0, 0, 0);
                        session[s].ip = 0;
                }
                else
@@ -1080,6 +1116,13 @@ void sessionshutdown(sessionidt s, char *reason)
                controladd(c, session[s].tunnel, s); // send the message
        }
 
+       if (!session[s].die)
+               session[s].die = now() + 150; // Clean up in 15 seconds
+
+       // update filter refcounts
+       if (session[s].filter_in) ip_filters[session[s].filter_in - 1].used--;
+       if (session[s].filter_out) ip_filters[session[s].filter_out - 1].used--;
+
        cluster_send_session(s);
 }
 
@@ -1109,11 +1152,7 @@ void sendipcp(tunnelidt t, sessionidt s)
        }
 
        q = makeppp(buf,sizeof(buf), 0, 0, t, s, PPPIPCP);
-       if (!q)
-       {
-               LOG(3, session[s].ip, s, t, "failed to send packet in sendipcp.\n");
-               return;
-       }
+       if (!q) return;
 
        *q = ConfigReq;
        q[1] = r << RADIUS_SHIFT;                    // ID, dont care, we only send one type of request
@@ -1129,7 +1168,7 @@ void sendipcp(tunnelidt t, sessionidt s)
 }
 
 // kill a session now
-void sessionkill(sessionidt s, char *reason)
+static void sessionkill(sessionidt s, char *reason)
 {
 
        CSTAT(call_sessionkill);
@@ -1149,8 +1188,15 @@ void sessionkill(sessionidt s, char *reason)
        cluster_send_session(s);
 }
 
+static void tunnelclear(tunnelidt t)
+{
+       if (!t) return;
+       memset(&tunnel[t], 0, sizeof(tunnel[t]));
+       tunnel[t].state = TUNNELFREE;
+}
+
 // kill a tunnel now
-void tunnelkill(tunnelidt t, char *reason)
+static void tunnelkill(tunnelidt t, char *reason)
 {
        sessionidt s;
        controlt *c;
@@ -1181,7 +1227,7 @@ void tunnelkill(tunnelidt t, char *reason)
 }
 
 // shut down a tunnel cleanly
-void tunnelshutdown(tunnelidt t, char *reason)
+static void tunnelshutdown(tunnelidt t, char *reason)
 {
        sessionidt s;
 
@@ -1198,7 +1244,7 @@ void tunnelshutdown(tunnelidt t, char *reason)
        // close session
        for (s = 1; s < MAXSESSION; s++)
                if (session[s].tunnel == t)
-                       sessionkill(s, reason);
+                       sessionshutdown(s, reason);
 
        tunnel[t].state = TUNNELDIE;
        tunnel[t].die = now() + 700; // Clean up in 70 seconds
@@ -1305,10 +1351,9 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr)
                        STAT(tunnel_rx_errors);
                        return;
                }
-               LOG(3, ntohl(addr->sin_addr.s_addr), s, t, "Control message (%d bytes): (unacked %d) l-ns %d l-nr %d r-ns %d r-nr %d\n",
-                               l, tunnel[t].controlc, tunnel[t].ns, tunnel[t].nr, ns, nr);
-               // if no tunnel specified, assign one
-               if (!t)
+
+               // check for duplicate tunnel open message
+               if (!t && ns == 0)
                {
                        int i;
 
@@ -1322,10 +1367,15 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr)
                                        tunnel[i].port != ntohs(addr->sin_port) )
                                        continue;
                                t = i;
+                               LOG(3, ntohl(addr->sin_addr.s_addr), s, t, "Duplicate SCCRQ?\n");
                                break;
                        }
                }
 
+               LOG(3, ntohl(addr->sin_addr.s_addr), s, t, "Control message (%d bytes): (unacked %d) l-ns %d l-nr %d r-ns %d r-nr %d\n",
+                               l, tunnel[t].controlc, tunnel[t].ns, tunnel[t].nr, ns, nr);
+
+               // if no tunnel specified, assign one
                if (!t)
                {
                        if (!(t = new_tunnel()))
@@ -1338,8 +1388,28 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr)
                        tunnel[t].ip = ntohl(*(ipt *) & addr->sin_addr);
                        tunnel[t].port = ntohs(addr->sin_port);
                        tunnel[t].window = 4; // default window
-                       LOG(1, ntohl(addr->sin_addr.s_addr), 0, t, "   New tunnel from %u.%u.%u.%u/%u ID %d\n", tunnel[t].ip >> 24, tunnel[t].ip >> 16 & 255, tunnel[t].ip >> 8 & 255, tunnel[t].ip & 255, tunnel[t].port, t);
                        STAT(tunnel_created);
+                       LOG(1, ntohl(addr->sin_addr.s_addr), 0, t, "   New tunnel from %u.%u.%u.%u/%u ID %d\n",
+                               tunnel[t].ip >> 24, tunnel[t].ip >> 16 & 255,
+                               tunnel[t].ip >> 8 & 255, tunnel[t].ip & 255, tunnel[t].port, t);
+               }
+
+                       // If the 'ns' just received is not the 'nr' we're
+                       // expecting, just send an ack and drop it.
+                       //
+                       // if 'ns' is less, then we got a retransmitted packet.
+                       // if 'ns' is greater than missed a packet. Either way
+                       // we should ignore it.
+               if (ns != tunnel[t].nr)
+               {
+                       // is this the sequence we were expecting?
+                       STAT(tunnel_rx_errors);
+                       LOG(1, ntohl(addr->sin_addr.s_addr), 0, t, "   Out of sequence tunnel %d, (%d is not the expected %d)\n",
+                               t, ns, tunnel[t].nr);
+
+                       if (l)  // Is this not a ZLB?
+                               controlnull(t);
+                       return;
                }
 
                // This is used to time out old tunnels
@@ -1349,7 +1419,7 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr)
                {
                        int skip = tunnel[t].window; // track how many in-window packets are still in queue
                                // some to clear maybe?
-                       while (tunnel[t].controlc && (((tunnel[t].ns - tunnel[t].controlc) - nr) & 0x8000))
+                       while (tunnel[t].controlc > 0 && (((tunnel[t].ns - tunnel[t].controlc) - nr) & 0x8000))
                        {
                                controlt *c = tunnel[t].controls;
                                tunnel[t].controls = c->next;
@@ -1360,22 +1430,6 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr)
                                tunnel[t].try = 0; // we have progress
                        }
 
-                               // If the 'ns' just received is not the 'nr' we're
-                               // expecting, just send an ack and drop it.
-                               //
-                               // if 'ns' is less, then we got a retransmitted packet.
-                               // if 'ns' is greater than missed a packet. Either way
-                               // we should ignore it.
-                       if (ns != tunnel[t].nr)
-                       {
-                               // is this the sequence we were expecting?
-                               LOG(1, ntohl(addr->sin_addr.s_addr), 0, t, "   Out of sequence tunnel %d, (%d is not the expected %d)\n", t, ns, tunnel[t].nr);
-                               STAT(tunnel_rx_errors);
-
-                               if (l)  // Is this not a ZLB?
-                                       controlnull(t);
-                               return;
-                       }
                        // receiver advance (do here so quoted correctly in any sends below)
                        if (l) tunnel[t].nr = (ns + 1);
                        if (skip < 0) skip = 0;
@@ -1921,7 +1975,7 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr)
 }
 
 // read and process packet on tun
-void processtun(u8 * buf, int len)
+static void processtun(u8 * buf, int len)
 {
        LOG_HEX(5, "Receive TUN Data", buf, len);
        STAT(tun_rx_packets);
@@ -1949,7 +2003,7 @@ void processtun(u8 * buf, int len)
 // at once.
 #define MAX_ACTIONS 500
 
-int regular_cleanups(void)
+static int regular_cleanups(void)
 {
        static sessionidt s = 0;        // Next session to check for actions on.
        tunnelidt t;
@@ -2060,11 +2114,7 @@ int regular_cleanups(void)
                        u8 b[MAXCONTROL] = {0};
 
                        u8 *q = makeppp(b, sizeof(b), 0, 0, session[s].tunnel, s, PPPLCP);
-                       if (!q)
-                       {
-                               LOG(3, session[s].ip, s, t, "failed to send ECHO packet.\n");
-                               continue;
-                       }
+                       if (!q) continue;
 
                        *q = EchoReq;
                        *(u8 *)(q + 1) = (time_now % 255); // ID
@@ -2123,6 +2173,22 @@ int regular_cleanups(void)
                                send++;
                        }
 
+                       if (a & CLI_SESS_NOFILTER)
+                       {
+                               LOG(2, 0, s, session[s].tunnel, "Un-filtering session by CLI\n");
+                               filter_session(s, 0, 0);
+                               send++;
+                       }
+                       else if (a & CLI_SESS_FILTER)
+                       {
+                               LOG(2, 0, s, session[s].tunnel, "Filtering session by CLI (in=%d, out=%d)\n",
+                                   cli_session_actions[s].filter_in,
+                                   cli_session_actions[s].filter_out);
+
+                               filter_session(s, cli_session_actions[s].filter_in, cli_session_actions[s].filter_out);
+                               send++;
+                       }
+
                        if (send)
                                cluster_send_session(s);
 
@@ -2149,7 +2215,7 @@ int regular_cleanups(void)
 // Are we in the middle of a tunnel update, or radius
 // requests??
 //
-int still_busy(void)
+static int still_busy(void)
 {
        int i;
        static clockt last_talked = 0;
@@ -2199,7 +2265,7 @@ static fd_set readset;
 static int readset_n = 0;
 
 // main loop - gets packets on tun or udp and processes them
-void mainloop(void)
+static void mainloop(void)
 {
        int i;
        u8 buf[65536];
@@ -2317,7 +2383,7 @@ void mainloop(void)
                        }
 
                        if (FD_ISSET(controlfd, &r))
-                               processcontrol(buf, recvfrom(controlfd, buf, sizeof(buf), MSG_WAITALL, (void *) &addr, &alen), &addr);
+                               processcontrol(buf, recvfrom(controlfd, buf, sizeof(buf), MSG_WAITALL, (void *) &addr, &alen), &addr, alen);
 
                        if (FD_ISSET(clifd, &r))
                        {
@@ -2474,7 +2540,7 @@ static void stripdomain(char *host)
 }
 
 // Init data structures
-void initdata(int optdebug, char *optconfig)
+static void initdata(int optdebug, char *optconfig)
 {
        int i;
 
@@ -2483,12 +2549,12 @@ void initdata(int optdebug, char *optconfig)
                LOG(0, 0, 0, 0, "Error doing malloc for _statistics: %s\n", strerror(errno));
                exit(1);
        }
-       if (!(config = shared_malloc(sizeof(struct configt))))
+       if (!(config = shared_malloc(sizeof(configt))))
        {
                LOG(0, 0, 0, 0, "Error doing malloc for configuration: %s\n", strerror(errno));
                exit(1);
        }
-       memset(config, 0, sizeof(struct configt));
+       memset(config, 0, sizeof(configt));
        time(&config->start_time);
        strncpy(config->config_file, optconfig, strlen(optconfig));
        config->debug = optdebug;
@@ -2524,6 +2590,13 @@ void initdata(int optdebug, char *optconfig)
                exit(1);
        }
 
+if (!(ip_filters = shared_malloc(sizeof(ip_filtert) * MAXFILTER)))
+{
+       LOG(0, 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))))
        {
@@ -2575,15 +2648,15 @@ void initdata(int optdebug, char *optconfig)
        _statistics->start_time = _statistics->last_reset = time(NULL);
 
 #ifdef BGP
-       if (!(bgp_peers = shared_malloc(sizeof(struct bgp_peer) * BGP_NUM_PEERS)))
-       {
-               LOG(0, 0, 0, 0, "Error doing malloc for bgp: %s\n", strerror(errno));
-               exit(1);
-       }
+       if (!(bgp_peers = shared_malloc(sizeof(struct bgp_peer) * BGP_NUM_PEERS)))
+       {
+               LOG(0, 0, 0, 0, "Error doing malloc for bgp: %s\n", strerror(errno));
+               exit(1);
+       }
 #endif /* BGP */
 }
 
-int assign_ip_address(sessionidt s)
+static int assign_ip_address(sessionidt s)
 {
        u32 i;
        int best = -1;
@@ -2640,10 +2713,13 @@ int assign_ip_address(sessionidt s)
        return 1;
 }
 
-void free_ip_address(sessionidt s)
+static void free_ip_address(sessionidt s)
 {
        int i = session[s].ip_pool_index;
 
+
+       CSTAT(call_free_ip_address);
+
        if (!session[s].ip)
                return; // what the?
 
@@ -2656,10 +2732,6 @@ void free_ip_address(sessionidt s)
        ip_address_pool[i].assigned = 0;
        ip_address_pool[i].session = 0;
        ip_address_pool[i].last = time_now;
-
-
-       CSTAT(call_free_ip_address);
-
 }
 
 //
@@ -2726,7 +2798,7 @@ void rebuild_address_pool(void)
 //
 // Fix the address pool to match a changed session.
 // (usually when the master sends us an update).
-void fix_address_pool(int sid)
+static void fix_address_pool(int sid)
 {
        int ipid;
 
@@ -2747,7 +2819,7 @@ void fix_address_pool(int sid)
 //
 // Add a block of addresses to the IP pool to hand out.
 //
-void add_to_ip_pool(u32 addr, u32 mask)
+static void add_to_ip_pool(u32 addr, u32 mask)
 {
        int i;
        if (mask == 0)
@@ -2775,7 +2847,7 @@ void add_to_ip_pool(u32 addr, u32 mask)
 }
 
 // Initialize the IP address pool
-void initippool()
+static void initippool()
 {
        FILE *f;
        char *p;
@@ -2861,7 +2933,7 @@ void snoop_send_packet(char *packet, u16 size, ipt destination, u16 port)
        STAT(packets_snooped);
 }
 
-void dump_acct_info()
+static void dump_acct_info()
 {
        char filename[1024];
        char timestr[64];
@@ -2921,8 +2993,6 @@ int main(int argc, char *argv[])
        int optdebug = 0;
        char *optconfig = CONFIGFILE;
 
-       _program_name = strdup(argv[0]);
-
        time(&basetime);             // start clock
 
        // scan args
@@ -2930,32 +3000,31 @@ int main(int argc, char *argv[])
        {
                switch (i)
                {
-                       case 'd':
-                               if (fork()) exit(0);
-                               setsid();
-                               freopen("/dev/null", "r", stdin);
-                               freopen("/dev/null", "w", stdout);
-                               freopen("/dev/null", "w", stderr);
-                               break;
-                       case 'v':
-                               optdebug++;
-                               break;
-                       case 'c':
-                               optconfig = optarg;
-                               break;
-                       case 'h':
-                               snprintf(hostname, sizeof(hostname), "%s", optarg);
-                               break;
-                       default:
-                               printf("Args are:\n"
-                                      "\t-d\tDetach from terminal\n"
-                                      "\t-c <file>\tConfig file\n"
-                                      "\t-h <hostname>\tForce hostname\n"
-                                      "\t-a <address>\tUse specific address\n"
-                                      "\t-v\t\tDebug\n");
-
-                               return (0);
-                               break;
+               case 'd':
+                       if (fork()) exit(0);
+                       setsid();
+                       freopen("/dev/null", "r", stdin);
+                       freopen("/dev/null", "w", stdout);
+                       freopen("/dev/null", "w", stderr);
+                       break;
+               case 'v':
+                       optdebug++;
+                       break;
+               case 'c':
+                       optconfig = optarg;
+                       break;
+               case 'h':
+                       snprintf(hostname, sizeof(hostname), "%s", optarg);
+                       break;
+               default:
+                       printf("Args are:\n"
+                              "\t-d\t\tDetach from terminal\n"
+                              "\t-c <file>\tConfig file\n"
+                              "\t-h <hostname>\tForce hostname\n"
+                              "\t-v\t\tDebug\n");
+
+                       return (0);
+                       break;
                }
        }
 
@@ -3012,7 +3081,7 @@ int main(int argc, char *argv[])
        }
 
        /* Set up the cluster communications port. */
-       if (cluster_init(config->bind_address) < 0)
+       if (cluster_init() < 0)
                exit(1);
 
 #ifdef BGP
@@ -3081,7 +3150,7 @@ int main(int argc, char *argv[])
        return 0;
 }
 
-void sighup_handler(int junk)
+static void sighup_handler(int sig)
 {
        if (log_stream && log_stream != stderr)
        {
@@ -3092,7 +3161,7 @@ void sighup_handler(int junk)
        read_config_file();
 }
 
-void sigalrm_handler(int junk)
+static void sigalrm_handler(int sig)
 {
        // Log current traffic stats
 
@@ -3125,7 +3194,7 @@ void sigalrm_handler(int junk)
 
 }
 
-void sigterm_handler(int junk)
+static void sigterm_handler(int sig)
 {
        LOG(1, 0, 0, 0, "Shutting down cleanly\n");
        if (config->save_state)
@@ -3134,7 +3203,7 @@ void sigterm_handler(int junk)
        main_quit++;
 }
 
-void sigquit_handler(int junk)
+static void sigquit_handler(int sig)
 {
        int i;
 
@@ -3153,13 +3222,13 @@ void sigquit_handler(int junk)
        main_quit++;
 }
 
-void sigchild_handler(int signal)
+static void sigchild_handler(int sig)
 {
        while (waitpid(-1, NULL, WNOHANG) > 0)
            ;
 }
 
-void read_state()
+static void read_state()
 {
        struct stat sb;
        int i;
@@ -3284,7 +3353,7 @@ void read_state()
        LOG(0, 0, 0, 0, "Loaded saved state information\n");
 }
 
-void dump_state()
+static void dump_state()
 {
        FILE *f;
        u32 buf[2];
@@ -3335,7 +3404,7 @@ void dump_state()
        unlink(STATEFILE);
 }
 
-void build_chap_response(char *challenge, u8 id, u16 challenge_length, char **challenge_response)
+static void build_chap_response(char *challenge, u8 id, u16 challenge_length, char **challenge_response)
 {
        MD5_CTX ctx;
        *challenge_response = NULL;
@@ -3370,7 +3439,7 @@ static int facility_value(char *name)
        return 0;
 }
 
-void update_config()
+static void update_config()
 {
        int i;
        static int timeout = 0;
@@ -3511,7 +3580,7 @@ void update_config()
        config->reload_config = 0;
 }
 
-void read_config_file()
+static void read_config_file()
 {
        FILE *f;
 
@@ -3573,18 +3642,30 @@ int sessionsetup(tunnelidt t, sessionidt s)
                }
        }
 
+       {
+               int routed = 0;
+
                // Add the route for this session.
-               //
-               // Static IPs need to be routed. Anything else
-               // is part of the IP address pool and is already routed,
-               // it just needs to be added to the IP cache.
-       if (session[s].ip_pool_index == -1) // static ip
-               routeset(s, session[s].ip, 0, 0, 1);
-       else
-               cache_ipmap(session[s].ip, s);
+               for (r = 0; r < MAXROUTE && session[s].route[r].ip; r++)
+               {
+                       if ((session[s].ip & session[s].route[r].mask) ==
+                           (session[s].route[r].ip & session[s].route[r].mask))
+                               routed++;
+
+                       routeset(s, session[s].route[r].ip, session[s].route[r].mask, 0, 1);
+               }
 
-       for (r = 0; r < MAXROUTE && session[s].route[r].ip; r++)
-               routeset(s, session[s].route[r].ip, session[s].route[r].mask, session[s].ip, 1);
+               // Static IPs need to be routed if not already
+               // convered by a Framed-Route.  Anything else is part
+               // of the IP address pool and is already routed, it
+               // just needs to be added to the IP cache.
+               if (session[s].ip_pool_index == -1) // static ip
+               {
+                       if (!routed) routeset(s, session[s].ip, 0, 0, 1);
+               }
+               else
+                       cache_ipmap(session[s].ip, s);
+       }
 
        if (!session[s].unique_id)
        {
@@ -3628,6 +3709,7 @@ int sessionsetup(tunnelidt t, sessionidt s)
 int load_session(sessionidt s, sessiont *new)
 {
        int i;
+       int newip = 0;
 
                // Sanity checks.
        if (new->ip_pool_index >= MAXIPPOOL ||
@@ -3645,46 +3727,89 @@ int load_session(sessionidt s, sessiont *new)
 
        session[s].tunnel = new->tunnel; // For logging in cache_ipmap
 
+       // See if routes/ip cache need updating
+       if (new->ip != session[s].ip)
+               newip++;
+
+       for (i = 0; !newip && i < MAXROUTE && (session[s].route[i].ip || new->route[i].ip); i++)
+               if (new->route[i].ip != session[s].route[i].ip ||
+                   new->route[i].mask != session[s].route[i].mask)
+                       newip++;
 
-       if (new->ip != session[s].ip)   // Changed ip. fix up hash tables.
+       // needs update
+       if (newip)
        {
-               if (session[s].ip)      // If there's an old one, remove it.
+               int routed = 0;
+
+               // remove old routes...
+               for (i = 0; i < MAXROUTE && session[s].route[i].ip; i++)
                {
-                       // Remove any routes if the IP has changed
-                       for (i = 0; i < MAXROUTE && session[s].route[i].ip; i++)
-                       {
-                               routeset(s, session[s].route[i].ip, session[s].route[i].mask, session[s].ip, 0);
-                               session[s].route[i].ip = 0;
-                       }
+                       if ((session[s].ip & session[s].route[i].mask) ==
+                           (session[s].route[i].ip & session[s].route[i].mask))
+                               routed++;
+
+                       routeset(s, session[s].route[i].ip, session[s].route[i].mask, 0, 0);
+               }
 
+               // ...ip
+               if (session[s].ip)
+               {
                        if (session[s].ip_pool_index == -1) // static IP
-                               routeset(s, session[s].ip, 0, 0, 0);
+                       {
+                               if (!routed) routeset(s, session[s].ip, 0, 0, 0);
+                       }
                        else            // It's part of the IP pool, remove it manually.
                                uncache_ipmap(session[s].ip);
                }
 
+               routed = 0;
+
+               // add new routes...
+               for (i = 0; i < MAXROUTE && new->route[i].ip; i++)
+               {
+                       if ((new->ip & new->route[i].mask) ==
+                           (new->route[i].ip & new->route[i].mask))
+                               routed++;
+
+                       routeset(s, new->route[i].ip, new->route[i].mask, 0, 1);
+               }
+
+               // ...ip
                if (new->ip)
                {
                        // If there's a new one, add it.
                        if (new->ip_pool_index == -1)
-                               routeset(s, new->ip, 0, 0, 1);
+                       {
+                               if (!routed) routeset(s, new->ip, 0, 0, 1);
+                       }
                        else
                                cache_ipmap(new->ip, s);
                }
        }
 
-       // Update routed networks
-       for (i = 0; i < MAXROUTE && (session[s].route[i].ip || new->route[i].ip); i++)
+       // check filters
+       if (new->filter_in && (new->filter_in > MAXFILTER || !ip_filters[new->filter_in - 1].name[0]))
        {
-               if (new->route[i].ip == session[s].route[i].ip &&
-                   new->route[i].mask == session[s].route[i].mask)
-                       continue;
+               LOG(2, session[s].ip, s, session[s].tunnel, "Dropping invalid input filter %d\n", (int) new->filter_in);
+               new->filter_in = 0;
+       }
+
+       if (new->filter_out && (new->filter_out > MAXFILTER || !ip_filters[new->filter_out - 1].name[0]))
+       {
+               LOG(2, session[s].ip, s, session[s].tunnel, "Dropping invalid output filter %d\n", (int) new->filter_out);
+               new->filter_out = 0;
+       }
 
-               if (session[s].route[i].ip) // Remove the old one if it exists.
-                       routeset(s, session[s].route[i].ip, session[s].route[i].mask, session[s].ip, 0);
+       if (new->filter_in != session[s].filter_in)
+       {
+               if (session[s].filter_in) ip_filters[session[s].filter_in - 1].used--;
+               if (new->filter_in)       ip_filters[new->filter_in - 1].used++;
+       }
 
-               if (new->route[i].ip)   // Add the new one if it exists.
-                       routeset(s, new->route[i].ip, new->route[i].mask, new->ip, 1);
+       if (new->filter_out != session[s].filter_out)
+       {
+               if (session[s].filter_out) ip_filters[session[s].filter_out - 1].used--;
+               if (new->filter_out)       ip_filters[new->filter_out - 1].used++;
        }
 
        if (new->tunnel && s > config->cluster_highest_sessionid)       // Maintain this in the slave. It's used
@@ -3706,22 +3831,7 @@ int load_session(sessionidt s, sessiont *new)
        return 1;
 }
 
-#ifdef RINGBUFFER
-void ringbuffer_dump(FILE *stream)
-{
-       int i = ringbuffer->head;
-
-       while (i != ringbuffer->tail)
-       {
-               if (*ringbuffer->buffer[i].message)
-                       fprintf(stream, "%d-%s", ringbuffer->buffer[i].level, ringbuffer->buffer[i].message);
-               if (++i == ringbuffer->tail) break;
-               if (i == RINGBUFFER_SIZE) i = 0;
-       }
-}
-#endif
-
-void initplugins()
+static void initplugins()
 {
        int i;
 
@@ -3763,7 +3873,7 @@ static void *getconfig(char *key, enum config_typet type)
        return 0;
 }
 
-void add_plugin(char *plugin_name)
+static int add_plugin(char *plugin_name)
 {
        static struct pluginfuncs funcs = {
                _log,
@@ -3772,10 +3882,12 @@ void add_plugin(char *plugin_name)
                sessionbyuser,
                sessiontbysessionidt,
                sessionidtbysessiont,
-               sessionkill,
                radiusnew,
                radiussend,
                getconfig,
+               sessionkill,
+               throttle_session,
+               cluster_send_session,
        };
 
        void *p = open_plugin(plugin_name, 1);
@@ -3785,22 +3897,22 @@ void add_plugin(char *plugin_name)
        if (!p)
        {
                LOG(1, 0, 0, 0, "   Plugin load failed: %s\n", dlerror());
-               return;
+               return -1;
        }
 
        if (ll_contains(loaded_plugins, p))
        {
                dlclose(p);
-               return;
+               return 0; // already loaded
        }
 
        {
-               int *v = dlsym(p, "__plugin_api_version");
+               int *v = dlsym(p, "plugin_api_version");
                if (!v || *v != PLUGIN_API_VERSION)
                {
                        LOG(1, 0, 0, 0, "   Plugin load failed: API version mismatch: %s\n", dlerror());
                        dlclose(p);
-                       return;
+                       return -1;
                }
        }
 
@@ -3810,7 +3922,7 @@ void add_plugin(char *plugin_name)
                {
                        LOG(1, 0, 0, 0, "   Plugin load failed: plugin_init() returned FALSE: %s\n", dlerror());
                        dlclose(p);
-                       return;
+                       return -1;
                }
        }
 
@@ -3827,6 +3939,7 @@ void add_plugin(char *plugin_name)
        }
 
        LOG(2, 0, 0, 0, "   Loaded plugin %s\n", plugin_name);
+       return 1;
 }
 
 static void run_plugin_done(void *plugin)
@@ -3837,48 +3950,54 @@ static void run_plugin_done(void *plugin)
                donefunc();
 }
 
-void remove_plugin(char *plugin_name)
+static int remove_plugin(char *plugin_name)
 {
        void *p = open_plugin(plugin_name, 0);
-       int i;
+       int loaded = 0;
 
        if (!p)
-               return;
-
-       for (i = 0; i < max_plugin_functions; i++)
-       {
-               void *x;
-               if (plugin_functions[i] && (x = dlsym(p, plugin_functions[i])))
-                       ll_delete(plugins[i], x);
-       }
+               return -1;
 
        if (ll_contains(loaded_plugins, p))
        {
+               int i;
+               for (i = 0; i < max_plugin_functions; i++)
+               {
+                       void *x;
+                       if (plugin_functions[i] && (x = dlsym(p, plugin_functions[i])))
+                               ll_delete(plugins[i], x);
+               }
+
                ll_delete(loaded_plugins, p);
                run_plugin_done(p);
+               loaded = 1;
        }
 
        dlclose(p);
        LOG(2, 0, 0, 0, "Removed plugin %s\n", plugin_name);
+       return loaded;
 }
 
 int run_plugins(int plugin_type, void *data)
 {
        int (*func)(void *data);
-       if (!plugins[plugin_type] || plugin_type > max_plugin_functions) return 1;
+
+       if (!plugins[plugin_type] || plugin_type > max_plugin_functions)
+               return PLUGIN_RET_ERROR;
 
        ll_reset(plugins[plugin_type]);
        while ((func = ll_next(plugins[plugin_type])))
        {
-               int rc;
-               rc = func(data);
-               if (rc == PLUGIN_RET_STOP) return 1;
-               if (rc == PLUGIN_RET_ERROR) return 0;
+               int r = func(data);
+
+               if (r != PLUGIN_RET_OK)
+                       return r; // stop here
        }
-       return 1;
+
+       return PLUGIN_RET_OK;
 }
 
-void plugins_done()
+static void plugins_done()
 {
        void *p;
 
@@ -3887,49 +4006,177 @@ void plugins_done()
                run_plugin_done(p);
 }
 
-void processcontrol(u8 * buf, int len, struct sockaddr_in *addr)
+static void processcontrol(u8 * buf, int len, struct sockaddr_in *addr, int alen)
 {
-       char *resp;
-       int l;
-       struct param_control param = { buf, len, ntohl(addr->sin_addr.s_addr), ntohs(addr->sin_port), NULL, 0, 0 };
-
+       struct nsctl request;
+       struct nsctl response;
+       int type = unpack_control(&request, buf, len);
+       int r;
+       void *p;
 
        if (log_stream && config->debug >= 4)
        {
-               LOG(4, ntohl(addr->sin_addr.s_addr), 0, 0, "Received ");
-               dump_packet(buf, log_stream);
+               if (type < 0)
+               {
+                       LOG(4, ntohl(addr->sin_addr.s_addr), 0, 0, "Bogus control message (%d)\n", type);
+               }
+               else
+               {
+                       LOG(4, ntohl(addr->sin_addr.s_addr), 0, 0, "Received ");
+                       dump_control(&request, log_stream);
+               }
        }
 
-       resp = calloc(1400, 1);
-       l = new_packet(PKT_RESP_ERROR, resp);
-       *(int *)(resp + 6) = *(int *)(buf + 6);
+       switch (type)
+       {
+       case NSCTL_REQ_LOAD:
+               if (request.argc != 1)
+               {
+                       response.type = NSCTL_RES_ERR;
+                       response.argc = 1;
+                       response.argv[0] = "name of plugin required";
+               }
+               else if ((r = add_plugin(request.argv[0])) < 1)
+               {
+                       response.type = NSCTL_RES_ERR;
+                       response.argc = 1;
+                       response.argv[0] = !r
+                               ? "plugin already loaded"
+                               : "error loading plugin";
+               }
+               else
+               {
+                       response.type = NSCTL_RES_OK;
+                       response.argc = 0;
+               }
+
+               break;
+
+       case NSCTL_REQ_UNLOAD:
+               if (request.argc != 1)
+               {
+                       response.type = NSCTL_RES_ERR;
+                       response.argc = 1;
+                       response.argv[0] = "name of plugin required";
+               }
+               else if ((r = remove_plugin(request.argv[0])) < 1)
+               {
+                       response.type = NSCTL_RES_ERR;
+                       response.argc = 1;
+                       response.argv[0] = !r
+                               ? "plugin not loaded"
+                               : "plugin not found";
+               }
+               else
+               {
+                       response.type = NSCTL_RES_OK;
+                       response.argc = 0;
+               }
+
+               break;
+
+       case NSCTL_REQ_HELP:
+               response.type = NSCTL_RES_OK;
+               response.argc = 0;
 
-       param.type = ntohs(*(short *)(buf + 2));
-       param.id = ntohl(*(int *)(buf + 6));
-       param.data_length = ntohs(*(short *)(buf + 4)) - 10;
-       param.data = (param.data_length > 0) ? (char *)(buf + 10) : NULL;
-       param.response = resp;
-       param.response_length = l;
+               ll_reset(loaded_plugins);
+               while ((p = ll_next(loaded_plugins)))
+               {
+                       char **help = dlsym(p, "plugin_control_help");
+                       while (response.argc < 0xff && help && *help)
+                               response.argv[response.argc++] = *help++;
+               }
 
-       run_plugins(PLUGIN_CONTROL, &param);
+               break;
+
+       case NSCTL_REQ_CONTROL:
+               {
+                       struct param_control param = {
+                               config->cluster_iam_master,
+                               request.argc,
+                               request.argv,
+                               0,
+                               NULL,
+                       };
+
+                       int r = run_plugins(PLUGIN_CONTROL, &param);
+
+                       if (r == PLUGIN_RET_ERROR)
+                       {
+                               response.type = NSCTL_RES_ERR;
+                               response.argc = 1;
+                               response.argv[0] = param.additional
+                                       ? param.additional
+                                       : "error returned by plugin";
+                       }
+                       else if (r == PLUGIN_RET_NOTMASTER)
+                       {
+                               static char msg[] = "must be run on master: 000.000.000.000";
 
-       if (param.send_response)
+                               response.type = NSCTL_RES_ERR;
+                               response.argc = 1;
+                               if (config->cluster_master_address)
+                               {
+                                       strcpy(msg + 23, inet_toa(config->cluster_master_address));
+                                       response.argv[0] = msg;
+                               }
+                               else
+                               {
+                                       response.argv[0] = "must be run on master: none elected";
+                               }
+                       }
+                       else if (!(param.response & NSCTL_RESPONSE))
+                       {
+                               response.type = NSCTL_RES_ERR;
+                               response.argc = 1;
+                               response.argv[0] = param.response
+                                       ? "unrecognised response value from plugin"
+                                       : "unhandled action";
+                       }
+                       else
+                       {
+                               response.type = param.response;
+                               response.argc = 0;
+                               if (param.additional)
+                               {
+                                       response.argc = 1;
+                                       response.argv[0] = param.additional;
+                               }
+                       }
+               }
+
+               break;
+
+       default:
+               response.type = NSCTL_RES_ERR;
+               response.argc = 1;
+               response.argv[0] = "error unpacking control packet";
+       }
+
+       buf = calloc(NSCTL_MAX_PKT_SZ, 1);
+       if (!buf)
        {
-               send_packet(controlfd, ntohl(addr->sin_addr.s_addr), ntohs(addr->sin_port), param.response, param.response_length);
-               LOG(4, ntohl(addr->sin_addr.s_addr), 0, 0, "Sent Control packet response\n");
+               LOG(2, ntohl(addr->sin_addr.s_addr), 0, 0, "Failed to allocate nsctl response\n");
+               return;
        }
 
-       free(resp);
-}
+       r = pack_control(buf, NSCTL_MAX_PKT_SZ, response.type, response.argc, response.argv);
+       if (r > 0)
+       {
+               sendto(controlfd, buf, r, 0, (const struct sockaddr *) addr, alen);
+               if (log_stream && config->debug >= 4)
+               {
+                       LOG(4, ntohl(addr->sin_addr.s_addr), 0, 0, "Sent ");
+                       dump_control(&response, log_stream);
+               }
+       }
+       else
+               LOG(2, ntohl(addr->sin_addr.s_addr), 0, 0, "Failed to pack nsctl response (%d)\n", r);
 
-void tunnelclear(tunnelidt t)
-{
-       if (!t) return;
-       memset(&tunnel[t], 0, sizeof(tunnel[t]));
-       tunnel[t].state = TUNNELFREE;
+       free(buf);
 }
 
-tunnelidt new_tunnel()
+static tunnelidt new_tunnel()
 {
        tunnelidt i;
        for (i = 1; i < MAXTUNNEL; i++)
@@ -3976,7 +4223,6 @@ void become_master(void)
        // add radius fds
        for (i = 0; i < config->num_radfds; i++)
        {
-               if (!radfds[i]) continue;
                FD_SET(radfds[i], &readset);
                if (radfds[i] > readset_n)
                        readset_n = radfds[i];
@@ -4070,7 +4316,7 @@ int cmd_show_hist_open(struct cli_def *cli, char *command, char **argv, int argc
  *
  * Based on code from rp-l2tpd by Roaring Penguin Software Inc.
  */
-int unhide_avp(u8 *avp, tunnelidt t, sessionidt s, u16 length)
+static int unhide_avp(u8 *avp, tunnelidt t, sessionidt s, u16 length)
 {
        MD5_CTX ctx;
        u8 *cursor;