]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - net/ipv4/ip_gre.c
Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
[karo-tx-linux.git] / net / ipv4 / ip_gre.c
index 4d2025f7ec578b7e3f3fd342e82e734d36395068..5b1481be028212cfb61735a2d8ff8fbccbee203f 100644 (file)
 #include <net/gre.h>
 #include <net/dst_metadata.h>
 
-#if IS_ENABLED(CONFIG_IPV6)
-#include <net/ipv6.h>
-#include <net/ip6_fib.h>
-#include <net/ip6_route.h>
-#endif
-
 /*
    Problems & solutions
    --------------------
@@ -144,6 +138,7 @@ static void ipgre_err(struct sk_buff *skb, u32 info,
        const struct iphdr *iph;
        const int type = icmp_hdr(skb)->type;
        const int code = icmp_hdr(skb)->code;
+       unsigned int data_len = 0;
        struct ip_tunnel *t;
 
        switch (type) {
@@ -169,6 +164,7 @@ static void ipgre_err(struct sk_buff *skb, u32 info,
        case ICMP_TIME_EXCEEDED:
                if (code != ICMP_EXC_TTL)
                        return;
+               data_len = icmp_hdr(skb)->un.reserved[1] * 4; /* RFC 4884 4.1 */
                break;
 
        case ICMP_REDIRECT:
@@ -187,6 +183,13 @@ static void ipgre_err(struct sk_buff *skb, u32 info,
        if (!t)
                return;
 
+#if IS_ENABLED(CONFIG_IPV6)
+       if (tpi->proto == htons(ETH_P_IPV6) &&
+           !ip6_err_gen_icmpv6_unreach(skb, iph->ihl * 4 + tpi->hdr_len,
+                                      type, data_len))
+               return;
+#endif
+
        if (t->parms.iph.daddr == 0 ||
            ipv4_is_multicast(t->parms.iph.daddr))
                return;
@@ -217,12 +220,14 @@ static void gre_err(struct sk_buff *skb, u32 info)
         * by themselves???
         */
 
+       const struct iphdr *iph = (struct iphdr *)skb->data;
        const int type = icmp_hdr(skb)->type;
        const int code = icmp_hdr(skb)->code;
        struct tnl_ptk_info tpi;
        bool csum_err = false;
 
-       if (gre_parse_header(skb, &tpi, &csum_err, htons(ETH_P_IP)) < 0) {
+       if (gre_parse_header(skb, &tpi, &csum_err, htons(ETH_P_IP),
+                            iph->ihl * 4) < 0) {
                if (!csum_err)          /* ignore csum errors. */
                        return;
        }
@@ -338,7 +343,7 @@ static int gre_rcv(struct sk_buff *skb)
        }
 #endif
 
-       hdr_len = gre_parse_header(skb, &tpi, &csum_err, htons(ETH_P_IP));
+       hdr_len = gre_parse_header(skb, &tpi, &csum_err, htons(ETH_P_IP), 0);
        if (hdr_len < 0)
                goto drop;
 
@@ -841,17 +846,19 @@ out:
        return ipgre_tunnel_validate(tb, data);
 }
 
