// 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.107 2005/06/02 11:32:30 bodea Exp $";
+char const *cvs_id_l2tpns = "$Id: l2tpns.c,v 1.111 2005/06/14 04:47:24 bodea Exp $";
#include <arpa/inet.h>
#include <assert.h>
#ifdef BGP
#include "bgp.h"
-#endif /* BGP */
+#endif
// Globals
configt *config = NULL; // all configuration
int *radfds = NULL; // RADIUS requests file handles
int ifrfd = -1; // File descriptor for routing, etc
int ifr6fd = -1; // File descriptor for IPv6 routing, etc
-static int rand_fd = -1; // Random data source
+int rand_fd = -1; // Random data source
+int cluster_sockfd = -1; // Intra-cluster communications socket.
+int epollfd = -1; // event polling
time_t basetime = 0; // base clock
char hostname[1000] = ""; // us.
static int tunidx; // ifr_ifindex of tun device
static int syslog_log = 0; // are we logging to syslog
static FILE *log_stream = 0; // file handle for direct logging (i.e. direct into file, not via syslog).
-extern int cluster_sockfd; // Intra-cluster communications socket.
uint32_t last_id = 0; // Unique ID for radius accounting
struct cli_session_actions *cli_session_actions = NULL; // Pending session changes requested by CLI
static void dump_acct_info(int all);
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 shutdown_handler(int sig);
static void sigchild_handler(int sig);
static void build_chap_response(char *challenge, uint8_t id, uint16_t challenge_length, char **challenge_response);
static void update_config(void);
// on slaves, alow BGP to withdraw cleanly before exiting
#define QUIT_DELAY 5
+// quit actions (master)
+#define QUIT_FAILOVER 1 // SIGTERM: exit when all control messages have been acked (for cluster failover)
+#define QUIT_SHUTDOWN 2 // SIGQUIT: shutdown sessions/tunnels, reject new connections
+
// return internal time (10ths since process startup), set f if given
static clockt now(double *f)
{
return;
// close previous source, if any
- if (rand_fd >= 0) close(rand_fd);
+ if (rand_fd >= 0)
+ close(rand_fd);
rand_fd = -1;
path, strerror(errno));
}
}
-
- // no source: seed prng
- {
- unsigned seed = time_now ^ getpid();
- LOG(4, 0, 0, "Seeding the pseudo random generator: %u\n", seed);
- srand(seed);
- }
}
// fill buffer with random data
strerror(errno));
// fall back to rand()
- initrandom(0);
+ initrandom(NULL);
}
n = 0;
if (rate++ < config->icmp_rate) // Only send a max of icmp_rate per second.
{
LOG(4, 0, 0, "IP: Sending ICMP host unreachable to %s\n", fmtaddr(*(in_addr_t *)(buf + 12), 0));
- host_unreachable(*(in_addr_t *)(buf + 12), *(uint16_t *)(buf + 4), ip, buf, (len < 64) ? 64 : len);
+ host_unreachable(*(in_addr_t *)(buf + 12), *(uint16_t *)(buf + 4),
+ config->bind_address ? config->bind_address : my_address, buf, len);
}
return;
}
// close session
for (s = 1; s <= config->cluster_highest_sessionid ; ++s)
if (session[s].tunnel == t)
- sessionshutdown(s, reason, 3, 0);
+ sessionshutdown(s, reason, 0, 0);
tunnel[t].state = TUNNELDIE;
tunnel[t].die = TIME + 700; // Clean up in 70 seconds
continue;
}
- LOG(4, s, t, "Hidden AVP\n");
-
// Unhide the AVP
unhide_value(b, n, mtype, session[s].random_vector, session[s].random_vector_length);
n = orig_len;
}
- LOG(4, s, t, " AVP %d (%s) len %d\n", mtype, avp_name(mtype), n);
+ LOG(4, s, t, " AVP %d (%s) len %d%s%s\n", mtype, avp_name(mtype), n,
+ flags & 0x40 ? ", hidden" : "", flags & 0x80 ? ", mandatory" : "");
+
switch (mtype)
{
case 0: // message type
case 36: // Random Vector
LOG(4, s, t, " Random Vector received. Enabled AVP Hiding.\n");
memset(session[s].random_vector, 0, sizeof(session[s].random_vector));
+ if (n > sizeof(session[s].random_vector))
+ n = sizeof(session[s].random_vector);
memcpy(session[s].random_vector, b, n);
session[s].random_vector_length = n;
break;
switch (message)
{
case 1: // SCCRQ - Start Control Connection Request
+ tunnel[t].state = TUNNELOPENING;
+ if (main_quit != QUIT_SHUTDOWN)
{
controlt *c = controlnew(2); // sending SCCRP
control16(c, 2, version, 1); // protocol version
control16(c, 9, t, 1); // assigned tunnel
controladd(c, t, 0); // send the resply
}
- tunnel[t].state = TUNNELOPENING;
+ else
+ {
+ tunnelshutdown(t, "Shutting down", 6, 0, 0);
+ }
break;
case 2: // SCCRP
tunnel[t].state = TUNNELOPEN;
// TBA
break;
case 10: // ICRQ
- if (sessionfree)
+ if (sessionfree && main_quit != QUIT_SHUTDOWN)
{
uint16_t r;
{
controlt *c = controlnew(14); // CDN
- control16(c, 1, 4, 1); // temporary lack of resources
- controladd(c, session[s].tunnel, asession); // send the message
+ if (main_quit == QUIT_SHUTDOWN)
+ control16(c, 1, 2, 7); // try another
+ else
+ control16(c, 1, 4, 0); // temporary lack of resources
+
+ controladd(c, t, asession); // send the message
}
return;
case 11: // ICRP
static int still_busy(void)
{
int i;
- static time_t stopped_bgp = 0;
static clockt last_talked = 0;
static clockt start_busy_wait = 0;
if (!config->cluster_iam_master)
{
#ifdef BGP
+ static time_t stopped_bgp = 0;
if (bgp_configured)
{
if (!stopped_bgp)
return 0;
}
+ if (main_quit == QUIT_SHUTDOWN)
+ {
+ static int dropped = 0;
+ if (!dropped)
+ {
+ int i;
+
+ LOG(1, 0, 0, "Dropping sessions and tunnels\n");
+ for (i = 1; i < MAXTUNNEL; i++)
+ if (tunnel[i].ip || tunnel[i].state)
+ tunnelshutdown(i, "L2TPNS Closing", 6, 0, 0);
+
+ dropped = 1;
+ }
+ }
+
if (start_busy_wait == 0)
start_busy_wait = TIME;
return 0;
}
-static fd_set readset;
-static int readset_n = 0;
+#ifdef HAVE_EPOLL
+# include <sys/epoll.h>
+#else
+# define FAKE_EPOLL_IMPLEMENTATION /* include the functions */
+# include "fake_epoll.h"
+#endif
+
+// the base set of fds polled: control, cli, udp, tun, cluster
+#define BASE_FDS 5
+
+// additional polled fds
+#ifdef BGP
+# define EXTRA_FDS BGP_NUM_PEERS
+#else
+# define EXTRA_FDS 0
+#endif
// main loop - gets packets on tun or udp and processes them
static void mainloop(void)
{
int i;
uint8_t buf[65536];
- struct timeval to;
clockt next_cluster_ping = 0; // send initial ping immediately
+ struct epoll_event events[BASE_FDS + RADIUS_FDS + EXTRA_FDS];
+ int maxevent = sizeof(events)/sizeof(*events);
+
+ if ((epollfd = epoll_create(maxevent)) < 0)
+ {
+ LOG(0, 0, 0, "epoll_create failed: %s\n", strerror(errno));
+ exit(1);
+ }
LOG(4, 0, 0, "Beginning of main loop. udpfd=%d, tunfd=%d, cluster_sockfd=%d, controlfd=%d\n",
udpfd, tunfd, cluster_sockfd, controlfd);
- FD_ZERO(&readset);
- FD_SET(udpfd, &readset);
- FD_SET(tunfd, &readset);
- FD_SET(controlfd, &readset);
- FD_SET(clifd, &readset);
- if (cluster_sockfd) FD_SET(cluster_sockfd, &readset);
- readset_n = udpfd;
- if (tunfd > readset_n) readset_n = tunfd;
- if (controlfd > readset_n) readset_n = controlfd;
- if (clifd > readset_n) readset_n = clifd;
- if (cluster_sockfd > readset_n) readset_n = cluster_sockfd;
-
- while (!main_quit || still_busy())
+ /* setup our fds to poll for input */
{
- fd_set r;
- int n = readset_n;
+ static struct event_data d[BASE_FDS];
+ struct epoll_event e;
+
+ e.events = EPOLLIN;
+ i = 0;
+
+ d[i].type = FD_TYPE_CONTROL;
+ e.data.ptr = &d[i++];
+ epoll_ctl(epollfd, EPOLL_CTL_ADD, controlfd, &e);
+
+ d[i].type = FD_TYPE_CLI;
+ e.data.ptr = &d[i++];
+ epoll_ctl(epollfd, EPOLL_CTL_ADD, clifd, &e);
+
+ d[i].type = FD_TYPE_UDP;
+ e.data.ptr = &d[i++];
+ epoll_ctl(epollfd, EPOLL_CTL_ADD, udpfd, &e);
+
+ d[i].type = FD_TYPE_TUN;
+ e.data.ptr = &d[i++];
+ epoll_ctl(epollfd, EPOLL_CTL_ADD, tunfd, &e);
+
+ d[i].type = FD_TYPE_CLUSTER;
+ e.data.ptr = &d[i++];
+ epoll_ctl(epollfd, EPOLL_CTL_ADD, cluster_sockfd, &e);
+ }
+
#ifdef BGP
- fd_set w;
- int bgp_set[BGP_NUM_PEERS];
+ signal(SIGPIPE, SIG_IGN);
+ bgp_setup(config->as_number);
+ if (config->bind_address)
+ bgp_add_route(config->bind_address, 0xffffffff);
+
+ for (i = 0; i < BGP_NUM_PEERS; i++)
+ {
+ if (config->neighbour[i].name[0])
+ bgp_start(&bgp_peers[i], config->neighbour[i].name,
+ config->neighbour[i].as, config->neighbour[i].keepalive,
+ config->neighbour[i].hold, 0); /* 0 = routing disabled */
+ }
#endif /* BGP */
+
+ while (!main_quit || still_busy())
+ {
int more = 0;
+ int n;
if (config->reload_config)
{
update_config();
}
- memcpy(&r, &readset, sizeof(fd_set));
- to.tv_sec = 0;
- to.tv_usec = 100000; // 1/10th of a second.
-
#ifdef BGP
- FD_ZERO(&w);
- for (i = 0; i < BGP_NUM_PEERS; i++)
- {
- bgp_set[i] = bgp_select_state(&bgp_peers[i]);
- if (bgp_set[i] & 1)
- {
- FD_SET(bgp_peers[i].sock, &r);
- if (bgp_peers[i].sock > n)
- n = bgp_peers[i].sock;
- }
-
- if (bgp_set[i] & 2)
- {
- FD_SET(bgp_peers[i].sock, &w);
- if (bgp_peers[i].sock > n)
- n = bgp_peers[i].sock;
- }
- }
-
- n = select(n + 1, &r, &w, 0, &to);
-#else /* BGP */
- n = select(n + 1, &r, 0, 0, &to);
+ bgp_set_poll();
#endif /* BGP */
+ n = epoll_wait(epollfd, events, maxevent, 100); // timeout 100ms (1/10th sec)
STAT(select_called);
TIME = now(NULL);
continue;
LOG(0, 0, 0, "Error returned from select(): %s\n", strerror(errno));
- main_quit++;
- break;
+ break; // exit
}
- else if (n)
+
+ if (n)
{
struct sockaddr_in addr;
int alen, c, s;
+ int udp_ready = 0;
+ int tun_ready = 0;
+ int cluster_ready = 0;
int udp_pkts = 0;
int tun_pkts = 0;
int cluster_pkts = 0;
+#ifdef BGP
+ uint32_t bgp_events[BGP_NUM_PEERS];
+ memset(bgp_events, 0, sizeof(bgp_events));
+#endif /* BGP */
- // nsctl commands
- if (FD_ISSET(controlfd, &r))
+ for (c = n, i = 0; i < c; i++)
{
- alen = sizeof(addr);
- processcontrol(buf, recvfrom(controlfd, buf, sizeof(buf), MSG_WAITALL, (void *) &addr, &alen), &addr, alen);
- n--;
- }
+ struct event_data *d = events[i].data.ptr;
+ switch (d->type)
+ {
+ case FD_TYPE_CONTROL: // nsctl commands
+ alen = sizeof(addr);
+ processcontrol(buf, recvfrom(controlfd, buf, sizeof(buf), MSG_WAITALL, (void *) &addr, &alen), &addr, alen);
+ n--;
+ break;
- // RADIUS responses
- if (config->cluster_iam_master)
- {
- for (i = 0; i < config->num_radfds; i++)
+ case FD_TYPE_CLI: // CLI connections
{
- if (FD_ISSET(radfds[i], &r))
+ int cli;
+
+ alen = sizeof(addr);
+ if ((cli = accept(clifd, (struct sockaddr *)&addr, &alen)) >= 0)
{
- processrad(buf, recv(radfds[i], buf, sizeof(buf), 0), i);
- n--;
+ cli_do(cli);
+ close(cli);
}
- }
- }
+ else
+ LOG(0, 0, 0, "accept error: %s\n", strerror(errno));
- // CLI connections
- if (FD_ISSET(clifd, &r))
- {
- int cli;
-
- alen = sizeof(addr);
- if ((cli = accept(clifd, (struct sockaddr *)&addr, &alen)) >= 0)
- {
- cli_do(cli);
- close(cli);
+ n--;
+ break;
}
- else
- LOG(0, 0, 0, "accept error: %s\n", strerror(errno));
- n--;
- }
+ // these are handled below, with multiple interleaved reads
+ case FD_TYPE_UDP: udp_ready++; break;
+ case FD_TYPE_TUN: tun_ready++; break;
+ case FD_TYPE_CLUSTER: cluster_ready++; break;
+
+ case FD_TYPE_RADIUS: // RADIUS response
+ s = recv(radfds[d->index], buf, sizeof(buf), 0);
+ if (s >= 0 && config->cluster_iam_master)
+ processrad(buf, s, d->index);
+
+ n--;
+ break;
#ifdef BGP
- for (i = 0; i < BGP_NUM_PEERS; i++)
- {
- int isr = bgp_set[i] ? FD_ISSET(bgp_peers[i].sock, &r) : 0;
- int isw = bgp_set[i] ? FD_ISSET(bgp_peers[i].sock, &w) : 0;
- bgp_process(&bgp_peers[i], isr, isw);
- if (isr) n--;
- if (isw) n--;
+ case FD_TYPE_BGP:
+ bgp_events[d->index] = events[i].events;
+ n--;
+ break;
+#endif /* BGP */
+
+ default:
+ LOG(0, 0, 0, "Unexpected fd type returned from epoll_wait: %d\n", d->type);
+ }
}
+
+#ifdef BGP
+ bgp_process(bgp_events);
#endif /* BGP */
for (c = 0; n && c < config->multi_read_count; c++)
{
// L2TP
- if (FD_ISSET(udpfd, &r))
+ if (udp_ready)
{
alen = sizeof(addr);
if ((s = recvfrom(udpfd, buf, sizeof(buf), 0, (void *) &addr, &alen)) > 0)
}
else
{
- FD_CLR(udpfd, &r);
+ udp_ready = 0;
n--;
}
}
// incoming IP
- if (FD_ISSET(tunfd, &r))
+ if (tun_ready)
{
if ((s = read(tunfd, buf, sizeof(buf))) > 0)
{
}
else
{
- FD_CLR(tunfd, &r);
+ tun_ready = 0;
n--;
}
}
// cluster
- if (FD_ISSET(cluster_sockfd, &r))
+ if (cluster_ready)
{
alen = sizeof(addr);
if ((s = recvfrom(cluster_sockfd, buf, sizeof(buf), MSG_WAITALL, (void *) &addr, &alen)) > 0)
}
else
{
- FD_CLR(cluster_sockfd, &r);
+ cluster_ready = 0;
n--;
}
}
}
// Runs on every machine (master and slaves).
- if (cluster_sockfd && next_cluster_ping <= TIME)
+ if (next_cluster_ping <= TIME)
{
// Check to see which of the cluster is still alive..
//
// Important!!! We MUST not process any packets past this point!
- LOG(1, 0, 0, "Clean shutdown complete\n");
+ LOG(1, 0, 0, "Shutdown complete\n");
}
static void stripdomain(char *host)
LOG(3, 0, 0, "Dumping accounting information to %s\n", filename);
fprintf(*f, "# dslwatch.pl dump file V1.01\n"
"# host: %s\n"
+ "# endpoint: %s\n"
"# time: %ld\n"
"# uptime: %ld\n"
"# format: username ip qos uptxoctets downrxoctets\n",
hostname,
+ fmtaddr(config->bind_address ? config->bind_address : my_address, 0),
now,
now - basetime);
}
if (cluster_init() < 0)
exit(1);
-#ifdef BGP
- signal(SIGPIPE, SIG_IGN);
- bgp_setup(config->as_number);
- bgp_add_route(config->bind_address, 0xffffffff);
- for (i = 0; i < BGP_NUM_PEERS; i++)
- {
- if (config->neighbour[i].name[0])
- bgp_start(&bgp_peers[i], config->neighbour[i].name,
- config->neighbour[i].as, config->neighbour[i].keepalive,
- config->neighbour[i].hold, 0); /* 0 = routing disabled */
- }
-#endif /* BGP */
-
inittun();
LOG(1, 0, 0, "Set up on interface %s\n", config->tundevice);
initrad();
initippool();
- signal(SIGHUP, sighup_handler);
- signal(SIGTERM, sigterm_handler);
- signal(SIGINT, sigterm_handler);
- signal(SIGQUIT, sigquit_handler);
+ // seed prng
+ {
+ unsigned seed = time_now ^ getpid();
+ LOG(4, 0, 0, "Seeding the pseudo random generator: %u\n", seed);
+ srand(seed);
+ }
+
+ signal(SIGHUP, sighup_handler);
signal(SIGCHLD, sigchild_handler);
+ signal(SIGTERM, shutdown_handler);
+ signal(SIGINT, shutdown_handler);
+ signal(SIGQUIT, shutdown_handler);
// Prevent us from getting paged out
if (config->lock_pages)
}
-static void sigterm_handler(int sig)
+static void shutdown_handler(int sig)
{
- LOG(1, 0, 0, "Shutting down cleanly\n");
- main_quit++;
-}
-
-static void sigquit_handler(int sig)
-{
- int i;
-
- LOG(1, 0, 0, "Shutting down without saving sessions\n");
-
- if (config->cluster_iam_master)
- {
- for (i = 1; i < MAXSESSION; i++)
- {
- if (session[i].opened)
- sessionkill(i, "L2TPNS Closing");
- }
- for (i = 1; i < MAXTUNNEL; i++)
- {
- if (tunnel[i].ip || tunnel[i].state)
- tunnelshutdown(i, "L2TPNS Closing", 6, 0, 0);
- }
- }
-
- main_quit++;
+ LOG(1, 0, 0, "Shutting down\n");
+ main_quit = (sig == SIGQUIT) ? QUIT_SHUTDOWN : QUIT_FAILOVER;
}
static void sigchild_handler(int sig)
if (!config->numradiusservers)
LOG(0, 0, 0, "No RADIUS servers defined!\n");
- config->num_radfds = 1 << RADIUS_SHIFT;
-
// parse radius_authtypes_s
config->radius_authtypes = config->radius_authprefer = 0;
p = config->radius_authtypes_s;
if (!session[s].ip)
{
LOG(0, s, t, " No IP allocated. The IP address pool is FULL!\n");
- sessionshutdown(s, "No IP addresses available.", 2, 7);
+ sessionshutdown(s, "No IP addresses available.", 2, 7); // try another
return 0;
}
LOG(3, s, t, " No IP allocated. Assigned %s from pool\n",
void become_master(void)
{
int s, i;
+ static struct event_data d[RADIUS_FDS];
+ struct epoll_event e;
+
run_plugins(PLUGIN_BECOME_MASTER, NULL);
// running a bunch of iptables commands is slow and can cause
}
// add radius fds
- for (i = 0; i < config->num_radfds; i++)
+ e.events = EPOLLIN;
+ for (i = 0; i < RADIUS_FDS; i++)
{
- FD_SET(radfds[i], &readset);
- if (radfds[i] > readset_n)
- readset_n = radfds[i];
+ d[i].type = FD_TYPE_RADIUS;
+ d[i].index = i;
+ e.data.ptr = &d[i];
+
+ epoll_ctl(epollfd, EPOLL_CTL_ADD, radfds[i], &e);
}
}
uint8_t digest[16];
uint8_t *last;
size_t d = 0;
+ uint16_t m = htons(type);
// Compute initial pad
MD5Init(&ctx);
- MD5Update(&ctx, (uint8_t) (type >> 8) & 0xff, 1);
- MD5Update(&ctx, (uint8_t) type & 0xff, 1);
+ MD5Update(&ctx, (unsigned char *) &m, 2);
MD5Update(&ctx, config->l2tpsecret, strlen(config->l2tpsecret));
MD5Update(&ctx, vector, vec_len);
MD5Final(digest, &ctx);