IPv6 load-balancing
[l2tpns.git] / l2tpns.c
index cc69c2d..5445013 100644 (file)
--- a/l2tpns.c
+++ b/l2tpns.c
@@ -77,7 +77,7 @@ int cluster_sockfd = -1;      // Intra-cluster communications socket.
 int epollfd = -1;              // event polling
 time_t basetime = 0;           // base clock
 char hostname[MAXHOSTNAME] = "";       // us.
-static int tunidx;             // ifr_ifindex of tun device
+int tunidx;                            // ifr_ifindex of tun device
 int nlseqnum = 0;              // netlink sequence number
 int min_initok_nlseqnum = 0;   // minimun seq number for messages after init is ok
 static int syslog_log = 0;     // are we logging to syslog
@@ -94,15 +94,16 @@ uint16_t MSS = 0;           // TCP MSS
 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
 
-union iphash {
+union iphash
+{
        sessionidt sess;
        union iphash *idx;
-} ip_hash[256];                        // Mapping from IP address to session structures.
+}ip_hash[256]; // Mapping from IP address to session structures.
 
 struct ipv6radix {
        sessionidt sess;
        struct ipv6radix *branch;
-} ipv6_hash[256];              // Mapping from IPv6 address to session structures.
+} ipv6_hash[16];               // Mapping from IPv6 address to session structures.
 
 // Traffic counters.
 static uint32_t udp_rx = 0, udp_rx_pkt = 0, udp_tx = 0;
@@ -188,6 +189,7 @@ config_descriptt config_values[] = {
        CONFIG("disable_sending_hello", disable_sending_hello, BOOL),
        CONFIG("disable_no_spoof", disable_no_spoof, BOOL),
        CONFIG("bind_multi_address", bind_multi_address, STRING),
+       CONFIG("grp_txrate_average_time", grp_txrate_average_time, INT),
        CONFIG("pppoe_only_equal_svc_name", pppoe_only_equal_svc_name, BOOL),
        CONFIG("multi_hostname", multi_hostname, STRING),
        CONFIG("no_throttle_local_IP", no_throttle_local_IP, BOOL),
@@ -225,6 +227,7 @@ tunnelt *tunnel = NULL;                     // Array of tunnel structures.
 bundlet *bundle = NULL;                        // Array of bundle structures.
 fragmentationt *frag = NULL;           // Array of fragmentation structures.
 sessiont *session = NULL;              // Array of session structures.
+groupsesst *grpsession = NULL;         // Array of groupsesst structures.
 sessionlocalt *sess_local = NULL;      // Array of local per-session counters.
 radiust *radius = NULL;                        // Array of radius structures.
 ippoolt *ip_address_pool = NULL;       // Array of dynamic IP addresses.
@@ -235,9 +238,6 @@ struct Tstats *_statistics = NULL;
 struct Tringbuffer *ringbuffer = NULL;
 #endif
 
-static ssize_t netlink_send(struct nlmsghdr *nh);
-static void netlink_addattr(struct nlmsghdr *nh, int type, const void *data, int alen);
-static void cache_ipmap(in_addr_t ip, sessionidt s);
 static void uncache_ipmap(in_addr_t ip);
 static void cache_ipv6map(struct in6_addr ip, int prefixlen, sessionidt s);
 static void free_ip_address(sessionidt s);
@@ -266,8 +266,9 @@ static clockt now(double *f)
        if (f) *f = t.tv_sec + t.tv_usec / 1000000.0;
        if (t.tv_sec != time_now)
        {
-           time_now = t.tv_sec;
-           time_changed++;
+               time_now = t.tv_sec;
+               time_changed++;
+               grp_time_changed();
        }
 
        // Time in milliseconds
@@ -626,7 +627,7 @@ static void initnetlink(void)
        }
 }
 
-static ssize_t netlink_send(struct nlmsghdr *nh)
+ssize_t netlink_send(struct nlmsghdr *nh)
 {
        struct sockaddr_nl nladdr;
        struct iovec iov;
@@ -662,7 +663,7 @@ static ssize_t netlink_recv(void *buf, ssize_t len)
 }
 
 /* adapted from iproute2 */
