From: Blair Steven <blair.ste...@alliedtelesis.co.nz>

Mapping of Addresses and Ports with Encapsulation (MAP-E) is defined in
RFC7597, and is an IPv6 transition technology providing interoperability
between IPv4 and IPv6 networks.

MAP-E uses the encapsulation mode described in RFC2473 (IPv6 Tunneling)
to transport IPv4 and IPv6 packets over an IPv6 network. It requires a
list rules for mapping between IPv4 prefix/shared addresses and IPv6
addresses.

This patch also supports the mapping rules defined in the draft3 version
of the RFC.

Co-developed-by: Felix Jia <felix....@alliedtelesis.co.nz>
Co-developed-by: Sheena Mira-ato <sheena.mira-...@alliedtelesis.co.nz>
Co-developed-by: Masakazu Asama <masakazu.as...@gmail.com>
Signed-off-by: Blair Steven <blair.ste...@alliedtelesis.co.nz>
Signed-off-by: Felix Jia <felix....@alliedtelesis.co.nz>
Signed-off-by: Sheena Mira-ato <sheena.mira-...@alliedtelesis.co.nz>
Signed-off-by: Masakazu Asama <masakazu.as...@gmail.com>
---
 include/net/ip6_tunnel.h       |  18 ++
 include/uapi/linux/if_tunnel.h |  18 ++
 net/ipv6/ip6_tunnel.c          | 490 ++++++++++++++++++++++++++++++++-
 3 files changed, 524 insertions(+), 2 deletions(-)

diff --git a/include/net/ip6_tunnel.h b/include/net/ip6_tunnel.h
index 69b4bcf880c9..ed715ee8d87c 100644
--- a/include/net/ip6_tunnel.h
+++ b/include/net/ip6_tunnel.h
@@ -18,6 +18,16 @@
 /* determine capability on a per-packet basis */
 #define IP6_TNL_F_CAP_PER_PACKET 0x40000
 
