From a60d08c8ca66de1844a430cc487a725e4c0e0d54 Mon Sep 17 00:00:00 2001
From: Kozlov Dmitry <xeb@mail.ru>
Date: Thu, 19 Jul 2012 19:09:24 +0400
Subject: ipoe: implemented L4-Redirect radius attribute ipoe: implemented
 client address, router address and mask to be passed via radius

---
 accel-pppd/ctrl/ipoe/backup.c  |  47 ++++++++++++-
 accel-pppd/ctrl/ipoe/dhcpv4.c  |  16 +++++
 accel-pppd/ctrl/ipoe/dhcpv4.h  |   1 +
 accel-pppd/ctrl/ipoe/ipoe.c    | 153 +++++++++++++++++++++++++++++++++++++++--
 accel-pppd/ctrl/ipoe/ipoe.h    |   1 +
 accel-pppd/libnetlink/iplink.c |  72 +++++++++++++++++++
 accel-pppd/libnetlink/iplink.h |   3 +
 7 files changed, 288 insertions(+), 5 deletions(-)

diff --git a/accel-pppd/ctrl/ipoe/backup.c b/accel-pppd/ctrl/ipoe/backup.c
index 8347a4e5..0bc64484 100644
--- a/accel-pppd/ctrl/ipoe/backup.c
+++ b/accel-pppd/ctrl/ipoe/backup.c
@@ -14,7 +14,7 @@
 #include "ap_session_backup.h"
 
 #define IPOE_TAG_HWADDR              1
-#define IPOE_TAG_CLIENT_ID            2
+#define IPOE_TAG_CLIENT_ID           2
 #define IPOE_TAG_AGENT_CIRCUIT_ID    3
 #define IPOE_TAG_AGENT_REMOTE_ID     4
 #define IPOE_TAG_XID                 5
@@ -22,9 +22,16 @@
 #define IPOE_TAG_CALLING_SID         7
 #define IPOE_TAG_CALLED_SID          8
 #define IPOE_TAG_IFNAME              9
+#define IPOE_TAG_FLAGS              10
+#define IPOE_TAG_YIADDR             11
+#define IPOE_TAG_SIADDR             12
+#define IPOE_TAG_MASK               13
 
 #define IPOE_TAG_IFINDEX           100
 
+#define IPOE_FLAG_IFCFG      0x01
+#define IPOE_FLAG_DHCP_ADDR  0x02
+#define IPOE_FLAG_L4_REDIR   0x04
 
 #define add_tag(id, data, size) if (!backup_add_tag(m, id, 0, data, size)) return -1;
 #define add_tag_i(id, data, size) if (!backup_add_tag(m, id, 1, data, size)) return -1;
@@ -37,12 +44,26 @@ static void restore_complete(void);
 static int session_save(struct ap_session *ses, struct backup_mod *m)
 {
 	struct ipoe_session *conn = container_of(ses, typeof(*conn), ses);
+	int flags = 0;
+
+	if (conn->ifcfg)
+		flags |= IPOE_FLAG_IFCFG;
+	
+	if (conn->dhcp_addr)
+		flags |= IPOE_FLAG_DHCP_ADDR;
+	
+	if (conn->l4_redirect)
+		flags |= IPOE_FLAG_L4_REDIR;
 
 	add_tag(IPOE_TAG_HWADDR, conn->hwaddr, 6);
 	add_tag(IPOE_TAG_CALLING_SID, ses->ctrl->calling_station_id, strlen(ses->ctrl->calling_station_id));
 	add_tag(IPOE_TAG_CALLED_SID, ses->ctrl->called_station_id, strlen(ses->ctrl->called_station_id));
 	add_tag(IPOE_TAG_XID, &conn->xid, 4);
 	add_tag(IPOE_TAG_GIADDR, &conn->giaddr, 4);
+	add_tag(IPOE_TAG_YIADDR, &conn->yiaddr, 4);
+	add_tag(IPOE_TAG_SIADDR, &conn->siaddr, 4);
+	add_tag(IPOE_TAG_MASK, &conn->mask, 1);
+	add_tag(IPOE_TAG_FLAGS, &flags, 4);
 
 	if (conn->client_id)
 		add_tag(IPOE_TAG_CLIENT_ID, conn->client_id->data, conn->client_id->len);
@@ -83,6 +104,7 @@ static struct ap_session *ctrl_restore(struct backup_mod *m)
 	int dlen = 0;
 	uint8_t *ptr;
 	struct ipoe_session_info *info;
+	int flags = 0;
 
 	//if (!m->data->internal)
 	//	return NULL;
@@ -149,9 +171,32 @@ static struct ap_session *ctrl_restore(struct backup_mod *m)
 			case IPOE_TAG_IFINDEX:
 				ses->ifindex = *(uint32_t *)t->data;
 				break;
+			case IPOE_TAG_YIADDR:
+				ses->yiaddr = *(uint32_t *)t->data;
+				break;
+			case IPOE_TAG_SIADDR:
+				ses->siaddr = *(uint32_t *)t->data;
+				break;
+			case IPOE_TAG_MASK:
+				ses->mask = *(uint8_t *)t->data;
+				break;
+			case IPOE_TAG_FLAGS:
+				flags = *(uint32_t *)t->data;
+				break;
 		}
 	}
 
