From 824b6d0d59a9f31ee26c9c64b35faad888dbae77 Mon Sep 17 00:00:00 2001
From: Maxime Bizon <mbizon@freebox.fr>
Date: Tue, 22 Oct 2019 13:47:42 +0200
Subject: [PATCH 2/3] add fmr support

---
 include/linux-private/linux/if_tunnel.h |  13 +++
 include/netlink/route/link/ip6tnl.h     |   7 ++
 lib/route/link/ip6tnl.c                 | 123 ++++++++++++++++++++++++
 libnl-route-3.sym                       |   1 +
 4 files changed, 144 insertions(+)

diff --git a/include/linux-private/linux/if_tunnel.h b/include/linux-private/linux/if_tunnel.h
index ecdc766..4cf9048 100644
--- a/include/linux-private/linux/if_tunnel.h
+++ b/include/linux-private/linux/if_tunnel.h
@@ -77,10 +77,23 @@ enum {
 	IFLA_IPTUN_ENCAP_DPORT,
 	IFLA_IPTUN_COLLECT_METADATA,
 	IFLA_IPTUN_FWMARK,
+	IFLA_IPTUN_FMRS,
 	__IFLA_IPTUN_MAX,
 };
 #define IFLA_IPTUN_MAX	(__IFLA_IPTUN_MAX - 1)
 
