From 92bfbe46e854e7ff40d6d4297c8df74f1efcf10f Mon Sep 17 00:00:00 2001 From: fendo Date: Sun, 21 Sep 2014 13:51:32 +0200 Subject: [PATCH] IPv6 load-balancing --- Makefile | 2 +- dhcp6.c | 1 + dictionary.sames | 1 + grpsess.c | 388 +++++++++++++++++++++++++++++++++++++++++------ l2tpns.c | 33 ++-- l2tpns.h | 15 +- ppp.c | 10 +- 7 files changed, 386 insertions(+), 64 deletions(-) diff --git a/Makefile b/Makefile index abab4e0..458b494 100644 --- a/Makefile +++ b/Makefile @@ -129,7 +129,7 @@ util.o: util.c dhcp6.h l2tpns.h bgp.h pppoe.o: pppoe.c dhcp6.h l2tpns.h cluster.h constants.h md5.h util.h l2tplac.o: l2tplac.c md5.h dhcp6.h l2tpns.h util.h cluster.h l2tplac.h \ pppoe.h -grpsess.o: grpsess.c l2tpns.h util.h cluster.h bgp.h +grpsess.o: grpsess.c dhcp6.h l2tpns.h util.h cluster.h bgp.h dhcp6.o: dhcp6.c dhcp6.h l2tpns.h ipv6_u.h ipv6_u.o: ipv6_u.c ipv6_u.h bgp.o: bgp.c dhcp6.h l2tpns.h bgp.h util.h diff --git a/dhcp6.c b/dhcp6.c index 171c1f0..710f5c0 100644 --- a/dhcp6.c +++ b/dhcp6.c @@ -5,6 +5,7 @@ */ #include +#include #include #include diff --git a/dictionary.sames b/dictionary.sames index a8a244d..9e34def 100644 --- a/dictionary.sames +++ b/dictionary.sames @@ -13,6 +13,7 @@ BEGIN-VENDOR SAMES ATTRIBUTE SAMES-Group-Framed-Route 22 string ATTRIBUTE SAMES-Group-Session-Weight 23 string +ATTRIBUTE SAMES-Group-Delegated-IPv6-Prefix 24 string END-VENDOR SAMES diff --git a/grpsess.c b/grpsess.c index 5528453..92adcec 100644 --- a/grpsess.c +++ b/grpsess.c @@ -25,6 +25,11 @@ union grp_iphash { union grp_iphash *idx; } grp_ip_hash[256]; // Mapping from IP address to group structures. +struct grp_ipv6radix { + groupidt grp; + struct grp_ipv6radix *branch; +} grp_ipv6_hash[16]; // Mapping from IPv6 address to session structures. + groupidt gnextgrpid = 0; typedef struct @@ -91,6 +96,192 @@ groupidt grp_groupbyip(in_addr_t ip) return 0; } +static void grp_cache_ipv6map(struct in6_addr ip, int prefixlen, groupidt g) +{ + int i; + int niblles; + struct grp_ipv6radix *curnode; + char ipv6addr[INET6_ADDRSTRLEN]; + + curnode = &grp_ipv6_hash[((ip.s6_addr[0]) & 0xF0)>>4]; + + niblles = prefixlen >> 2; + i = 1; + + while (i < niblles) + { + if (curnode->branch == NULL) + { + if (!(curnode->branch = calloc(16, sizeof(struct grp_ipv6radix)))) + return; + } + + if (i & 1) + curnode = &curnode->branch[ip.s6_addr[i>>1] & 0x0F]; + else + curnode = &curnode->branch[(ip.s6_addr[i>>1] & 0xF0)>>4]; + + i++; + } + + curnode->grp = g; + + if (g > 0) + LOG(4, 0, 0, "Caching Group:%d ip address %s/%d\n", g, + inet_ntop(AF_INET6, &ip, ipv6addr, + INET6_ADDRSTRLEN), + prefixlen); + else if (g == 0) + LOG(4, 0, 0, "Un-caching Group:%d ip address %s/%d\n", g, + inet_ntop(AF_INET6, &ip, ipv6addr, + INET6_ADDRSTRLEN), + prefixlen); +} + +static void grp_route6set(groupidt g, struct in6_addr ip, int prefixlen, int add) +{ + struct { + struct nlmsghdr nh; + struct rtmsg rt; + char buf[64]; + } req; + int metric; + char ipv6addr[INET6_ADDRSTRLEN]; + + if (!config->ipv6_prefix.s6_addr[0]) + { + LOG(0, 0, 0, "Asked to set IPv6 route, but IPv6 not setup.\n"); + return; + } + + memset(&req, 0, sizeof(req)); + + if (add) + { + req.nh.nlmsg_type = RTM_NEWROUTE; + req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE; + } + else + { + req.nh.nlmsg_type = RTM_DELROUTE; + req.nh.nlmsg_flags = NLM_F_REQUEST; + } + + req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.rt)); + + req.rt.rtm_family = AF_INET6; + req.rt.rtm_dst_len = prefixlen; + req.rt.rtm_table = RT_TABLE_MAIN; + req.rt.rtm_protocol = 42; + req.rt.rtm_scope = RT_SCOPE_LINK; + req.rt.rtm_type = RTN_UNICAST; + + netlink_addattr(&req.nh, RTA_OIF, &tunidx, sizeof(int)); + netlink_addattr(&req.nh, RTA_DST, &ip, sizeof(ip)); + metric = 1; + netlink_addattr(&req.nh, RTA_METRICS, &metric, sizeof(metric)); + + LOG(1, g, 0, "Route Group %s %s/%d\n", + add ? "add" : "del", + inet_ntop(AF_INET6, &ip, ipv6addr, INET6_ADDRSTRLEN), + prefixlen); + + if (netlink_send(&req.nh) < 0) + LOG(0, 0, 0, "grp_route6set() error in sending netlink message: %s\n", strerror(errno)); + +#ifdef BGP + if (add) + bgp_add_route6(ip, prefixlen); + else + bgp_del_route6(ip, prefixlen); +#endif /* BGP */ + + if (g) + { + if (!add) // Are we deleting a route? + g = 0; // Caching the session as '0' is the same as uncaching. + + grp_cache_ipv6map(ip, prefixlen, g); + } + + return; +} + +static groupidt grp_lookup_ipv6map(struct in6_addr ip) +{ + struct grp_ipv6radix *curnode; + int i; + int g; + char ipv6addr[INET6_ADDRSTRLEN]; + + curnode = &grp_ipv6_hash[((ip.s6_addr[0]) & 0xF0)>>4]; + i = 1; + g = curnode->grp; + + while (g == 0 && i < 32 && curnode->branch != NULL) + { + if (i & 1) + curnode = &curnode->branch[ip.s6_addr[i>>1] & 0x0F]; + else + curnode = &curnode->branch[(ip.s6_addr[i>>1] & 0xF0)>>4]; + + g = curnode->grp; + i++; + } + + LOG(4, 0, 0, "Looking up Group address %s and got %d\n", + inet_ntop(AF_INET6, &ip, ipv6addr, + INET6_ADDRSTRLEN), + g); + + return g; +} + +groupidt grp_groupbyipv6(struct in6_addr ip) +{ + groupidt g = grp_lookup_ipv6map(ip); + + if (g > 0 && g < MAXGROUPE) + return g; + + return 0; +} + +// Set all route of a group +void grp_setgrouproute6(groupidt g, int add) +{ + int i, j; + int ipv6opened = 0; + + if (add) + { + for (j = 0; j < grpsession[g].nbsession; j++) + { + if (grpsession[g].sesslist[j].sid != 0) + { + if (session[grpsession[g].sesslist[j].sid].ppp.ipv6cp == Opened) + { + // IPv6 opened + ipv6opened = 1; + break; + } + } + } + } + + if (ipv6opened || !add) + { + for (i = 0; i < grpsession[g].nbroutes6grp; i++) + { + if (grpsession[g].route6[i].ipv6prefixlen != 0) + { + grpsession[g].ipv6cp_opened = add; + grp_route6set(g, grpsession[g].route6[i].ipv6route, grpsession[g].route6[i].ipv6prefixlen, add); + } + } + } +} + // Add a route // // This adds it to the routing table, advertises it @@ -223,6 +414,7 @@ void grp_removesession(groupidt g, sessionidt s) // Del all routes grp_setgrouproute(g, 0); + grp_setgrouproute6(g, 0); if (gnextgrpid == g) { @@ -343,6 +535,52 @@ static int grp_addroute(groupidt g, sessionidt s, in_addr_t ip, int prefixlen) return 0; } +// Add a route to a group +// return 1 if OK +static int grp_addroute6(groupidt g, sessionidt s, struct in6_addr ip6, int prefixlen) +{ + int i; + char ipv6addr[INET6_ADDRSTRLEN]; + + for (i = 0; i < MAXROUTE6INGRP; i++) + { + if ((i >= grpsession[g].nbroutes6grp)) + { + LOG(3, s, session[s].tunnel, " Radius reply Group %d contains route for %s/%d\n", + g, inet_ntop(AF_INET6, &ip6, ipv6addr, INET6_ADDRSTRLEN), prefixlen); + + grpsession[g].route6[i].ipv6route = ip6; + grpsession[g].route6[i].ipv6prefixlen = prefixlen; + grpsession[g].nbroutes6grp++; + return 1; + } + else if (!memcmp(&grpsession[g].route6[i].ipv6route, &ip6, sizeof(ip6)) && (grpsession[g].route6[i].ipv6prefixlen == prefixlen)) + { + // route already defined in group + LOG(3, s, session[s].tunnel, + " Radius reply Group %d contains route for %s/%d (this already defined)\n", + g, inet_ntop(AF_INET6, &ip6, ipv6addr, INET6_ADDRSTRLEN), prefixlen); + + return 1; + } + else if (grpsession[g].route6[i].ipv6prefixlen == 0) + { + LOG(3, s, session[s].tunnel, " Radius reply Group %d contains route for %s/%d (find empty on list!!!)\n", + g, inet_ntop(AF_INET6, &ip6, ipv6addr, INET6_ADDRSTRLEN), prefixlen); + + grpsession[g].route6[i].ipv6route = ip6; + grpsession[g].route6[i].ipv6prefixlen = prefixlen; + return 1; + } + } + + if (i >= MAXROUTE6INGRP) + { + LOG(1, s, session[s].tunnel, " Too many IPv6 routes for Group %d\n", g); + } + return 0; +} + // Process Sames vendor specific attribut radius void grp_processvendorspecific(sessionidt s, uint8_t *pvs) { @@ -351,7 +589,7 @@ void grp_processvendorspecific(sessionidt s, uint8_t *pvs) uint8_t *n = pvs + 2; uint8_t *e = pvs + pvs[1]; - if ((attrib >= 22) && (attrib <= 23)) + if ((attrib >= 22) && (attrib <= 24)) { while (n < e && isdigit(*n)) { @@ -415,6 +653,26 @@ void grp_processvendorspecific(sessionidt s, uint8_t *pvs) if (!grp_addroute(grpid, s, ip, bits)) return; } + else if (attrib == 24) + { + struct in6_addr r6; + int prefixlen; + uint8_t *m = memchr(n, '/', e - n); + + *m++ = 0; + inet_pton(AF_INET6, (char *) n, &r6); + + prefixlen = 0; + while (m < e && isdigit(*m)) { + prefixlen = prefixlen * 10 + *m++ - '0'; + } + + if (prefixlen) + { + if (!grp_addroute6(grpid, s, r6, prefixlen)) + return; + } + } else if (attrib == 23) //SAMES-Group-Session-Weight { uint8_t weight = 0; @@ -479,46 +737,50 @@ void grp_time_changed() } // Uncache all IP of a session -static void grp_uncache_ipsession(groupidt g, sessionidt s) -{ - int i; - uint8_t *a; - in_addr_t ip; - in_addr_t n_ip, j; - int prefixlen; - union iphash *h; - - for (i = 0; i < grpsession[g].nbroutesgrp; i++) - { - if (grpsession[g].route[i].ip != 0) - { - prefixlen = grpsession[g].route[i].prefixlen; - ip = grpsession[g].route[i].ip & (0xffffffff << (32 - prefixlen)); // Force the ip to be the first one in the route. - - for (j = ip; j < ip+(1<<(32-prefixlen)) ; ++j) - { - n_ip = htonl(j); // To network order - a = (uint8_t *) &n_ip; - h = ip_hash; - - if (!(h = h[*a++].idx)) continue; - if (!(h = h[*a++].idx)) continue; - if (!(h = h[*a++].idx)) continue; - - if (s == h[*a].sess) - { - h[*a].sess = 0; - //LOG(3, s, session[s].tunnel, "UnCaching ip address %s\n", fmtaddr(n_ip, 0)); - } - } - } - } -} +//~ static void grp_uncache_ipsession(groupidt g, sessionidt s) +//~ { + //~ int i; + //~ uint8_t *a; + //~ in_addr_t ip; + //~ in_addr_t n_ip, j; + //~ int prefixlen; + //~ union iphash *h; +//~ + //~ for (i = 0; i < grpsession[g].nbroutesgrp; i++) + //~ { + //~ if (grpsession[g].route[i].ip != 0) + //~ { + //~ prefixlen = grpsession[g].route[i].prefixlen; + //~ ip = grpsession[g].route[i].ip & (0xffffffff << (32 - prefixlen)); // Force the ip to be the first one in the route. +//~ + //~ for (j = ip; j < ip+(1<<(32-prefixlen)) ; ++j) + //~ { + //~ n_ip = htonl(j); // To network order + //~ a = (uint8_t *) &n_ip; + //~ h = ip_hash; +//~ + //~ if (!(h = h[*a++].idx)) continue; + //~ if (!(h = h[*a++].idx)) continue; + //~ if (!(h = h[*a++].idx)) continue; +//~ + //~ if (s == h[*a].sess) + //~ { + //~ h[*a].sess = 0; + //~ //LOG(3, s, session[s].tunnel, "UnCaching ip address %s\n", fmtaddr(n_ip, 0)); + //~ } + //~ } + //~ } + //~ } +//~ } uint16_t guint16_index_loadlist; // return the next session can be used on the group -sessionidt grp_getnextsession(groupidt g, in_addr_t ip, in_addr_t ip_src) +sessionidt grp_getnextsession(groupidt g, void *p_ip, void *p_ip_src, int is_ipv6) { + in_addr_t *p_ipv4 = p_ip; + in_addr_t *p_ipv4_src = p_ip_src; + struct in6_addr *p_ipv6 = p_ip; + struct in6_addr *p_ipv6_src = p_ip_src; sessionidt s = 0, s2 = 0, s3 = 0; int i; uint32_t ltime_changed = 0, mintxrate = 0xFFFFFFFF, maxtxrate = 0; @@ -569,11 +831,27 @@ sessionidt grp_getnextsession(groupidt g, in_addr_t ip, in_addr_t ip_src) } } - if ((s = sessionbyip(ip))) + if (!is_ipv6) + s = sessionbyip(*p_ipv4); + + if (s || is_ipv6) { - uint8_t *as = (uint8_t *) &ip_src; - uint8_t *ad = (uint8_t *) &ip; - uint16_t ai = ad[3]; + uint8_t *as; + uint8_t *ad; + uint16_t ai; + + if (is_ipv6) + { + as = (uint8_t *) &p_ipv6_src[12]; + ad = (uint8_t *) &p_ipv6[12]; + } + else + { + as = (uint8_t *) p_ipv4_src; + ad = (uint8_t *) p_ipv4; + } + + ai = ad[3]; ai <<= 8; ai |= as[3]; @@ -641,8 +919,10 @@ sessionidt grp_getnextsession(groupidt g, in_addr_t ip, in_addr_t ip_src) if (!s) s = s3; - if (s) - cache_ipmap(ntohl(ip), s); + if (s && !is_ipv6) + { + cache_ipmap(ntohl(*p_ipv4), s); + } return s; } @@ -660,6 +940,7 @@ int grp_cluster_load_groupe(groupidt g, groupsesst *new) } if ((grpsession[g].nbroutesgrp != new->nbroutesgrp) || + (grpsession[g].nbroutes6grp != new->nbroutes6grp) || (grpsession[g].nbsession != new->nbsession)) { updategroup = 1; @@ -691,11 +972,31 @@ int grp_cluster_load_groupe(groupidt g, groupsesst *new) } } + if (!updategroup) + { + // Check IPv6 routes list + for (i = 0; i < grpsession[g].nbroutes6grp; i++) + { + if ((grpsession[g].route6[i].ipv6prefixlen != new->route6[i].ipv6prefixlen) || + memcmp(&grpsession[g].route6[i].ipv6route, &new->route6[i].ipv6route, sizeof(new->route6[i].ipv6route))) + { + updategroup = 1; + break; + } + } + } + + if (grpsession[g].ipv6cp_opened != new->ipv6cp_opened) + { + updategroup = 1; + } + // needs update if (updategroup) { // Del all routes grp_setgrouproute(g, 0); + grp_setgrouproute6(g, 0); } memcpy(&grpsession[g], new, sizeof(grpsession[g])); // Copy over.. @@ -705,6 +1006,7 @@ int grp_cluster_load_groupe(groupidt g, groupsesst *new) { // Add all routes grp_setgrouproute(g, 1); + grp_setgrouproute6(g, 1); } return 1; diff --git a/l2tpns.c b/l2tpns.c index 5c887ec..5445013 100644 --- a/l2tpns.c +++ b/l2tpns.c @@ -94,7 +94,11 @@ 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 ip_hash[256]; // Mapping from IP address to session structures. +union iphash +{ + sessionidt sess; + union iphash *idx; +}ip_hash[256]; // Mapping from IP address to session structures. struct ipv6radix { sessionidt sess; @@ -979,7 +983,6 @@ 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; @@ -1076,7 +1079,6 @@ 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. @@ -1484,7 +1486,7 @@ void processipout(uint8_t *buf, int len) ip = *(uint32_t *)(buf + 16); if ((g = grp_groupbyip(ip))) { - s = grp_getnextsession(g, ip, ip_src); + s = grp_getnextsession(g, &ip, &ip_src, 0); if (!s) { // Is this a packet for a session that doesn't exist? @@ -1735,9 +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; - 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; @@ -1770,12 +1774,16 @@ 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))) { - s = sessionbyipv6new(ip6); + s = grp_getnextsession(g, p_ip6, p_ip6_src, 1); + } + else if (!(s = sessionbyipv6(*p_ip6))) + { + s = sessionbyipv6new(*p_ip6); } if (s == 0) @@ -5958,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) diff --git a/l2tpns.h b/l2tpns.h index a5a89f4..45428e6 100644 --- a/l2tpns.h +++ b/l2tpns.h @@ -27,6 +27,7 @@ #define MAXSESSINGRP 12 // Maximum number of member links in grouped session #define MAXGROUPE 300 // could be up to 65535, Maximum number of grouped session #define MAXROUTEINGRP 15 // max static routes per group +#define MAXROUTE6INGRP 15 // max static Ipv6 routes per group // Tunnel Id reserved for pppoe #define TUNNEL_ID_PPPOE 1 @@ -371,17 +372,14 @@ typedef struct sessionidt smin; groupsesslistt sesslist[MAXSESSINGRP]; routet route[MAXROUTEINGRP]; // static routes + routet6 route6[MAXROUTE6INGRP]; // static IPv6 routes + uint8_t ipv6cp_opened; uint8_t nbroutesgrp; + uint8_t nbroutes6grp; uint8_t nbsession; } groupsesst; -union iphash -{ - sessionidt sess; - union iphash *idx; -}; // Mapping from IP address to session structures. - typedef struct { int state; // current state (bundlestate enum) @@ -1026,12 +1024,14 @@ void lac_send_ICRQ(tunnelidt t, sessionidt s); void lac_tunnelshutdown(tunnelidt t, char *reason, int result, int error, char *msg); // grpsess.c -sessionidt grp_getnextsession(groupidt g, in_addr_t ip, in_addr_t ip_src); +sessionidt grp_getnextsession(groupidt g, void *p_ip, void *p_ip_src, int is_ipv6); void grp_initdata(void); void grp_processvendorspecific(sessionidt s, uint8_t *pvs); groupidt grp_groupbysession(sessionidt s); groupidt grp_groupbyip(in_addr_t ip); +groupidt grp_groupbyipv6(struct in6_addr ip); void grp_setgrouproute(groupidt g, int add); +void grp_setgrouproute6(groupidt g, int add); void grp_time_changed(void); void grp_removesession(groupidt g, sessionidt s); int grp_cluster_load_groupe(groupidt g, groupsesst *new); @@ -1085,7 +1085,6 @@ extern in_addr_t my_address; extern int clifd; extern int epollfd; extern int tunidx; // ifr_ifindex of tun device -extern union iphash ip_hash[256]; struct event_data { enum { diff --git a/ppp.c b/ppp.c index c4f9ab4..29bf1bc 100644 --- a/ppp.c +++ b/ppp.c @@ -1482,6 +1482,7 @@ void processipcp(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l) static void ipv6cp_open(sessionidt s, tunnelidt t) { int i; + groupidt g; LOG(3, s, t, "IPV6CP: Opened\n"); change_state(s, ipv6cp, Opened); @@ -1497,6 +1498,12 @@ static void ipv6cp_open(sessionidt s, tunnelidt t) route6set(s, session[s].ipv6address, 128, 1); } + if ((g = grp_groupbysession(s))) + { + grp_setgrouproute6(g, 1); + cluster_send_groupe(g); + } + // Send an initial RA (TODO: Should we send these regularly?) send_ipv6_ra(s, t, NULL); } @@ -2267,7 +2274,8 @@ void processipv6in(sessionidt s, tunnelidt t, uint8_t *p, uint16_t l) if (session[s].ipv6address.s6_addr[0]) { if ((sessionbyipv6new(ip) != s) && - (ip.s6_addr[0] != 0xFE || ip.s6_addr[1] != 0x80 || ip.s6_addr16[1] != 0 || ip.s6_addr16[2] != 0 || ip.s6_addr16[3] != 0)) + (ip.s6_addr[0] != 0xFE || ip.s6_addr[1] != 0x80 || ip.s6_addr16[1] != 0 || ip.s6_addr16[2] != 0 || ip.s6_addr16[3] != 0) && + (!grp_groupbyipv6(ip))) { char str[INET6_ADDRSTRLEN]; LOG(5, s, t, "Dropping packet with spoofed IP %s\n", -- 2.20.1