+	if (flags & IPOE_FLAG_IFCFG)
+		ses->ifcfg = 1;
+	
+	if (flags & IPOE_FLAG_DHCP_ADDR) {
+		dhcpv4_reserve_ip(ses->serv->dhcpv4, ses->yiaddr);
+		ses->dhcp_addr = 1;
+	}
+	
+	if (flags & IPOE_FLAG_L4_REDIR)
+		ses->l4_redirect = 1;
+
 	ses->serv = serv;
 	
 	triton_context_register(&ses->ctx, &ses->ses);
diff --git a/accel-pppd/ctrl/ipoe/dhcpv4.c b/accel-pppd/ctrl/ipoe/dhcpv4.c
index f1a8876f..08da43ac 100644
--- a/accel-pppd/ctrl/ipoe/dhcpv4.c
+++ b/accel-pppd/ctrl/ipoe/dhcpv4.c
@@ -721,6 +721,22 @@ int dhcpv4_get_ip(struct dhcpv4_serv *serv, uint32_t *yiaddr, uint32_t *siaddr,
 void dhcpv4_put_ip(struct dhcpv4_serv *serv, uint32_t ip)
 {
 	int n = ntohl(ip) - serv->range->startip;
+
+	if (n <= 0 || n / (8 * sizeof(long)) >= serv->range->len)
+		return;
+
+	pthread_mutex_lock(&serv->range->lock);
+	serv->range->free[n / (8 * sizeof(long))] |= 1 << (n % (8 * sizeof(long)));
+	pthread_mutex_unlock(&serv->range->lock);
+}
+
+void dhcpv4_reserve_ip(struct dhcpv4_serv *serv, uint32_t ip)
+{
+	int n = ntohl(ip) - serv->range->startip;
+	
+	if (n <= 0 || n / (8 * sizeof(long)) >= serv->range->len)
+		return;
+
 	pthread_mutex_lock(&serv->range->lock);
 	serv->range->free[n / (8 * sizeof(long))] |= 1 << (n % (8 * sizeof(long)));
 	pthread_mutex_unlock(&serv->range->lock);
diff --git a/accel-pppd/ctrl/ipoe/dhcpv4.h b/accel-pppd/ctrl/ipoe/dhcpv4.h
index cf3aac72..3ebb74cc 100644
--- a/accel-pppd/ctrl/ipoe/dhcpv4.h
+++ b/accel-pppd/ctrl/ipoe/dhcpv4.h
@@ -102,5 +102,6 @@ void dhcpv4_print_packet(struct dhcpv4_packet *pack, void (*print)(const char *f
 
 int dhcpv4_get_ip(struct dhcpv4_serv *serv, uint32_t *yiaddr, uint32_t *siaddr, int *mask);
 void dhcpv4_put_ip(struct dhcpv4_serv *serv, uint32_t ip);
+void dhcpv4_reserve_ip(struct dhcpv4_serv *serv, uint32_t ip);
 
 #endif
diff --git a/accel-pppd/ctrl/ipoe/ipoe.c b/accel-pppd/ctrl/ipoe/ipoe.c
index 69ffe36c..711dca39 100644
--- a/accel-pppd/ctrl/ipoe/ipoe.c
+++ b/accel-pppd/ctrl/ipoe/ipoe.c
@@ -30,6 +30,9 @@
 
 #include "iplink.h"
 #include "connlimit.h"
+#ifdef RADIUS
+#include "radius.h"
+#endif
 
 #include "ipoe.h"
 
@@ -49,6 +52,13 @@ static int conf_ifcfg = 1;
 //static int conf_dhcpv6;
 static int conf_username;
 static int conf_unit_cache;
+#ifdef RADIUS
+static int conf_attr_dhcp_client_ip;
+static int conf_attr_dhcp_router_ip;
+static int conf_attr_dhcp_mask;
+static int conf_attr_l4_redirect;
+#endif
+static int conf_l4_redirect_table;
 
 #ifdef USE_LUA
 static const char *conf_lua_username_func;
@@ -182,6 +192,29 @@ static void ipoe_session_set_username(struct ipoe_session *ses)
 	ses->ses.username = _strdup(ses->ses.ifname);
 }
 
+static void ipoe_change_l4_redirect(struct ipoe_session *ses, int del)
+{
+	in_addr_t addr;
+	
+	if (conf_l4_redirect_table <= 0)
+		return;
+
+	if (ses->ses.ipv4)
+		addr = ses->ses.ipv4->addr;
+	else
+		addr = ses->yiaddr;
+
+	if (del)
+		iprule_del(addr, conf_l4_redirect_table);
+	else
+		iprule_add(addr, conf_l4_redirect_table);
+}
+
+static void ipoe_change_addr(struct ipoe_session *ses, in_addr_t newaddr)
+{
+
+}
+
 static void ipoe_session_start(struct ipoe_session *ses)
 {
 	int r;
@@ -256,9 +289,11 @@ static void ipoe_session_start(struct ipoe_session *ses)
 		return;
 	}
 
-	dhcpv4_get_ip(ses->serv->dhcpv4, &ses->yiaddr, &ses->siaddr, &ses->mask);
-	if (ses->yiaddr)
-		ses->dhcp_addr = 1;
+	if (!ses->yiaddr) {
+		dhcpv4_get_ip(ses->serv->dhcpv4, &ses->yiaddr, &ses->siaddr, &ses->mask);
+		if (ses->yiaddr)
+			ses->dhcp_addr = 1;
+	}
 
 	ses->ses.ipv4 = ipdb_get_ipv4(&ses->ses);
 	/*if (!ses->ses.ipv4) {
@@ -390,6 +425,9 @@ static void ipoe_session_activate(struct ipoe_session *ses)
 	
 	if (ses->serv->opt_ifcfg)
 		ipoe_ifcfg_add(ses);
+	
+	if (ses->l4_redirect)
+		ipoe_change_l4_redirect(ses, 0);
 
 	ap_session_activate(&ses->ses);
 
@@ -494,6 +532,11 @@ static void ipoe_session_finished(struct ap_session *s)
 
 static void ipoe_session_terminate(struct ap_session *s, int hard)
 {
+	struct ipoe_session *ses = container_of(s, typeof(*ses), ses);
+
+	if (ses->l4_redirect)
+		ipoe_change_l4_redirect(ses, 1);
+
 	ap_session_finished(s);
 }
 
@@ -814,6 +857,62 @@ void ipoe_recv_up(int ifindex, struct ethhdr *eth, struct iphdr *iph)
 	}
 }
 
+#ifdef RADIUS
+static void ev_radius_access_accept(struct ev_radius_t *ev)
+{
+	struct ipoe_session *ses = container_of(ev->ses, typeof(*ses), ses);
+	struct rad_attr_t *attr;
+
+	if (ev->ses->ctrl->type != CTRL_TYPE_IPOE)
+		return;
+
+	list_for_each_entry(attr, &ev->reply->attrs, entry) {
+		if (attr->attr->id == conf_attr_dhcp_client_ip)
+			ses->yiaddr = attr->val.ipaddr;
+		else if (attr->attr->id == conf_attr_dhcp_router_ip)
+			ses->siaddr = attr->val.ipaddr;
+		else if (attr->attr->id == conf_attr_dhcp_mask) {
+			if (attr->val.integer > 0 && attr->val.integer < 31)
+				ses->mask = attr->val.integer;
+		} else if (attr->attr->id == conf_attr_l4_redirect) {
+			if (attr->attr->type == ATTR_TYPE_STRING) {
+				if (attr->len && attr->val.string[0] != '0')
+					ses->l4_redirect = 1;
+			} else if (attr->val.integer != 0)
+				ses->l4_redirect = 1;
+		}
+	}
+}
+
+static void ev_radius_coa(struct ev_radius_t *ev)
+{
+	struct ipoe_session *ses = container_of(ev->ses, typeof(*ses), ses);
+	struct rad_attr_t *attr;
+	int l4_redirect;
+	
+	if (ev->ses->ctrl->type != CTRL_TYPE_IPOE)
+		return;
+	
+	l4_redirect = ses->l4_redirect;
+
+	list_for_each_entry(attr, &ev->request->attrs, entry) {
+		if (attr->attr->id == conf_attr_l4_redirect) {
+			if (attr->attr->type == ATTR_TYPE_STRING)
+				ses->l4_redirect = attr->len && attr->val.string[0] != '0';
+			else
+				ses->l4_redirect = ((unsigned int)attr->val.integer) > 0;
+		} else if (strcmp(attr->attr->name, "Framed-IP-Address") == 0) {
+			if (ses->ses.ipv4 && ses->ses.ipv4->peer_addr != attr->val.ipaddr)
+				ipoe_change_addr(ses, attr->val.ipaddr);
+		}
+	}
+
+	//if (l4_redirect && !ses->l4_redirect) || (!l4_redirect && ses->l4_redirect))
+	if (l4_redirect != ses->l4_redirect)
+		ipoe_change_l4_redirect(ses, l4_redirect);
+}
+#endif
+
 static void ipoe_serv_close(struct triton_context_t *ctx)
 {
 	struct ipoe_serv *serv = container_of(ctx, typeof(*serv), ctx);
@@ -1155,6 +1254,35 @@ static void load_local_nets(struct conf_sect_t *sect)
 	}
 }
 
+#ifdef RADIUS
+static void parse_conf_rad_attr(const char *opt, int *val)
+{
+	struct rad_dict_attr_t *attr;
+
+	opt = conf_get_opt("ipoe", opt);
+
+	if (opt) {
+		if (atoi(opt) > 0)
+			*val = atoi(opt);
+		else {
+			attr = rad_dict_find_attr(opt);
+			if (attr)
+				*val = attr->id;
+			else
+				log_emerg("ipoe: couldn't find '%s' in dictionary\n", opt);
+		}
+	} else
+		*val = -1;
+}
+static void load_radius_attrs(void)
+{
+	parse_conf_rad_attr("attr-dhcp-client-ip", &conf_attr_dhcp_client_ip);
+	parse_conf_rad_attr("attr-dhcp-router-ip", &conf_attr_dhcp_router_ip);
+	parse_conf_rad_attr("attr-dhcp-mask", &conf_attr_dhcp_mask);
+	parse_conf_rad_attr("attr-l4-redirect", &conf_attr_l4_redirect);
+}
+#endif
+
 static void load_config(void)
 {
 	const char *opt;
@@ -1210,6 +1338,12 @@ static void load_config(void)
 	if (opt)
 		conf_unit_cache = atoi(opt);
 	
+	opt = conf_get_opt("ipoe", "l4-redirect-table");
+	if (opt)
+		conf_l4_redirect_table = atoi(opt);
+	else
+		conf_l4_redirect_table = 0;
+	
 	opt = conf_get_opt("ipoe", "shared");
 	if (opt)
 		conf_shared = atoi(opt);
@@ -1248,6 +1382,11 @@ static void load_config(void)
 	if (!conf_dhcpv4 && !conf_up)
 		conf_dhcpv4 = 1;
 	
+#ifdef RADIUS
+	if (triton_module_loaded("radius"))
+		load_radius_attrs();
+#endif
+	
 	load_interfaces(s);
 	load_local_nets(s);
 }
@@ -1262,6 +1401,12 @@ static void ipoe_init(void)
 	cli_register_simple_cmd2(show_stat_exec, NULL, 2, "show", "stat");
 	
 	triton_event_register_handler(EV_CONFIG_RELOAD, (triton_event_func)load_config);
+
+#ifdef RADIUS
+	if (triton_module_loaded("radius"))
+		triton_event_register_handler(EV_RADIUS_ACCESS_ACCEPT, (triton_event_func)ev_radius_access_accept);
+		triton_event_register_handler(EV_RADIUS_COA, (triton_event_func)ev_radius_coa);
+#endif
 }
 
-DEFINE_INIT(20, ipoe_init);
+DEFINE_INIT(52, ipoe_init);
diff --git a/accel-pppd/ctrl/ipoe/ipoe.h b/accel-pppd/ctrl/ipoe/ipoe.h
index edf97540..b955b952 100644
--- a/accel-pppd/ctrl/ipoe/ipoe.h
+++ b/accel-pppd/ctrl/ipoe/ipoe.h
@@ -55,6 +55,7 @@ struct ipoe_session
 	int ifindex;
 	int ifcfg:1;
 	int dhcp_addr:1;
+	int l4_redirect:1;
 };
 
 struct ipoe_session_info
diff --git a/accel-pppd/libnetlink/iplink.c b/accel-pppd/libnetlink/iplink.c
index 779a8aeb..4b85afe2 100644
--- a/accel-pppd/libnetlink/iplink.c
+++ b/accel-pppd/libnetlink/iplink.c
@@ -14,6 +14,7 @@
 //#include <linux/if_link.h>
 //#include <linux/if_addr.h>
 //#include <linux/rtnetlink.h>
+#include <linux/fib_rules.h>
 
 #include "triton.h"
 #include "log.h"
@@ -294,6 +295,77 @@ int __export iproute_del(int ifindex, in_addr_t dst)
 	return 0;
 }
 
+int __export iprule_add(uint32_t addr, int table)
+{
+	struct ipaddr_req {
+		struct nlmsghdr n;
+		struct rtmsg i;
+		char buf[1024];
+	} req;
+
+	if (!rth)
+		open_rth();
+	
+	if (!rth)
+		return -1;
+
+	memset(&req, 0, sizeof(req) - 1024);
+	
+	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+	req.n.nlmsg_flags = NLM_F_REQUEST;
+	req.n.nlmsg_type = RTM_NEWRULE;
+	req.i.rtm_family = AF_INET;
+	req.i.rtm_table = table < 256 ? table : RT_TABLE_UNSPEC;
+	req.i.rtm_scope = RT_SCOPE_UNIVERSE;
+	req.i.rtm_protocol = RTPROT_BOOT;
+	req.i.rtm_type = RTN_UNICAST;
+	req.i.rtm_src_len = 32;
+
+	addattr32(&req.n, sizeof(req), FRA_SRC, addr);
+	if (table >= 256)
+		addattr32(&req.n, sizeof(req), FRA_TABLE, table);
+
+	if (rtnl_talk(rth, &req.n, 0, 0, NULL, NULL, NULL, 0) < 0)
+		return -1;
+	
+	return 0;
+}
+
+int __export iprule_del(uint32_t addr, int table)
+{
+	struct ipaddr_req {
+		struct nlmsghdr n;
+		struct rtmsg i;
+		char buf[1024];
+	} req;
+
+	if (!rth)
+		open_rth();
+	
+	if (!rth)
+		return -1;
+
+	memset(&req, 0, sizeof(req) - 1024);
+	
+	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+	req.n.nlmsg_flags = NLM_F_REQUEST;
+	req.n.nlmsg_type = RTM_DELRULE;
+	req.i.rtm_family = AF_INET;
+	req.i.rtm_table = table < 256 ? table : RT_TABLE_UNSPEC;
+	req.i.rtm_scope = RT_SCOPE_UNIVERSE;
+	req.i.rtm_protocol = RTPROT_BOOT;
+	req.i.rtm_type = RTN_UNICAST;
+	req.i.rtm_src_len = 32;
+
+	addattr32(&req.n, sizeof(req), FRA_SRC, addr);
+	if (table >= 256)
+		addattr32(&req.n, sizeof(req), FRA_TABLE, table);
+
+	if (rtnl_talk(rth, &req.n, 0, 0, NULL, NULL, NULL, 0) < 0)
+		return -1;
+	
+	return 0;
+}
 
 
 static void init(void)
diff --git a/accel-pppd/libnetlink/iplink.h b/accel-pppd/libnetlink/iplink.h
index a6af6627..f9124343 100644
--- a/accel-pppd/libnetlink/iplink.h
+++ b/accel-pppd/libnetlink/iplink.h
@@ -13,4 +13,7 @@ int ipaddr_del(int ifindex, in_addr_t addr);
 
 int iproute_add(int ifindex, in_addr_t src, in_addr_t dst);
 int iproute_del(int ifindex, in_addr_t dst);
+
+int iprule_add(uint32_t addr, int table);
+int iprule_del(uint32_t addr, int table);
 #endif
-- 
cgit v1.2.3