-static void netlink_addattr(struct nlmsghdr *nh, int type, const void *data, int alen)
+void netlink_addattr(struct nlmsghdr *nh, int type, const void *data, int alen)
 {
        int len = RTA_LENGTH(alen);
        struct rtattr *rta;
@@ -982,20 +983,24 @@ static sessionidt lookup_ipv6map(struct in6_addr ip)
        int s;
        char ipv6addr[INET6_ADDRSTRLEN];
 
-       curnode = &ipv6_hash[ip.s6_addr[0]];
+       curnode = &ipv6_hash[((ip.s6_addr[0]) & 0xF0)>>4];
        i = 1;
        s = curnode->sess;
 
-       while (s == 0 && i < 15 && curnode->branch != NULL)
+       while (s == 0 && i < 32 && curnode->branch != NULL)
        {
-               curnode = &curnode->branch[ip.s6_addr[i]];
+               if (i & 1)
+                       curnode = &curnode->branch[ip.s6_addr[i>>1] & 0x0F];
+               else
+                       curnode = &curnode->branch[(ip.s6_addr[i>>1] & 0xF0)>>4];
+
                s = curnode->sess;
                i++;
        }
 
        LOG(4, s, session[s].tunnel, "Looking up address %s and got %d\n",
-                       inet_ntop(AF_INET6, &ip, ipv6addr,
-                               INET6_ADDRSTRLEN),
+                       inet_ntop(AF_INET6, &ip, ipv6addr,
+                       INET6_ADDRSTRLEN),
                        s);
 
        return s;
@@ -1036,13 +1041,26 @@ sessionidt sessionbyipv6(struct in6_addr ip)
        return 0;
 }
 
+sessionidt sessionbyipv6new(struct in6_addr ip)
+{
+       sessionidt s;
+       CSTAT(sessionbyipv6new);
+
+       s = lookup_ipv6map(ip);
+
+       if (s > 0 && s < MAXSESSION && session[s].opened)
+               return s;
+
+       return 0;
+}
+
 //
 // Take an IP address in HOST byte order and
 // add it to the sessionid by IP cache.
 //
 // (It's actually cached in network order)
 //