+struct ip6_tnl_rule {
+       struct in_addr ipv4_subnet;
+       struct in6_addr ipv6_subnet;
+       u8 version;
+       u8 ea_length;
+       u8 psid_offset;
+       u8 ipv4_prefixlen;
+       u8 ipv6_prefixlen;
+};
+
 struct __ip6_tnl_parm {
        char name[IFNAMSIZ];    /* name of tunnel device */
        int link;               /* ifindex of underlying L2 interface */
@@ -40,6 +50,13 @@ struct __ip6_tnl_parm {
        __u8                    erspan_ver;     /* ERSPAN version */
        __u8                    dir;    /* direction */
        __u16                   hwid;   /* hwid */
+       __u8                    rule_action;
+       struct ip6_tnl_rule     rule;
+};
+
+struct ip6_rule_list {
+       struct list_head list;
+       struct ip6_tnl_rule data;
 };
 
 /* IPv6 tunnel */
@@ -63,6 +80,7 @@ struct ip6_tnl {
        int encap_hlen; /* Encap header length (FOU,GUE) */
        struct ip_tunnel_encap encap;
        int mlink;
+       struct ip6_rule_list rules;
 };
 
 struct ip6_tnl_encap_ops {
diff --git a/include/uapi/linux/if_tunnel.h b/include/uapi/linux/if_tunnel.h
index 1b3d148c4560..7cb09c8c4d8a 100644
--- a/include/uapi/linux/if_tunnel.h
+++ b/include/uapi/linux/if_tunnel.h
@@ -77,10 +77,28 @@ enum {
        IFLA_IPTUN_ENCAP_DPORT,
        IFLA_IPTUN_COLLECT_METADATA,
        IFLA_IPTUN_FWMARK,
+       IFLA_IPTUN_RULE_VERSION,
+       IFLA_IPTUN_RULE_ACTION,
+       IFLA_IPTUN_RULE_IPV6_PREFIX,
+       IFLA_IPTUN_RULE_IPV6_PREFIXLEN,
+       IFLA_IPTUN_RULE_IPV4_PREFIX,
+       IFLA_IPTUN_RULE_IPV4_PREFIXLEN,
+       IFLA_IPTUN_RULE_EA_LENGTH,
+       IFLA_IPTUN_RULE_PSID_OFFSET,
        __IFLA_IPTUN_MAX,
 };
 #define IFLA_IPTUN_MAX (__IFLA_IPTUN_MAX - 1)
 
+enum map_rule_versions {
+       MAP_VERSION_RFC,
+       MAP_VERSION_DRAFT3,
+};
+
+enum tunnel_rule_actions {
+       TUNNEL_RULE_ACTION_ADD = 1,
+       TUNNEL_RULE_ACTION_DELETE = 2,
+};
+
 enum tunnel_encap_types {
        TUNNEL_ENCAP_NONE,
        TUNNEL_ENCAP_FOU,
diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
index a9d06d4dd057..3bd7a5045f28 100644
--- a/net/ipv6/ip6_tunnel.c
+++ b/net/ipv6/ip6_tunnel.c
@@ -20,6 +20,8 @@
 
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
+#include <linux/list.h>
+#include <linux/list_sort.h>
 #include <linux/module.h>
 #include <linux/capability.h>
 #include <linux/errno.h>
@@ -32,6 +34,7 @@
 #include <linux/net.h>
 #include <linux/in6.h>
 #include <linux/netdevice.h>
+#include <linux/inetdevice.h>
 #include <linux/if_arp.h>
 #include <linux/icmpv6.h>
 #include <linux/init.h>
@@ -124,6 +127,226 @@ static struct net_device_stats *ip6_get_stats(struct 
net_device *dev)
        return &dev->stats;
 }
 
+int
+ip6_get_addrport(struct iphdr *iph, __be32 *saddr4, __be32 *daddr4,
+                __be16 *sport4, __be16 *dport4, __u8 *proto, int *icmperr)
+{
+       u8 *ptr;
+       struct iphdr *icmpiph = NULL;
+       struct tcphdr *tcph, *icmptcph;
+       struct udphdr *udph, *icmpudph;
+       struct icmphdr *icmph, *icmpicmph;
+
+       *icmperr = 0;
+       *saddr4 = iph->saddr;
+       *daddr4 = iph->daddr;
+       ptr = (u8 *)iph;
+       ptr += iph->ihl * 4;
+       switch (iph->protocol) {
+       case IPPROTO_TCP:
+               *proto = IPPROTO_TCP;
+               tcph = (struct tcphdr *)ptr;
+               *sport4 = tcph->source;
+               *dport4 = tcph->dest;
+               break;
+       case IPPROTO_UDP:
+               *proto = IPPROTO_UDP;
+               udph = (struct udphdr *)ptr;
+               *sport4 = udph->source;
+               *dport4 = udph->dest;
+               break;
+       case IPPROTO_ICMP:
+               *proto = IPPROTO_ICMP;
+               icmph = (struct icmphdr *)ptr;
+               switch (icmph->type) {
+               case ICMP_DEST_UNREACH:
+               case ICMP_SOURCE_QUENCH:
+               case ICMP_TIME_EXCEEDED:
+               case ICMP_PARAMETERPROB:
+                       *icmperr = 1;
+                       ptr = (u8 *)icmph;
+                       ptr += sizeof(struct icmphdr);
+                       icmpiph = (struct iphdr *)ptr;
+                       *saddr4 = icmpiph->saddr;
+                       *daddr4 = icmpiph->daddr;
+                       if (ntohs(iph->tot_len) < icmpiph->ihl * 4 + 12)
+                               return -1;
+                       ptr += icmpiph->ihl * 4;
+                       switch (icmpiph->protocol) {
+                       case IPPROTO_TCP:
+                               *proto = IPPROTO_TCP;
+                               icmptcph = (struct tcphdr *)ptr;
+                               *sport4 = icmptcph->source;
+                               *dport4 = icmptcph->dest;
+                               break;
+                       case IPPROTO_UDP:
+                               *proto = IPPROTO_UDP;
+                               icmpudph = (struct udphdr *)ptr;
+                               *sport4 = icmpudph->source;
+                               *dport4 = icmpudph->dest;
+                               break;
+                       case IPPROTO_ICMP:
+                               *proto = IPPROTO_ICMP;
+                               icmpicmph = (struct icmphdr *)ptr;
+                               *sport4 = icmpicmph->un.echo.id;
+                               *dport4 = icmpicmph->un.echo.id;
+                               break;
+                       default:
+                               return -1;
+                       }
+                       break;
+               default:
+                       *sport4 = icmph->un.echo.id;
+                       *dport4 = icmph->un.echo.id;
+               }
+               break;
+       default:
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+ip6_mape_gen_addr6(struct ip6_tnl_rule *mr, struct in6_addr *addr6,
+                  __be32 addr4, __be16 port4)
+{
+       __u32 addr[4];
+       __u32 psid = 0;
+       __u32 mask = 0;
+       int psid_mask;
+       int psid_prefix_length;
+       __u32 a = ntohl(addr4);
+       __u16 p = ntohs(port4);
+       int i, pbw0, pbi0, pbi1;
+
+       /* If we have no rule then we can return immediately */
+       if (!mr)
+               return -1;
+
+       psid_prefix_length = mr->ea_length - (32 - mr->ipv4_prefixlen);
+
+       /* In some configurations the PSID can overlap the IPv4 address
+        * segment
+        */
+       if (psid_prefix_length < 0)
+               a &= 0xffffffff << (psid_prefix_length * -1);
+
+       /* Find the PSID from the port by shifting the non-PSID bits out */
+       if (psid_prefix_length > 0) {
+               mask = 0xffffffff >> (32 - psid_prefix_length);
+               psid =
+                   (p >> (16 - psid_prefix_length - mr->psid_offset)) & mask;
+       }
+
+       /* We start with the IPv6 subnet provided for this rule */
+       for (i = 0; i < 4; ++i)
+               addr[i] = ntohl(mr->ipv6_subnet.s6_addr32[i]);
+
+       /* We then copy in the IPv4 host portion - pbw0 is the starting
+        * 32 bit chunk, pbi0 is the offset into that chunk
+        */
+       if (mr->ipv4_prefixlen < 32) {
+               pbw0 = mr->ipv6_prefixlen >> 5;
+               pbi0 = mr->ipv6_prefixlen & 0x1f;
+               addr[pbw0] |= (a << mr->ipv4_prefixlen) >> pbi0;
+               pbi1 = pbi0 - mr->ipv4_prefixlen;
+               if (pbi1 > 0)
+                       addr[pbw0 + 1] |= a << (32 - pbi1);
+       }
+
+       /* The PSID may need to be inserted into the IPv6 address within the
+        * top 64 bits. Again: pbw0 is the chunk, pbi0 is the offset.
+        */
+       if ((psid_prefix_length - mr->psid_offset) > 0) {
+               psid_mask = mask;
+               pbw0 = (mr->ipv6_prefixlen + (32 - mr->ipv4_prefixlen)) >> 5;
+               pbi0 = (mr->ipv6_prefixlen + (32 - mr->ipv4_prefixlen)) & 0x1f;
+
+               pbi1 = pbi0 - (32 - psid_prefix_length);
+
+               if (pbi1 > 0) {
+                       addr[pbw0] |= (psid >> pbi1);
+                       addr[pbw0 + 1] |= (psid & psid_mask) << (32 - pbi1);
+               } else {
+                       addr[pbw0] |= psid << (32 - pbi0 - psid_prefix_length);
+               }
+       }
+
+       /* The format of the interface id changes depending on version */
+       if (mr->version == MAP_VERSION_RFC) {
+               addr[2] |= (a >> 16);
+               addr[3] |= (a << 16);
+               addr[3] |= psid;
+       } else if (mr->version == MAP_VERSION_DRAFT3) {
+               __u32 id0 = a >> 8;
+               __u32 id1 = (a & 0xFF) << 24;
+               id1 |= psid << 8;
+               addr[2] |= id0;
+               addr[3] |= id1;
+       } else {
+               return -1;
+       }
+
+       /* Copy the formed address back into network order */
+       for (i = 0; i < 4; ++i)
+               addr6->s6_addr32[i] = htonl(addr[i]);
+
+       return 0;
+}
+
+static struct ip6_tnl_rule *ip6_tnl_rule_find(struct net_device *dev,
+                                             __be32 _dst)
+{
+       u32 dst = ntohl(_dst);
+       struct ip6_rule_list *pos = NULL;
+       struct ip6_tnl *t = netdev_priv(dev);
+
+       list_for_each_entry(pos, &t->rules.list, list) {
+               int mask =
+                   0xFFFFFFFF ^ ((1 << (32 - pos->data.ipv4_prefixlen)) - 1);
+               if ((dst & mask) == ntohl(pos->data.ipv4_subnet.s_addr))
+                       return &pos->data;
+       }
+       return NULL;
+}
+
+static void
+ip6_tnl_mape_dst(struct net_device *dev, struct sk_buff *skb,
+                struct flowi6 *fl6)
+{
+       __u8 proto;
+       int icmperr;
+       struct iphdr *iph;
+       __be32 saddr4, daddr4, addr;
+       __be16 sport4, dport4, port;
+       struct ip6_tnl_rule *mr = NULL;
+       struct ip6_tnl *t = netdev_priv(dev);
+
+       /* If we have no rules then packets always go to the tunnel dst */
+       if (list_empty(&t->rules.list))
+               return;
+
+       /* Find the src / dst address / port (or ID for ICMP) */
+       iph = ip_hdr(skb);
+       ip6_get_addrport(iph, &saddr4, &daddr4, &sport4, &dport4, &proto,
+                        &icmperr);
+
+       if (icmperr) {
+               mr = ip6_tnl_rule_find(dev, saddr4);
+               addr = saddr4;
+               port = sport4;
+       } else {
+               mr = ip6_tnl_rule_find(dev, daddr4);
+               addr = daddr4;
+               port = dport4;
+       }
+
+       /* Generate an destination IPv6 address from the internal packet */
+       if (mr)
+               ip6_mape_gen_addr6(mr, &fl6->daddr, addr, port);
+}
+
 /**
  * ip6_tnl_lookup - fetch tunnel matching the end-point addresses
  *   @remote: the address of the tunnel exit-point
@@ -207,6 +430,33 @@ ip6_tnl_bucket(struct ip6_tnl_net *ip6n, const struct 
__ip6_tnl_parm *p)
        return &ip6n->tnls[prio][h];
 }
 
+/**
+ * ip6_tnl_bucket_r_any - get head of list matching given tunnel parameters
+ *   @p: parameters containing tunnel end-points
+ *
+ * Description:
+ *   ip6_tnl_bucket_r_any() returns the head of the list matching the
+ *   &struct in6_addr entry laddr in @p.
+ *
+ * Return: head of IPv6 tunnel list
+ **/
+
+static struct ip6_tnl __rcu **
+ip6_tnl_bucket_r_any(struct ip6_tnl_net *ip6n, const struct __ip6_tnl_parm *p)
+{
+       int prio = 0;
+       unsigned int h = 0;
+       struct in6_addr any;
+       const struct in6_addr *local = &p->laddr;
+
+       if (!ipv6_addr_any(local)) {
+               memset(&any, 0, sizeof(any));
+               prio = 1;
+               h = HASH(&any, local);
+       }
+       return &ip6n->tnls[prio][h];
+}
+
 /**
  * ip6_tnl_link - add tunnel to hash table
  *   @t: tunnel to be added
@@ -215,7 +465,12 @@ ip6_tnl_bucket(struct ip6_tnl_net *ip6n, const struct 
__ip6_tnl_parm *p)
 static void
 ip6_tnl_link(struct ip6_tnl_net *ip6n, struct ip6_tnl *t)
 {
-       struct ip6_tnl __rcu **tp = ip6_tnl_bucket(ip6n, &t->parms);
+       struct ip6_tnl __rcu **tp;
+
+       if (!list_empty(&t->rules.list))
+               tp = ip6_tnl_bucket_r_any(ip6n, &t->parms);
+       else
+               tp = ip6_tnl_bucket(ip6n, &t->parms);
 
        if (t->parms.collect_md)
                rcu_assign_pointer(ip6n->collect_md_tun, t);
@@ -234,6 +489,15 @@ ip6_tnl_unlink(struct ip6_tnl_net *ip6n, struct ip6_tnl *t)
        struct ip6_tnl __rcu **tp;
        struct ip6_tnl *iter;
 
+       for (tp = ip6_tnl_bucket_r_any(ip6n, &t->parms);
+            (iter = rtnl_dereference(*tp)) != NULL;
+            tp = &iter->next) {
+               if (t == iter) {
+                       rcu_assign_pointer(*tp, t->next);
+                       return;
+               }
+       }
+
        if (t->parms.collect_md)
                rcu_assign_pointer(ip6n->collect_md_tun, NULL);
 
@@ -352,6 +616,16 @@ static struct ip6_tnl *ip6_tnl_locate(struct net *net,
        struct ip6_tnl *t;
        struct ip6_tnl_net *ip6n = net_generic(net, ip6_tnl_net_id);
 
+       for (tp = ip6_tnl_bucket_r_any(ip6n, p);
+            (t = rtnl_dereference(*tp)) != NULL;
+            tp = &t->next) {
+               if (ipv6_addr_equal(local, &t->parms.laddr)) {
+                       if (create)
+                               return ERR_PTR(-EEXIST);
+                       return t;
+               }
+       }
+
        for (tp = ip6_tnl_bucket(ip6n, p);
             (t = rtnl_dereference(*tp)) != NULL;
             tp = &t->next) {
@@ -880,6 +1154,65 @@ static const struct tnl_ptk_info tpi_v4 = {
        .proto = htons(ETH_P_IP),
 };
 
+static int ip6_tnl_ipv4_addr_match(struct ip6_tnl *t, __be32 addr)
+{
+       int ret = 0;
+       struct in_device *ind;
+
+       ind = in_dev_get(t->dev);
+       if (!ind)
+               return 0;
+
+       for_ifa(ind) {
+               if (ifa->ifa_address == addr) {
+                       ret = 1;
+                       break;
+               }
+       }
+       endfor_ifa(ind);
+       in_dev_put(ind);
+
+       return ret;
+}
+
+static int ip6_tnl_map_rule_validate(struct ip6_tnl *t, struct sk_buff *skb)
+{
+
+       __u8 proto;
+       int icmperr;
+       __be32 src, dst;
+       __be16 sport, dport;
+       struct iphdr *iph;
+       struct ipv6hdr *ipv6h;
+       struct in6_addr src6 = { };
+       struct ip6_tnl_rule *mr = NULL;
+
+       if (likely(list_empty(&t->rules.list)))
+               return 1;
+
+       iph = ipip_hdr(skb);
+       ip6_get_addrport(iph, &src, &dst, &sport, &dport, &proto, &icmperr);
+
+       /* Check if the destination of the packet is for the MAP-E tunnel. */
+       if (!ip6_tnl_ipv4_addr_match(t, dst))
+               return 0;
+
+       /* if the packet's destination is for the tunnel, we need to check
+        * if the source ipv4 address matches any map rules. If there is
+        * no matching rule, just return success (the packet is from br).
+        * Otherwise, we check if the source ipv6 address is valid.
+        */
+       mr = ip6_tnl_rule_find(t->dev, src);
+       if (mr) {
+               ip6_mape_gen_addr6(mr, &src6, src, sport);
+               ipv6h = ipv6_hdr(skb);
+               if (!ipv6_addr_equal(&src6, &ipv6h->saddr))
+                       return 0;
+       }
+
+       return 1;
+}
+
 static int ipxip6_rcv(struct sk_buff *skb, u8 ipproto,
                      const struct tnl_ptk_info *tpi,
                      int (*dscp_ecn_decapsulate)(const struct ip6_tnl *t,
@@ -905,6 +1238,8 @@ static int ipxip6_rcv(struct sk_buff *skb, u8 ipproto,
                        goto drop;
                if (iptunnel_pull_header(skb, 0, tpi->proto, false))
                        goto drop;
+               if (!ip6_tnl_map_rule_validate(t, skb))
+                       goto drop;
                if (t->parms.collect_md) {
                        tun_dst = ipv6_tun_rx_dst(skb, 0, 0, 0);
                        if (!tun_dst)
@@ -1092,6 +1427,12 @@ int ip6_tnl_xmit(struct sk_buff *skb, struct net_device 
*dev, __u8 dsfield,
                use_cache = true;
        }
 
+       /* In MAP-E mode we need to always do a route lookup */
+       if (!list_empty(&t->rules.list)) {
+               use_cache = false;
+               ip6_tnl_mape_dst(dev, skb, fl6);
+       }
+
        if (use_cache)
                dst = dst_cache_get(&t->dst_cache);
 
@@ -1863,6 +2204,8 @@ ip6_tnl_dev_init_gen(struct net_device *dev)
        dev->min_mtu = ETH_MIN_MTU;
        dev->max_mtu = IP6_MAX_MTU - dev->hard_header_len;
 
+       INIT_LIST_HEAD(&t->rules.list);
+
        return 0;
 
 destroy_dst:
@@ -1968,6 +2311,95 @@ static void ip6_tnl_netlink_parms(struct nlattr *data[],
 
        if (data[IFLA_IPTUN_FWMARK])
                parms->fwmark = nla_get_u32(data[IFLA_IPTUN_FWMARK]);
+
+       if (data[IFLA_IPTUN_RULE_ACTION])
+               parms->rule_action = nla_get_u8(data[IFLA_IPTUN_RULE_ACTION]);
+
+       if (data[IFLA_IPTUN_RULE_VERSION])
+               parms->rule.version = nla_get_u8(data[IFLA_IPTUN_RULE_VERSION]);
+
+       if (data[IFLA_IPTUN_RULE_IPV6_PREFIX])
+               parms->rule.ipv6_subnet =
+                   nla_get_in6_addr(data[IFLA_IPTUN_RULE_IPV6_PREFIX]);
+
+       if (data[IFLA_IPTUN_RULE_IPV6_PREFIXLEN])
+               parms->rule.ipv6_prefixlen =
+                   nla_get_u8(data[IFLA_IPTUN_RULE_IPV6_PREFIXLEN]);
+
+       if (data[IFLA_IPTUN_RULE_IPV4_PREFIX])
+               parms->rule.ipv4_subnet.s_addr =
+                   nla_get_in_addr(data[IFLA_IPTUN_RULE_IPV4_PREFIX]);
+
+       if (data[IFLA_IPTUN_RULE_IPV4_PREFIXLEN])
+               parms->rule.ipv4_prefixlen =
+                   nla_get_u8(data[IFLA_IPTUN_RULE_IPV4_PREFIXLEN]);
+
+       if (data[IFLA_IPTUN_RULE_EA_LENGTH])
+               parms->rule.ea_length =
+                   nla_get_u8(data[IFLA_IPTUN_RULE_EA_LENGTH]);
+
+       if (data[IFLA_IPTUN_RULE_PSID_OFFSET])
+               parms->rule.psid_offset =
+                   nla_get_u8(data[IFLA_IPTUN_RULE_PSID_OFFSET]);
+}
+
+static int ip6_tnl_rule_cmp(void *unused, struct list_head *_a,
+                           struct list_head *_b)
+{
+       int diff;
+       struct ip6_rule_list *a = list_entry(_a, struct ip6_rule_list, list);
+       struct ip6_rule_list *b = list_entry(_b, struct ip6_rule_list, list);
+
+       diff = b->data.ipv4_prefixlen - a->data.ipv4_prefixlen;
+       if (!diff)
+               diff = b->data.ipv4_subnet.s_addr - a->data.ipv4_subnet.s_addr;
+
+       if (!diff)
+               diff = b->data.ipv6_prefixlen - a->data.ipv6_prefixlen;
+
+       if (!diff)
+               diff =
+                   memcmp(&b->data.ipv6_subnet, &a->data.ipv6_subnet,
+                          sizeof(b->data.ipv6_subnet));
+
+       if (!diff)
+               diff = b->data.ea_length - a->data.ea_length;
+
+       if (!diff)
+               diff = b->data.psid_offset - a->data.psid_offset;
+
+       return diff;
+}
+
+static void ip6_tnl_add_rule(struct net_device *dev,
+                            struct __ip6_tnl_parm *new_cfg)
+{
+       struct ip6_tnl *t = netdev_priv(dev);
+       struct ip6_rule_list *new_rule = kmalloc(sizeof(t->rules), GFP_KERNEL);
+
+       if (!new_rule)
+               return;
+
+       memcpy(&new_rule->data, &new_cfg->rule, sizeof(new_rule->data));
+       INIT_LIST_HEAD(&new_rule->list);
+       list_add(&new_rule->list, &t->rules.list);
+       list_sort(NULL, &t->rules.list, ip6_tnl_rule_cmp);
+}
+
+static void ip6_tnl_delete_rule(struct net_device *dev,
+                               struct __ip6_tnl_parm *_old_rule)
+{
+       struct ip6_rule_list *pos = NULL;
+       struct ip6_rule_list *iter = NULL;
+       struct ip6_tnl *t = netdev_priv(dev);
+       struct ip6_rule_list old_rule = { .data = _old_rule->rule };
+
+       list_for_each_entry_safe(pos, iter, &t->rules.list, list) {
+               if (ip6_tnl_rule_cmp(NULL, &pos->list, &old_rule.list) == 0) {
+                       list_del(&pos->list);
+                       break;
+               }
+       }
 }
 
 static bool ip6_tnl_netlink_encap_parms(struct nlattr *data[],
@@ -2035,6 +2467,16 @@ static int ip6_tnl_newlink(struct net *src_net, struct 
net_device *dev,
        err = ip6_tnl_create2(dev);
        if (!err && tb[IFLA_MTU])
                ip6_tnl_change_mtu(dev, nla_get_u32(tb[IFLA_MTU]));
+       if (!err && data[IFLA_IPTUN_RULE_ACTION]) {
+               switch (nla_get_u8(data[IFLA_IPTUN_RULE_ACTION])) {
+               case TUNNEL_RULE_ACTION_ADD:
+                       ip6_tnl_add_rule(dev, &nt->parms);
+                       break;
+               case TUNNEL_RULE_ACTION_DELETE:
+                       ip6_tnl_delete_rule(dev, &nt->parms);
+                       break;
+               }
+       }
 
        return err;
 }
@@ -2043,6 +2485,7 @@ static int ip6_tnl_changelink(struct net_device *dev, 
struct nlattr *tb[],
                              struct nlattr *data[],
                              struct netlink_ext_ack *extack)
 {
+       int err;
        struct ip6_tnl *t = netdev_priv(dev);
        struct __ip6_tnl_parm p;
        struct net *net = t->net;
@@ -2069,14 +2512,33 @@ static int ip6_tnl_changelink(struct net_device *dev, 
struct nlattr *tb[],
        } else
                t = netdev_priv(dev);
 
-       return ip6_tnl_update(t, &p);
+       err = ip6_tnl_update(t, &p);
+       if (!err && data[IFLA_IPTUN_RULE_ACTION]) {
+               switch (nla_get_u8(data[IFLA_IPTUN_RULE_ACTION])) {
+               case TUNNEL_RULE_ACTION_ADD:
+                       ip6_tnl_add_rule(dev, &p);
+                       break;
+               case TUNNEL_RULE_ACTION_DELETE:
+                       ip6_tnl_delete_rule(dev, &p);
+                       break;
+               }
+       }
+
+       return err;
 }
 
 static void ip6_tnl_dellink(struct net_device *dev, struct list_head *head)
 {
        struct net *net = dev_net(dev);
+       struct ip6_rule_list *pos = NULL;
+       struct ip6_rule_list *iter = NULL;
+       struct ip6_tnl *t = netdev_priv(dev);
        struct ip6_tnl_net *ip6n = net_generic(net, ip6_tnl_net_id);
 
+       list_for_each_entry_safe(pos, iter, &t->rules.list, list) {
+               list_del(&pos->list);
+       }
+
        if (dev != ip6n->fb_tnl_dev)
                unregister_netdevice_queue(dev, head);
 }
@@ -2112,6 +2574,22 @@ static size_t ip6_tnl_get_size(const struct net_device 
*dev)
                nla_total_size(0) +
                /* IFLA_IPTUN_FWMARK */
                nla_total_size(4) +
+               /* IFLA_IPTUN_RULE_VERSION */
+               nla_total_size(1) +
+               /* IFLA_IPTUN_RULE_ACTION */
+               nla_total_size(1) +
+               /* IFLA_IPTUN_RULE_IPV6_PREFIXLEN */
+               nla_total_size(1) +
+               /* IFLA_IPTUN_RULE_IPV4_PREFIXLEN */
+               nla_total_size(1) +
+               /* IFLA_IPTUN_RULE_EA_LENGTH */
+               nla_total_size(1) +
+               /* IFLA_IPTUN_RULE_PSID_OFFSET */
+               nla_total_size(1) +
+               /* IFLA_IPTUN_RULE_IPV6_PREFIX */
+               nla_total_size(sizeof(struct in6_addr)) +
+               /* IFLA_IPTUN_RULE_IPV4_PREFIX */
+               nla_total_size(sizeof(struct in6_addr)) +
                0;
 }
 
@@ -2170,6 +2648,14 @@ static const struct nla_policy 
ip6_tnl_policy[IFLA_IPTUN_MAX + 1] = {
        [IFLA_IPTUN_ENCAP_DPORT]        = { .type = NLA_U16 },
        [IFLA_IPTUN_COLLECT_METADATA]   = { .type = NLA_FLAG },
        [IFLA_IPTUN_FWMARK]             = { .type = NLA_U32 },
+       [IFLA_IPTUN_RULE_VERSION]       = { .type = NLA_U8 },
+       [IFLA_IPTUN_RULE_ACTION]        = { .type = NLA_U8 },
+       [IFLA_IPTUN_RULE_IPV6_PREFIXLEN] = { .type = NLA_U8 },
+       [IFLA_IPTUN_RULE_IPV4_PREFIXLEN] = { .type = NLA_U8 },
+       [IFLA_IPTUN_RULE_EA_LENGTH]     = { .type = NLA_U8 },
+       [IFLA_IPTUN_RULE_PSID_OFFSET]   = { .type = NLA_U8 },
+       [IFLA_IPTUN_RULE_IPV6_PREFIX]   = { .len = sizeof(struct in6_addr) },
+       [IFLA_IPTUN_RULE_IPV4_PREFIX]   = { .len = sizeof(struct in_addr) },
 };
 
 static struct rtnl_link_ops ip6_link_ops __read_mostly = {
-- 
2.19.2

Reply via email to