+enum {
+	IFLA_IPTUN_FMR_UNSPEC,
+	IFLA_IPTUN_FMR_IP6_PREFIX,
+	IFLA_IPTUN_FMR_IP4_PREFIX,
+	IFLA_IPTUN_FMR_IP6_PREFIX_LEN,
+	IFLA_IPTUN_FMR_IP4_PREFIX_LEN,
+	IFLA_IPTUN_FMR_EA_LEN,
+	IFLA_IPTUN_FMR_OFFSET,
+	__IFLA_IPTUN_FMR_MAX,
+};
+#define IFLA_IPTUN_FMR_MAX (__IFLA_IPTUN_FMR_MAX - 1)
+
 enum tunnel_encap_types {
 	TUNNEL_ENCAP_NONE,
 	TUNNEL_ENCAP_FOU,
diff --git a/include/netlink/route/link/ip6tnl.h b/include/netlink/route/link/ip6tnl.h
index 87ab164..1c868d5 100644
--- a/include/netlink/route/link/ip6tnl.h
+++ b/include/netlink/route/link/ip6tnl.h
@@ -51,6 +51,13 @@ extern "C" {
 	extern int rtnl_link_ip6_tnl_set_proto(struct rtnl_link *link, uint8_t proto);
 	extern uint8_t rtnl_link_ip6_tnl_get_proto(struct rtnl_link *link);
 
+	extern int rtnl_link_ip6_tnl_add_fmr(struct rtnl_link *link,
+					     const struct in6_addr *ip6,
+					     const struct in_addr *ip4,
+					     unsigned int ip6_plen,
+					     unsigned int ip4_plen,
+					     unsigned int ea_bits,
+					     unsigned int psid_offset);
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/route/link/ip6tnl.c b/lib/route/link/ip6tnl.c
index 085bf66..fd5c52e 100644
--- a/lib/route/link/ip6tnl.c
+++ b/lib/route/link/ip6tnl.c
@@ -43,6 +43,15 @@
 #define IP6_TNL_ATTR_PROTO         (1 << 7)
 #define IP6_TNL_ATTR_FLOWINFO      (1 << 8)
 
+struct ip6_tnl_fmr {
+	struct in6_addr		ip6;
+	struct in_addr		ip4;
+	uint8_t			ip6_plen;
+	uint8_t			ip4_plen;
+	uint8_t			ea_bits;
+	uint8_t			psid_offset;
+};
+
 struct ip6_tnl_info
 {
 	uint8_t                 ttl;
@@ -55,6 +64,9 @@ struct ip6_tnl_info
 	struct in6_addr         local;
 	struct in6_addr         remote;
 	uint32_t                ip6_tnl_mask;
+
+	struct ip6_tnl_fmr	fmrs[4];
+	unsigned int		fmr_count;
 };
 
 static struct nla_policy ip6_tnl_policy[IFLA_IPTUN_MAX + 1] = {
@@ -67,6 +79,16 @@ static struct nla_policy ip6_tnl_policy[IFLA_IPTUN_MAX + 1] = {
 	[IFLA_IPTUN_FLOWINFO]     = { .type = NLA_U32 },
 	[IFLA_IPTUN_FLAGS]        = { .type = NLA_U32 },
 	[IFLA_IPTUN_PROTO]        = { .type = NLA_U8 },
+	[IFLA_IPTUN_FMRS]         = { .type = NLA_NESTED },
+};
+
+static struct nla_policy ip6_fmr_policy[IFLA_IPTUN_FMR_MAX + 1] = {
+	[IFLA_IPTUN_FMR_IP6_PREFIX]	= { .minlen = sizeof(struct in6_addr) },
+	[IFLA_IPTUN_FMR_IP4_PREFIX]	= { .type = NLA_U32 },
+	[IFLA_IPTUN_FMR_IP6_PREFIX_LEN]	= { .type = NLA_U8 },
+	[IFLA_IPTUN_FMR_IP4_PREFIX_LEN]	= { .type = NLA_U8 },
+	[IFLA_IPTUN_FMR_EA_LEN]		= { .type = NLA_U8 },
+	[IFLA_IPTUN_FMR_OFFSET]		= { .type = NLA_U8 },
 };
 
 static int ip6_tnl_alloc(struct rtnl_link *link)
@@ -150,6 +172,53 @@ static int ip6_tnl_parse(struct rtnl_link *link, struct nlattr *data,
 		ip6_tnl->ip6_tnl_mask |= IP6_TNL_ATTR_PROTO;
 	}
 
+	if (tb[IFLA_IPTUN_FMRS]) {
+		struct nlattr *ltb[4];
+		size_t i;
+
+		memset(ltb, 0, sizeof (*ltb));
+		err = nla_parse_nested(ltb, 4, tb[IFLA_IPTUN_FMRS], NULL);
+		if (err < 0)
+			goto errout;
+
+		for (i = 1; i < 4; i++) {
+			struct nlattr *lltb[IFLA_IPTUN_FMR_MAX + 1];
+			struct ip6_tnl_fmr *fmr;
+
+			if (!ltb[i])
+				break;
+
+			err = nla_parse_nested(lltb, IFLA_IPTUN_FMR_MAX, ltb[i],
+					       ip6_fmr_policy);
+
+			if (err < 0)
+				goto errout;
+			if (!lltb[IFLA_IPTUN_FMR_IP6_PREFIX] ||
+			    !lltb[IFLA_IPTUN_FMR_IP4_PREFIX] ||
+			    !lltb[IFLA_IPTUN_FMR_IP4_PREFIX_LEN] ||
+			    !lltb[IFLA_IPTUN_FMR_IP6_PREFIX_LEN] ||
+			    !lltb[IFLA_IPTUN_FMR_EA_LEN] ||
+			    !lltb[IFLA_IPTUN_FMR_OFFSET])
+				break;
+
+			fmr = &ip6_tnl->fmrs[i - 1];
+
+			nla_memcpy(&fmr->ip6, lltb[IFLA_IPTUN_FMR_IP6_PREFIX],
+				   sizeof(struct in6_addr));
+
+			nla_memcpy(&fmr->ip4, lltb[IFLA_IPTUN_FMR_IP4_PREFIX],
+				   sizeof(struct in_addr));
+
+			fmr->ip6_plen = nla_get_u8(lltb[IFLA_IPTUN_FMR_IP6_PREFIX_LEN]);
+
+			fmr->ip4_plen = nla_get_u8(lltb[IFLA_IPTUN_FMR_IP4_PREFIX_LEN]);
+
+			fmr->ea_bits = nla_get_u8(lltb[IFLA_IPTUN_FMR_EA_LEN]);
+			fmr->psid_offset = nla_get_u8(lltb[IFLA_IPTUN_FMR_OFFSET]);
+			ip6_tnl->fmr_count++;
+		}
+	}
+
 	err = 0;
 
 errout:
@@ -195,6 +264,36 @@ static int ip6_tnl_put_attrs(struct nl_msg *msg, struct rtnl_link *link)
 	else
 		NLA_PUT_U8(msg, IFLA_IPTUN_PROTO, 0);
 
+	if (ip6_tnl->fmr_count) {
+		struct nlattr *fdata;
+		size_t i;
+
+		fdata = nla_nest_start(msg, IFLA_IPTUN_FMRS);
+
+		for (i = 0; i < ip6_tnl->fmr_count; i++) {
+			const struct ip6_tnl_fmr *fmr = &ip6_tnl->fmrs[i];
+			struct nlattr *ffdata;
+
+			ffdata = nla_nest_start(msg, i + 1);
+			NLA_PUT(msg, IFLA_IPTUN_FMR_IP6_PREFIX,
+				sizeof(struct in6_addr), &fmr->ip6);
+			NLA_PUT(msg, IFLA_IPTUN_FMR_IP4_PREFIX,
+				sizeof(struct in_addr), &fmr->ip4);
+
+			NLA_PUT_U8(msg, IFLA_IPTUN_FMR_IP6_PREFIX_LEN,
+				   fmr->ip6_plen);
+			NLA_PUT_U8(msg, IFLA_IPTUN_FMR_IP4_PREFIX_LEN,
+				   fmr->ip4_plen);
+			NLA_PUT_U8(msg, IFLA_IPTUN_FMR_EA_LEN,
+				   fmr->ea_bits);
+			NLA_PUT_U8(msg, IFLA_IPTUN_FMR_OFFSET,
+				   fmr->psid_offset);
+
+			nla_nest_end(msg, ffdata);
+		}
+		nla_nest_end(msg, fdata);
+	}
+
 	nla_nest_end(msg, data);
 
 nla_put_failure:
@@ -688,6 +787,30 @@ uint8_t rtnl_link_ip6_tnl_get_proto(struct rtnl_link *link)
 	return ip6_tnl->proto;
 }
 
+int rtnl_link_ip6_tnl_add_fmr(struct rtnl_link *link,
+			      const struct in6_addr *ip6,
+			      const struct in_addr *ip4,
+			      unsigned int ip6_plen,
+			      unsigned int ip4_plen,
+			      unsigned int ea_bits,
+			      unsigned int psid_offset)
+{
+	struct ip6_tnl_info *ip6_tnl = link->l_info;
+	struct ip6_tnl_fmr *fmr;
+
+	IS_IP6_TNL_LINK_ASSERT(link);
+
+	fmr = &ip6_tnl->fmrs[ip6_tnl->fmr_count++];
+	memcpy(&fmr->ip6, ip6, sizeof(struct in6_addr));
+	memcpy(&fmr->ip4, ip4, sizeof(struct in_addr));
+	fmr->ip6_plen = ip6_plen;
+	fmr->ip4_plen = ip4_plen;
+	fmr->ea_bits = ea_bits;
+	fmr->psid_offset = psid_offset;
+
+	return 0;
+}
+
 static void __init ip6_tnl_init(void)
 {
 	rtnl_link_register_info(&ip6_tnl_info_ops);
diff --git a/libnl-route-3.sym b/libnl-route-3.sym
index 4a65503..600f374 100644
--- a/libnl-route-3.sym
+++ b/libnl-route-3.sym
@@ -319,6 +319,7 @@ global:
 	rtnl_link_inet_str2devconf;
 	rtnl_link_info_parse;
 	rtnl_link_ip6_tnl_add;
+	rtnl_link_ip6_tnl_add_fmr;
 	rtnl_link_ip6_tnl_alloc;
 	rtnl_link_ip6_tnl_get_encaplimit;
 	rtnl_link_ip6_tnl_get_flags;
-- 
2.17.1

