From aa4ed7b9c637b70dc42a180b918fb4adb3e32d74 Mon Sep 17 00:00:00 2001
From: Dmitry Kozlov <xeb@mail.ru>
Date: Fri, 18 Mar 2016 23:15:20 +0300
Subject: ipoe: implemented starting UP session by arp request

---
 accel-pppd/ctrl/ipoe/arp.c          | 12 -----
 accel-pppd/ctrl/ipoe/ipoe.c         | 20 ++++----
 accel-pppd/ctrl/ipoe/ipoe.h         | 14 +++++-
 accel-pppd/ctrl/ipoe/ipoe_netlink.c | 18 ++++++--
 drivers/ipoe/ipoe.c                 | 92 ++++++++++++++++++++++++++++---------
 drivers/ipoe/ipoe.h                 |  1 +
 6 files changed, 109 insertions(+), 48 deletions(-)

diff --git a/accel-pppd/ctrl/ipoe/arp.c b/accel-pppd/ctrl/ipoe/arp.c
index 96996246..2bf5a951 100644
--- a/accel-pppd/ctrl/ipoe/arp.c
+++ b/accel-pppd/ctrl/ipoe/arp.c
@@ -26,18 +26,6 @@
 
 #include "memdebug.h"
 
-struct _arphdr {
-	__be16 ar_hrd;
-	__be16 ar_pro;
-	__u8   ar_hln;
-	__u8   ar_pln;
-	__be16 ar_op;
-	__u8   ar_sha[ETH_ALEN];
-	__be32 ar_spa;
-	__u8   ar_tha[ETH_ALEN];
-	__be32 ar_tpa;
-} __packed;
-
 struct arp_node {
 	struct rb_node node;
 	struct ipoe_serv *ipoe;
diff --git a/accel-pppd/ctrl/ipoe/ipoe.c b/accel-pppd/ctrl/ipoe/ipoe.c
index 514e787a..e6f142a3 100644
--- a/accel-pppd/ctrl/ipoe/ipoe.c
+++ b/accel-pppd/ctrl/ipoe/ipoe.c
@@ -1748,15 +1748,16 @@ static void ipoe_recv_dhcpv4_relay(struct dhcpv4_packet *pack)
 }
 
 
-static struct ipoe_session *ipoe_session_create_up(struct ipoe_serv *serv, struct ethhdr *eth, struct iphdr *iph)
+static struct ipoe_session *ipoe_session_create_up(struct ipoe_serv *serv, struct ethhdr *eth, struct iphdr *iph, struct _arphdr *arph)
 {
 	struct ipoe_session *ses;
-	uint8_t *hwaddr = eth->h_source;
+	uint8_t *hwaddr = arph ? arph->ar_sha : eth->h_source;
+	in_addr_t saddr = arph ? arph->ar_spa : iph->saddr;
 
 	if (ap_shutdown)
 		return NULL;
 
-	if (l4_redirect_list_check(iph->saddr))
+	if (l4_redirect_list_check(saddr))
 		return NULL;
 
 	ses = ipoe_session_alloc();
@@ -1764,8 +1765,8 @@ static struct ipoe_session *ipoe_session_create_up(struct ipoe_serv *serv, struc
 		return NULL;
 
 	ses->serv = serv;
-	memcpy(ses->hwaddr, eth->h_source, 6);
-	ses->yiaddr = iph->saddr;
+	memcpy(ses->hwaddr, hwaddr, ETH_ALEN);
+	ses->yiaddr = saddr;
 	ses->UP = 1;
 
 	if (!serv->opt_shared)
@@ -1779,7 +1780,7 @@ static struct ipoe_session *ipoe_session_create_up(struct ipoe_serv *serv, struc
 				hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5]);
 	} else {
 		ses->ctrl.calling_station_id = _malloc(17);
-		u_inet_ntoa(iph->saddr, ses->ctrl.calling_station_id);
+		u_inet_ntoa(saddr, ses->ctrl.calling_station_id);
 	}
 
 	if (ses->serv->opt_username == USERNAME_IFNAME)
@@ -1881,10 +1882,11 @@ struct ipoe_session *ipoe_session_alloc(void)
 	return ses;
 }
 