-static void cache_ipmap(in_addr_t ip, sessionidt s)
+void cache_ipmap(in_addr_t ip, sessionidt s)
 {
        in_addr_t nip = htonl(ip);      // MUST be in network order. I.e. MSB must in be ((char *) (&ip))[0]
        uint8_t *a = (uint8_t *) &nip;
@@ -1061,7 +1079,6 @@ static void cache_ipmap(in_addr_t ip, sessionidt s)
 
        if (s > 0)
                LOG(4, s, session[s].tunnel, "Caching ip address %s\n", fmtaddr(nip, 0));
-
        else if (s == 0)
                LOG(4, 0, 0, "Un-caching ip address %s\n", fmtaddr(nip, 0));
        // else a map to an ip pool index.
@@ -1075,22 +1092,28 @@ static void uncache_ipmap(in_addr_t ip)
 static void cache_ipv6map(struct in6_addr ip, int prefixlen, sessionidt s)
 {
        int i;
-       int bytes;
+       int niblles;
        struct ipv6radix *curnode;
        char ipv6addr[INET6_ADDRSTRLEN];
 
-       curnode = &ipv6_hash[ip.s6_addr[0]];
+       curnode = &ipv6_hash[((ip.s6_addr[0]) & 0xF0)>>4];
 
-       bytes = prefixlen >> 3;
+       niblles = prefixlen >> 2;
        i = 1;
-       while (i < bytes) {
+
+       while (i < niblles)
+       {
                if (curnode->branch == NULL)
                {
-                       if (!(curnode->branch = calloc(256,
-                                       sizeof (struct ipv6radix))))
+                       if (!(curnode->branch = calloc(16, sizeof (struct ipv6radix))))
                                return;
                }
-               curnode = &curnode->branch[ip.s6_addr[i]];
+
+               if (i & 1)
+                       curnode = &curnode->branch[ip.s6_addr[i>>1] & 0x0F];
+               else
+                       curnode = &curnode->branch[(ip.s6_addr[i>>1] & 0xF0)>>4];
+
                i++;
        }
 
@@ -1098,13 +1121,13 @@ static void cache_ipv6map(struct in6_addr ip, int prefixlen, sessionidt s)
 
        if (s > 0)
                LOG(4, s, session[s].tunnel, "Caching ip address %s/%d\n",
-                               inet_ntop(AF_INET6, &ip, ipv6addr, 
-                                       INET6_ADDRSTRLEN),
+                               inet_ntop(AF_INET6, &ip, ipv6addr,
+                               INET6_ADDRSTRLEN),
                                prefixlen);
        else if (s == 0)
                LOG(4, 0, 0, "Un-caching ip address %s/%d\n",
-                               inet_ntop(AF_INET6, &ip, ipv6addr, 
-                                       INET6_ADDRSTRLEN),
+                               inet_ntop(AF_INET6, &ip, ipv6addr,
+                               INET6_ADDRSTRLEN),
                                prefixlen);
 }
 
@@ -1155,7 +1178,6 @@ int cmd_show_ipcache(struct cli_def *cli, const char *command, char **argv, int
        return CLI_OK;
 }
 
-
 // Find session by username, 0 for not found
 // walled garden users aren't authenticated, so the username is
 // reasonably useless. Ignore them to avoid incorrect actions
@@ -1424,6 +1446,7 @@ static void update_session_out_stat(sessionidt s, sessiont *sp, int len)
 void processipout(uint8_t *buf, int len)
 {
        sessionidt s;
+       groupidt g;
        sessiont *sp;
        tunnelidt t;
        in_addr_t ip, ip_src;
@@ -1461,7 +1484,31 @@ void processipout(uint8_t *buf, int len)
 
        ip_src = *(uint32_t *)(buf + 12);
        ip = *(uint32_t *)(buf + 16);
-       if (!(s = sessionbyip(ip)))
+       if ((g = grp_groupbyip(ip)))
+       {
+               s = grp_getnextsession(g, &ip, &ip_src, 0);
+               if (!s)
+               {
+                       // Is this a packet for a session that doesn't exist?
+                       static int rate = 0;    // Number of ICMP packets we've sent this second.
+                       static int last = 0;    // Last time we reset the ICMP packet counter 'rate'.
+
+                       if (last != time_now)
+                       {
+                               last = time_now;
+                               rate = 0;
+                       }
+
+                       if (rate++ < config->icmp_rate) // Only send a max of icmp_rate per second.
+                       {
+                               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),
+                                       config->bind_address ? config->bind_address : my_address, buf, len);
+                       }
+                       return;
+               }
+       }
+       else if (!(s = sessionbyip(ip)))
        {
                // Is this a packet for a session that doesn't exist?
                static int rate = 0;    // Number of ICMP packets we've sent this second.
@@ -1690,10 +1737,11 @@ void processipout(uint8_t *buf, int len)
 static void processipv6out(uint8_t * buf, int len)
 {
        sessionidt s;
+       groupidt g;
        sessiont *sp;
        tunnelidt t;
-       in_addr_t ip;
-       struct in6_addr ip6;
+       struct in6_addr *p_ip6;
+       struct in6_addr *p_ip6_src;
 
        uint8_t *data = buf;    // Keep a copy of the originals.
        int size = len;
@@ -1726,15 +1774,18 @@ static void processipv6out(uint8_t * buf, int len)
                return;
        }
 
-       ip6 = *(struct in6_addr *)(buf+24);
-       s = sessionbyipv6(ip6);
+       p_ip6_src = (struct in6_addr *)(buf+8);
+       p_ip6 = (struct in6_addr *)(buf+24);
 
-       if (s == 0)
+       if ((g = grp_groupbyipv6(*p_ip6)))
        {
-               ip = *(uint32_t *)(buf + 32);
-               s = sessionbyip(ip);
+               s = grp_getnextsession(g, p_ip6, p_ip6_src, 1);
        }
-       
+       else if (!(s = sessionbyipv6(*p_ip6)))
+       {
+               s = sessionbyipv6new(*p_ip6);
+       }
+
        if (s == 0)
        {
                // Is this a packet for a session that doesn't exist?
@@ -2106,7 +2157,7 @@ void sessionshutdown(sessionidt s, char const *reason, int cdn_result, int cdn_e
                session[s].die = TIME + 150; // Clean up in 15 seconds
 
        if (session[s].ip)
-       {                          // IP allocated, clear and unroute
+       {       // IP allocated, clear and unroute
                int r;
                int routed = 0;
                for (r = 0; r < MAXROUTE && session[s].route[r].ip; r++)
@@ -2321,6 +2372,8 @@ static void sessionclear(sessionidt s)
 // kill a session now
 void sessionkill(sessionidt s, char *reason)
 {
+       groupidt g;
+
        CSTAT(sessionkill);
 
        if (!session[s].opened) // not alive
@@ -2349,6 +2402,12 @@ void sessionkill(sessionidt s, char *reason)
        }
 
        LOG(2, s, session[s].tunnel, "Kill session %d (%s): %s\n", s, session[s].user, reason);
+
+       if ((g = grp_groupbysession(s)))
+       {
+               grp_removesession(g, s);
+       }
+
        sessionclear(s);
        cluster_send_session(s);
 }
@@ -3760,7 +3819,7 @@ static void regular_cleanups(double period)
 
                // No data in ECHO_TIMEOUT seconds, send LCP ECHO
                if (session[s].ppp.phase >= Establish && (time_now - session[s].last_packet >= config->echo_timeout) &&
-                       (time_now - sess_local[s].last_echo >= ECHO_TIMEOUT))
+                       (time_now - sess_local[s].last_echo >= config->echo_timeout))
                {
                        uint8_t b[MAXETHER];
 
@@ -4697,6 +4756,8 @@ static void initdata(int optdebug, char *optconfig)
 #endif /* BGP */
 
        lac_initremotelnsdata();
+
+       grp_initdata();
 }
 
 static int assign_ip_address(sessionidt s)
@@ -5239,6 +5300,9 @@ int main(int argc, char *argv[])
                        LOG(0, 0, 0, "Can't lock pages: %s\n", strerror(errno));
        }
 
+       //LOG(3, 0, 0, "Debug sizeof struct: sessiont %lu, tunnelt %lu, bundlet %lu, groupsesst %lu\n",
+       //      sizeof(sessiont), sizeof(tunnelt), sizeof(bundlet), sizeof(groupsesst));
+
        mainloop();
 
        /* remove plugins (so cleanup code gets run) */
@@ -5750,6 +5814,7 @@ int sessionsetup(sessionidt s, tunnelidt t)
        if (!session[s].bundle || (bundle[session[s].bundle].num_of_links == 1))
        {
                int routed = 0;
+               groupidt g;
 
                // Add the route for this session.
                for (r = 0; r < MAXROUTE && session[s].route[r].ip; r++)
@@ -5772,6 +5837,12 @@ int sessionsetup(sessionidt s, tunnelidt t)
                }
                else
                        cache_ipmap(session[s].ip, s);
+
+               if ((g = grp_groupbysession(s)))
+               {
+                       grp_setgrouproute(g, 1);
+                       cluster_send_groupe(g);
+               }
        }
 
        sess_local[s].lcp_authtype = 0; // RADIUS authentication complete
@@ -5895,9 +5966,12 @@ int load_session(sessionidt s, sessiont *new)
        }
 
        // check v6 routing
-       for (i = 0; i < MAXROUTE6 && new->route6[i].ipv6prefixlen; i++)
+       if (new->ppp.ipv6cp == Opened && session[s].ppp.ipv6cp != Opened)
        {
-               route6set(s, new->route6[i].ipv6route, new->route6[i].ipv6prefixlen, 1);
+               for (i = 0; i < MAXROUTE6 && new->route6[i].ipv6prefixlen; i++)
+               {
+                       route6set(s, new->route6[i].ipv6route, new->route6[i].ipv6prefixlen, 1);
+               }
        }
 
        if (new->ipv6address.s6_addr[0] && new->ppp.ipv6cp == Opened && session[s].ppp.ipv6cp != Opened)