From 2e66e6a9e39b1e1b27a1b898efdd3a955bbf1d4e Mon Sep 17 00:00:00 2001 From: Guillaume Nault Date: Fri, 30 Nov 2018 17:36:17 +0100 Subject: radius: implement Framed-IPv6-Route attribute Framed-IPv6-Route is the IPv6 counterpart of Framed-Route. It's only used for defining routes to be added locally by accel-ppp. Routes that should be announced to the peer using Router Advertisements should be defined in the Route-IPv6-Information attribute (but that's currently not implemented). Framed-IPv6-Route format is: [ []] The gateway address and the route metric are optionals, but the metric can only be set if a gateway address is given. One can use the unspecified address '::' to define a route with no gateway and a non-default route metric. When no gateway address is defined, the session's network interface is used directly. Signed-off-by: Guillaume Nault --- accel-pppd/radius/radius.c | 157 +++++++++++++++++++++++++++++++++++++++++++ accel-pppd/radius/radius_p.h | 9 +++ 2 files changed, 166 insertions(+) (limited to 'accel-pppd') diff --git a/accel-pppd/radius/radius.c b/accel-pppd/radius/radius.c index dd05b83f..062e3b72 100644 --- a/accel-pppd/radius/radius.c +++ b/accel-pppd/radius/radius.c @@ -1,5 +1,7 @@ +#include #include #include +#include #include #include #include @@ -146,6 +148,125 @@ out_err: log_ppp_warn("radius: failed to parse Framed-Route=%s\n", attr); } +/* Parse a RADIUS Framed-IPv6-Route string. + * + * Full format is like: "2001:db8::/32 fc00::1 2000" + * + * * "2001:db8::/32" is the network prefix + * * "fc00::1" is the gateway address + * * "2000" is the route metric (priority) + * + * The route metric can be omitted, in which case it is set to 0. This let the + * kernel use its own default route metric. + * If the route metric is not set, the gateway address can be omitted too. In + * this case, it's set to the unspecified address ('::'). This makes the route + * use the session's network interface directly rather than an IP gateway. + */ +static int parse_framed_ipv6_route(const char *str, + struct framed_ip6_route *fr6) +{ + const char *ptr; + size_t len; + + /* Skip leading spaces */ + ptr = str + u_parse_spaces(str); + + /* Get network prefix and prefix length */ + len = u_parse_ip6cidr(ptr, &fr6->prefix, &fr6->plen); + if (!len) { + log_ppp_warn("radius: parsing Framed-IPv6-Route attribute \"%s\" failed at \"%s\":" + " expecting an IPv6 network prefix in CIDR notation\n", + str, ptr); + return -1; + } + ptr += len; + + /* Check separator, unless string ends here */ + len = u_parse_spaces(ptr); + if (!len && *ptr != '\0') { + log_ppp_warn("radius: parsing Framed-IPv6-Route attribute \"%s\" failed at \"%s\":" + " missing space after network prefix\n", + str, ptr); + return -1; + } + ptr += len; + + /* If end of string, use no gateway and default metric */ + if (*ptr == '\0') { + fr6->gw = in6addr_any; + fr6->prio = 0; + return 0; + } + + /* Get the gateway address */ + len = u_parse_ip6addr(ptr, &fr6->gw); + if (!len) { + log_ppp_warn("radius: parsing Framed-IPv6-Route attribute \"%s\" failed at \"%s\":" + " expecting a gateway IPv6 address\n", + str, ptr); + return -1; + } + ptr += len; + + /* Again, separator or end of string required */ + len = u_parse_spaces(ptr); + if (!len && *ptr != '\0') { + log_ppp_warn("radius: parsing Framed-IPv6-Route attribute \"%s\" failed at \"%s\":" + " missing space after gateway address\n", + str, ptr); + return -1; + } + ptr += len; + + /* If end of string, use default metric */ + if (*ptr == '\0') { + fr6->prio = 0; + return 0; + } + + /* Get route metric */ + len = u_parse_u32(ptr, &fr6->prio); + if (!len) { + log_ppp_warn("radius: parsing Framed-IPv6-Route attribute \"%s\" failed at \"%s\":" + " expecting a route metric between 0 and %u\n", + str, ptr, UINT32_MAX); + return -1; + } + ptr += len; + + /* Now this must be the end of the string */ + if (!u_parse_endstr(ptr)) { + log_ppp_warn("radius: parsing Framed-IPv6-Route attribute \"%s\" failed at \"%s\":" + " unexpected data after route metric\n", + str, ptr + u_parse_spaces(ptr)); + return -1; + } + + return 0; +} + +static int rad_add_framed_ipv6_route(const char *str, struct radius_pd_t *rpd) +{ + struct framed_ip6_route *fr6; + + fr6 = _malloc(sizeof(*fr6)); + if (!fr6) + goto err; + + if (parse_framed_ipv6_route(str, fr6) < 0) + goto err_fr6; + + fr6->next = rpd->fr6; + rpd->fr6 = fr6; + + return 0; + +err_fr6: + _free(fr6); +err: + return -1; +} + int rad_proc_attrs(struct rad_req_t *req) { struct ev_wins_t wins = {}; @@ -249,6 +370,9 @@ int rad_proc_attrs(struct rad_req_t *req) case Framed_Route: parse_framed_route(rpd, attr->val.string); break; + case Framed_IPv6_Route: + rad_add_framed_ipv6_route(attr->val.string, rpd); + break; } } @@ -444,6 +568,7 @@ static void ses_acct_start(struct ap_session *ses) static void ses_started(struct ap_session *ses) { struct radius_pd_t *rpd = find_pd(ses); + struct framed_ip6_route *fr6; struct framed_route *fr; if (rpd->session_timeout.expire_tv.tv_sec) { @@ -451,6 +576,18 @@ static void ses_started(struct ap_session *ses) triton_timer_add(ses->ctrl->ctx, &rpd->session_timeout, 0); } + for (fr6 = rpd->fr6; fr6; fr6 = fr6->next) { + bool gw_spec = !IN6_IS_ADDR_UNSPECIFIED(&fr6->gw); + char nbuf[INET6_ADDRSTRLEN]; + char gwbuf[INET6_ADDRSTRLEN]; + + if (ip6route_add(gw_spec ? 0 : rpd->ses->ifindex, &fr6->prefix, fr6->plen, gw_spec ? &fr6->gw : NULL, 3, fr6->prio)) { + log_ppp_warn("radius: failed to add route %s/%hhu %s %u\n", + u_ip6str(&fr6->prefix, nbuf), fr6->plen, + u_ip6str(&fr6->gw, gwbuf), fr6->prio); + } + } + for (fr = rpd->fr; fr; fr = fr->next) { if (iproute_add(fr->gw ? 0 : rpd->ses->ifindex, 0, fr->dst, fr->gw, 3, fr->mask, fr->prio)) { char dst[17], gw[17]; @@ -469,6 +606,7 @@ static void ses_started(struct ap_session *ses) static void ses_finishing(struct ap_session *ses) { struct radius_pd_t *rpd = find_pd(ses); + struct framed_ip6_route *fr6; struct framed_route *fr; if (rpd->auth_ctx) { @@ -478,6 +616,16 @@ static void ses_finishing(struct ap_session *ses) rpd->auth_ctx = NULL; } + for (fr6 = rpd->fr6; fr6; fr6 = fr6->next) { + /* Routes that have an unspecified gateway have been defined + * using the session's virtual network interface. No need to + * delete those routes here: kernel automatically drops them + * when the interface is removed. + */ + if (!IN6_IS_ADDR_UNSPECIFIED(&fr6->gw)) + ip6route_del(0, &fr6->prefix, fr6->plen, &fr6->gw, 3, fr6->prio); + } + for (fr = rpd->fr; fr; fr = fr->next) { if (fr->gw) iproute_del(0, fr->dst, 3, fr->mask, fr->prio); @@ -492,6 +640,7 @@ static void ses_finished(struct ap_session *ses) struct radius_pd_t *rpd = find_pd(ses); struct ipv6db_addr_t *a; struct framed_route *fr = rpd->fr; + struct framed_ip6_route *fr6; pthread_rwlock_wrlock(&sessions_lock); pthread_mutex_lock(&rpd->lock); @@ -542,6 +691,14 @@ static void ses_finished(struct ap_session *ses) _free(a); } + fr6 = rpd->fr6; + while (fr6) { + struct framed_ip6_route *next = fr6->next; + + _free(fr6); + fr6 = next; + } + while (fr) { struct framed_route *next = fr->next; _free(fr); diff --git a/accel-pppd/radius/radius_p.h b/accel-pppd/radius/radius_p.h index 1ce31c03..db8d277f 100644 --- a/accel-pppd/radius/radius_p.h +++ b/accel-pppd/radius/radius_p.h @@ -33,6 +33,14 @@ struct framed_route { struct framed_route *next; }; +struct framed_ip6_route { + struct in6_addr prefix; + struct in6_addr gw; + uint32_t prio; + uint8_t plen; + struct framed_ip6_route *next; +}; + struct radius_pd_t { struct list_head entry; struct ap_private pd; @@ -66,6 +74,7 @@ struct radius_pd_t { int termination_action; struct framed_route *fr; + struct framed_ip6_route *fr6; struct radius_auth_ctx *auth_ctx; -- cgit v1.2.3