-void ipoe_recv_up(int ifindex, struct ethhdr *eth, struct iphdr *iph)
+void ipoe_recv_up(int ifindex, struct ethhdr *eth, struct iphdr *iph, struct _arphdr *arph)
 {
 	struct ipoe_serv *serv;
 	struct ipoe_session *ses;
+	in_addr_t saddr = arph ? arph->ar_spa : iph->saddr;
 
 	pthread_mutex_lock(&serv_lock);
 	list_for_each_entry(serv, &serv_list, entry) {
@@ -1904,14 +1906,14 @@ void ipoe_recv_up(int ifindex, struct ethhdr *eth, struct iphdr *iph)
 		}
 
 		list_for_each_entry(ses, &serv->sessions, entry) {
-			if (ses->yiaddr == iph->saddr) {
+			if (ses->yiaddr == saddr) {
 				pthread_mutex_unlock(&serv->lock);
 				pthread_mutex_unlock(&serv_lock);
 				return;
 			}
 		}
 
-		ipoe_session_create_up(serv, eth, iph);
+		ipoe_session_create_up(serv, eth, iph, arph);
 
 		pthread_mutex_unlock(&serv->lock);
 
diff --git a/accel-pppd/ctrl/ipoe/ipoe.h b/accel-pppd/ctrl/ipoe/ipoe.h
index 0cf14ead..52b3c8da 100644
--- a/accel-pppd/ctrl/ipoe/ipoe.h
+++ b/accel-pppd/ctrl/ipoe/ipoe.h
@@ -106,6 +106,18 @@ struct ipoe_session_info {
 	uint32_t peer_addr;
 };
 
+struct _arphdr {
+	__be16 ar_hrd;
+	__be16 ar_pro;
+	__u8   ar_hln;
+	__u8   ar_pln;
+	__be16 ar_op;
+	__u8   ar_sha[ETH_ALEN];
+	__be32 ar_spa;
+	__u8   ar_tha[ETH_ALEN];
+	__be32 ar_tpa;
+} __packed;
+
 #ifdef USE_LUA
 char *ipoe_lua_get_username(struct ipoe_session *, const char *func);
 int ipoe_lua_make_vlan_name(const char *func, const char *parent, int svid, int cvid, char *name);
@@ -114,7 +126,7 @@ int ipoe_lua_make_vlan_name(const char *func, const char *parent, int svid, int
 struct iphdr;
 struct ethhdr;
 
-void ipoe_recv_up(int ifindex, struct ethhdr *eth, struct iphdr *iph);
+void ipoe_recv_up(int ifindex, struct ethhdr *eth, struct iphdr *iph, struct _arphdr *arph);
 
 struct ipoe_session *ipoe_session_alloc(void);
 
diff --git a/accel-pppd/ctrl/ipoe/ipoe_netlink.c b/accel-pppd/ctrl/ipoe/ipoe_netlink.c
index 9372a94c..8ed4d91f 100644
--- a/accel-pppd/ctrl/ipoe/ipoe_netlink.c
+++ b/accel-pppd/ctrl/ipoe/ipoe_netlink.c
@@ -467,6 +467,7 @@ static void ipoe_up_handler(const struct sockaddr_nl *addr, struct nlmsghdr *h)
 	int ifindex;
 	struct iphdr *iph;
 	struct ethhdr *eth;
+	struct _arphdr *arph;
 
 	len -= NLMSG_LENGTH(GENL_HDRLEN);
 
@@ -484,14 +485,23 @@ static void ipoe_up_handler(const struct sockaddr_nl *addr, struct nlmsghdr *h)
 
 		parse_rtattr_nested(tb2, IPOE_ATTR_MAX, tb[i]);
 
-		if (!tb2[IPOE_ATTR_ETH_HDR] || !tb2[IPOE_ATTR_IP_HDR] || !tb2[IPOE_ATTR_IFINDEX])
+		if (!tb2[IPOE_ATTR_IFINDEX])
 			continue;
 
 		ifindex = *(uint32_t *)(RTA_DATA(tb2[IPOE_ATTR_IFINDEX]));
-		iph = (struct iphdr *)(RTA_DATA(tb2[IPOE_ATTR_IP_HDR]));
-		eth = (struct ethhdr *)(RTA_DATA(tb2[IPOE_ATTR_ETH_HDR]));
 
-		ipoe_recv_up(ifindex, eth, iph);
+		if (tb2[IPOE_ATTR_ARP_HDR]) {
+			arph = (struct _arphdr *)(RTA_DATA(tb2[IPOE_ATTR_ARP_HDR]));
+			iph = NULL;
+			eth = NULL;
+		} else if (tb2[IPOE_ATTR_ETH_HDR] && !tb2[IPOE_ATTR_IP_HDR]) {
+			iph = (struct iphdr *)(RTA_DATA(tb2[IPOE_ATTR_IP_HDR]));
+			eth = (struct ethhdr *)(RTA_DATA(tb2[IPOE_ATTR_ETH_HDR]));
+			arph = NULL;
+		} else
+			continue;
+
+		ipoe_recv_up(ifindex, eth, iph, arph);
 	}
 }
 
diff --git a/drivers/ipoe/ipoe.c b/drivers/ipoe/ipoe.c
index 7eccf6e1..d787f827 100644
--- a/drivers/ipoe/ipoe.c
+++ b/drivers/ipoe/ipoe.c
@@ -101,6 +101,24 @@ struct ipoe_entry_u {
 	unsigned long tstamp;
 };
 
+
+struct _arphdr {
+	__be16		ar_hrd;		/* format of hardware address	*/
+	__be16		ar_pro;		/* format of protocol address	*/
+	unsigned char	ar_hln;		/* length of hardware address	*/
+	unsigned char	ar_pln;		/* length of protocol address	*/
+	__be16		ar_op;		/* ARP opcode (command)		*/
+
+	 /*
+	  *	 Ethernet looks like this : This bit is variable sized however...
+	  */
+	unsigned char		ar_sha[ETH_ALEN];	/* sender hardware address	*/
+	__be32		ar_sip;		/* sender IP address		*/
+	unsigned char		ar_tha[ETH_ALEN];	/* target hardware address	*/
+	__be32		ar_tip;		/* target IP address		*/
+} __packed;
+
+
 static struct list_head ipoe_list[HASH_BITS + 1];
 static struct list_head ipoe_list1_u[HASH_BITS + 1];
 static struct list_head ipoe_excl_list[HASH_BITS + 1];
@@ -504,25 +522,32 @@ static void ipoe_process_queue(struct work_struct *w)
 	struct sk_buff *skb;
 	struct ipoe_entry_u *e;
 	struct ethhdr *eth;
-	struct iphdr *iph;
+	struct iphdr *iph = NULL;
+	struct _arphdr *arph = NULL;
 	struct sk_buff *report_skb = NULL;
 	void *header = NULL;
 	struct nlattr *ns;
 	int id = 1;
+	__be32 saddr;
 
 	do {
 		while ((skb = skb_dequeue(&ipoe_queue))) {
-			eth = eth_hdr(skb);
-			iph = ip_hdr(skb);
+			if (likely(skb->protocol == htons(ETH_P_IP))) {
+				iph = ip_hdr(skb);
+				saddr = iph->saddr;
+			} else {
+				arph = (struct _arphdr *)skb_network_header(skb);
+				saddr = arph->ar_sip;
+			}
 
-			e = ipoe_lookup2_u(iph->saddr);
+			e = ipoe_lookup2_u(saddr);
 
 			if (!e) {
 				e = kmalloc(sizeof(*e), GFP_KERNEL);
-				e->addr = iph->saddr;
+				e->addr = saddr;
 				e->tstamp = jiffies;
 
-				list_add_tail_rcu(&e->entry1, &ipoe_list1_u[hash_addr(iph->saddr)]);
+				list_add_tail_rcu(&e->entry1, &ipoe_list1_u[hash_addr(saddr)]);
 				list_add_tail(&e->entry2, &ipoe_list2_u);
 
 				//pr_info("create %08x\n", e->addr);
@@ -554,11 +579,17 @@ static void ipoe_process_queue(struct work_struct *w)
 				if (nla_put_u32(report_skb, IPOE_ATTR_IFINDEX, skb->dev ? skb->dev->ifindex : skb->skb_iif))
 					goto nl_err;
 
-				if (nla_put(report_skb, IPOE_ATTR_ETH_HDR, sizeof(*eth), eth))
-					goto nl_err;
+				if (likely(skb->protocol == htons(ETH_P_IP))) {
+					eth = eth_hdr(skb);
+					if (nla_put(report_skb, IPOE_ATTR_ETH_HDR, sizeof(*eth), eth))
+						goto nl_err;
 
-				if (nla_put(report_skb, IPOE_ATTR_IP_HDR, sizeof(*iph), iph))
-					goto nl_err;
+					if (nla_put(report_skb, IPOE_ATTR_IP_HDR, sizeof(*iph), iph))
+						goto nl_err;
+				} else {
+					if (nla_put(report_skb, IPOE_ATTR_ARP_HDR, sizeof(*arph), arph))
+						goto nl_err;
+				}
 
 				if (nla_nest_end(report_skb, ns) >= IPOE_NLMSG_SIZE) {
 					genlmsg_end(report_skb, header);
@@ -671,10 +702,12 @@ static rx_handler_result_t ipoe_recv(struct sk_buff **pskb)
 	struct ipoe_iface *i = rcu_dereference(dev->rx_handler_data);
 	struct net_device *out = NULL;
 	struct ipoe_session *ses = NULL;
-	struct iphdr *iph;
+	struct iphdr *iph = NULL;
+	struct _arphdr *arph = NULL;
 	struct ethhdr *eth;
 	int noff;
 	struct net_device_stats *stats;
+	__be32 saddr;
 
 	if (!i)
 		return RX_HANDLER_PASS;
@@ -682,21 +715,36 @@ static rx_handler_result_t ipoe_recv(struct sk_buff **pskb)
 	if (unlikely(skb->pkt_type == PACKET_LOOPBACK))
 		return RX_HANDLER_PASS;
 
-	if (skb->protocol != htons(ETH_P_IP))
-		return RX_HANDLER_PASS;
+	if (likely(skb->protocol == htons(ETH_P_IP))) {
+		noff = skb_network_offset(skb);
 
-	noff = skb_network_offset(skb);
+		if (!pskb_may_pull(skb, sizeof(*iph) + noff))
+			return RX_HANDLER_PASS;
 
-	if (!pskb_may_pull(skb, sizeof(*iph) + noff))
-		return RX_HANDLER_PASS;
+		iph = ip_hdr(skb);
+		saddr = iph->saddr;
 
-	iph = ip_hdr(skb);
+		if (!saddr || saddr == 0xffffffff)
+			return RX_HANDLER_PASS;
+	} else if (likely(skb->protocol == htons(ETH_P_ARP))) {
+		noff = skb_network_offset(skb);
+
+		if (skb->len != sizeof(*arph))
+			return RX_HANDLER_PASS;
+
+		if (!pskb_may_pull(skb, sizeof(*arph) + noff))
+			return RX_HANDLER_PASS;
+
+		arph = (struct _arphdr *)skb_network_header(skb);
+		if (arph->ar_op != htons(ARPOP_REQUEST))
+			return RX_HANDLER_PASS;
 
-	if (!iph->saddr)
+		saddr = arph->ar_sip;
+	} else
 		return RX_HANDLER_PASS;
 
 	//pr_info("ipoe: recv %08x %08x\n", iph->saddr, iph->daddr);
-	ses = ipoe_lookup_rt(skb, iph->saddr, &out);
+	ses = ipoe_lookup_rt(skb, saddr, &out);
 
 	if (!ses) {
 		if (i->mode == 0)
@@ -710,11 +758,11 @@ static rx_handler_result_t ipoe_recv(struct sk_buff **pskb)
 			return RX_HANDLER_CONSUMED;
 		}
 
-		if (ipoe_check_exclude(iph->saddr))
+		if (ipoe_check_exclude(saddr))
 			return RX_HANDLER_PASS;
 
-		if (ipoe_check_network(iph->saddr)) {
-			if (ipoe_queue_u(skb, iph->saddr))
+		if (ipoe_check_network(saddr)) {
+			if (ipoe_queue_u(skb, saddr))
 				kfree_skb(skb);
 		} else
 			return RX_HANDLER_PASS;
diff --git a/drivers/ipoe/ipoe.h b/drivers/ipoe/ipoe.h
index c735c2cf..4097e2da 100644
--- a/drivers/ipoe/ipoe.h
+++ b/drivers/ipoe/ipoe.h
@@ -33,6 +33,7 @@ enum {
 	IPOE_ATTR_ETH_HDR,	   /* u32 */
 	IPOE_ATTR_IP_HDR,	   /* u32 */
 	IPOE_ATTR_MODE,	   /* u8 */
+	IPOE_ATTR_ARP_HDR,	   /* u8 */
 	__IPOE_ATTR_MAX,
 };
 
-- 
cgit v1.2.3