-static void ipgre_netlink_parms(struct net_device *dev,
+static int ipgre_netlink_parms(struct net_device *dev,
                                struct nlattr *data[],
                                struct nlattr *tb[],
                                struct ip_tunnel_parm *parms)
 {
+       struct ip_tunnel *t = netdev_priv(dev);
+
        memset(parms, 0, sizeof(*parms));
 
        parms->iph.protocol = IPPROTO_GRE;
 
        if (!data)
-               return;
+               return 0;
 
        if (data[IFLA_GRE_LINK])
                parms->link = nla_get_u32(data[IFLA_GRE_LINK]);
@@ -880,16 +887,26 @@ static void ipgre_netlink_parms(struct net_device *dev,
        if (data[IFLA_GRE_TOS])
                parms->iph.tos = nla_get_u8(data[IFLA_GRE_TOS]);
 
-       if (!data[IFLA_GRE_PMTUDISC] || nla_get_u8(data[IFLA_GRE_PMTUDISC]))
+       if (!data[IFLA_GRE_PMTUDISC] || nla_get_u8(data[IFLA_GRE_PMTUDISC])) {
+               if (t->ignore_df)
+                       return -EINVAL;
                parms->iph.frag_off = htons(IP_DF);
+       }
 
        if (data[IFLA_GRE_COLLECT_METADATA]) {
-               struct ip_tunnel *t = netdev_priv(dev);
-
                t->collect_md = true;
                if (dev->type == ARPHRD_IPGRE)
                        dev->type = ARPHRD_NONE;
        }
+
+       if (data[IFLA_GRE_IGNORE_DF]) {
+               if (nla_get_u8(data[IFLA_GRE_IGNORE_DF])
+                 && (parms->iph.frag_off & htons(IP_DF)))
+                       return -EINVAL;
+               t->ignore_df = !!nla_get_u8(data[IFLA_GRE_IGNORE_DF]);
+       }
+
+       return 0;
 }
 
 /* This function returns true when ENCAP attributes are present in the nl msg */
@@ -960,16 +977,19 @@ static int ipgre_newlink(struct net *src_net, struct net_device *dev,
 {
        struct ip_tunnel_parm p;
        struct ip_tunnel_encap ipencap;
+       int err;
 
        if (ipgre_netlink_encap_parms(data, &ipencap)) {
                struct ip_tunnel *t = netdev_priv(dev);
-               int err = ip_tunnel_encap_setup(t, &ipencap);
+               err = ip_tunnel_encap_setup(t, &ipencap);
 
                if (err < 0)
                        return err;
        }
 
-       ipgre_netlink_parms(dev, data, tb, &p);
+       err = ipgre_netlink_parms(dev, data, tb, &p);
+       if (err < 0)
+               return err;
        return ip_tunnel_newlink(dev, tb, &p);
 }
 
@@ -978,16 +998,19 @@ static int ipgre_changelink(struct net_device *dev, struct nlattr *tb[],
 {
        struct ip_tunnel_parm p;
        struct ip_tunnel_encap ipencap;
+       int err;
 
        if (ipgre_netlink_encap_parms(data, &ipencap)) {
                struct ip_tunnel *t = netdev_priv(dev);
-               int err = ip_tunnel_encap_setup(t, &ipencap);
+               err = ip_tunnel_encap_setup(t, &ipencap);
 
                if (err < 0)
                        return err;
        }
 
-       ipgre_netlink_parms(dev, data, tb, &p);
+       err = ipgre_netlink_parms(dev, data, tb, &p);
+       if (err < 0)
+               return err;
        return ip_tunnel_changelink(dev, tb, &p);
 }
 
@@ -1024,6 +1047,8 @@ static size_t ipgre_get_size(const struct net_device *dev)
                nla_total_size(2) +
                /* IFLA_GRE_COLLECT_METADATA */
                nla_total_size(0) +
+               /* IFLA_GRE_IGNORE_DF */
+               nla_total_size(1) +
                0;
 }
 
@@ -1057,6 +1082,9 @@ static int ipgre_fill_info(struct sk_buff *skb, const struct net_device *dev)
                        t->encap.flags))
                goto nla_put_failure;
 
+       if (nla_put_u8(skb, IFLA_GRE_IGNORE_DF, t->ignore_df))
+               goto nla_put_failure;
+
        if (t->collect_md) {
                if (nla_put_flag(skb, IFLA_GRE_COLLECT_METADATA))
                        goto nla_put_failure;
@@ -1084,6 +1112,7 @@ static const struct nla_policy ipgre_policy[IFLA_GRE_MAX + 1] = {
        [IFLA_GRE_ENCAP_SPORT]  = { .type = NLA_U16 },
        [IFLA_GRE_ENCAP_DPORT]  = { .type = NLA_U16 },
        [IFLA_GRE_COLLECT_METADATA]     = { .type = NLA_FLAG },
+       [IFLA_GRE_IGNORE_DF]    = { .type = NLA_U8 },
 };
 
 static struct rtnl_link_ops ipgre_link_ops __read_mostly = {
@@ -1121,6 +1150,7 @@ struct net_device *gretap_fb_dev_create(struct net *net, const char *name,
 {
        struct nlattr *tb[IFLA_MAX + 1];
        struct net_device *dev;
+       LIST_HEAD(list_kill);
        struct ip_tunnel *t;
        int err;
 
@@ -1136,8 +1166,10 @@ struct net_device *gretap_fb_dev_create(struct net *net, const char *name,
        t->collect_md = true;
 
        err = ipgre_newlink(net, dev, tb, NULL);
-       if (err < 0)
-               goto out;
+       if (err < 0) {
+               free_netdev(dev);
+               return ERR_PTR(err);
+       }
 
        /* openvswitch users expect packet sizes to be unrestricted,
         * so set the largest MTU we can.
@@ -1146,9 +1178,14 @@ struct net_device *gretap_fb_dev_create(struct net *net, const char *name,
        if (err)
                goto out;
 
+       err = rtnl_configure_link(dev, NULL);
+       if (err < 0)
+               goto out;
+
        return dev;
 out:
-       free_netdev(dev);
+       ip_tunnel_dellink(dev, &list_kill);
+       unregister_netdevice_many(&list_kill);
        return ERR_PTR(err);
 }
 EXPORT_SYMBOL_GPL(gretap_fb_dev_create);