linux/net/ipv4/gre_demux.c
<<
>>
Prefs
   1/*
   2 *      GRE over IPv4 demultiplexer driver
   3 *
   4 *      Authors: Dmitry Kozlov (xeb@mail.ru)
   5 *
   6 *      This program is free software; you can redistribute it and/or
   7 *      modify it under the terms of the GNU General Public License
   8 *      as published by the Free Software Foundation; either version
   9 *      2 of the License, or (at your option) any later version.
  10 *
  11 */
  12
  13#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  14
  15#include <linux/module.h>
  16#include <linux/if.h>
  17#include <linux/icmp.h>
  18#include <linux/kernel.h>
  19#include <linux/kmod.h>
  20#include <linux/skbuff.h>
  21#include <linux/in.h>
  22#include <linux/ip.h>
  23#include <linux/netdevice.h>
  24#include <linux/if_tunnel.h>
  25#include <linux/spinlock.h>
  26#include <net/protocol.h>
  27#include <net/gre.h>
  28
  29#include <net/icmp.h>
  30#include <net/route.h>
  31#include <net/xfrm.h>
  32
  33static const struct gre_protocol __rcu *gre_proto[GREPROTO_MAX] __read_mostly;
  34
  35int gre_add_protocol(const struct gre_protocol *proto, u8 version)
  36{
  37        if (version >= GREPROTO_MAX)
  38                return -EINVAL;
  39
  40        return (cmpxchg((const struct gre_protocol **)&gre_proto[version], NULL, proto) == NULL) ?
  41                0 : -EBUSY;
  42}
  43EXPORT_SYMBOL_GPL(gre_add_protocol);
  44
  45int gre_del_protocol(const struct gre_protocol *proto, u8 version)
  46{
  47        int ret;
  48
  49        if (version >= GREPROTO_MAX)
  50                return -EINVAL;
  51
  52        ret = (cmpxchg((const struct gre_protocol **)&gre_proto[version], proto, NULL) == proto) ?
  53                0 : -EBUSY;
  54
  55        if (ret)
  56                return ret;
  57
  58        synchronize_rcu();
  59        return 0;
  60}
  61EXPORT_SYMBOL_GPL(gre_del_protocol);
  62
  63/* Fills in tpi and returns header length to be pulled. */
  64int gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi,
  65                     bool *csum_err, __be16 proto, int nhs)
  66{
  67        const struct gre_base_hdr *greh;
  68        __be32 *options;
  69        int hdr_len;
  70
  71        if (unlikely(!pskb_may_pull(skb, nhs + sizeof(struct gre_base_hdr))))
  72                return -EINVAL;
  73
  74        greh = (struct gre_base_hdr *)(skb->data + nhs);
  75        if (unlikely(greh->flags & (GRE_VERSION | GRE_ROUTING)))
  76                return -EINVAL;
  77
  78        tpi->flags = gre_flags_to_tnl_flags(greh->flags);
  79        hdr_len = gre_calc_hlen(tpi->flags);
  80
  81        if (!pskb_may_pull(skb, nhs + hdr_len))
  82                return -EINVAL;
  83
  84        greh = (struct gre_base_hdr *)(skb->data + nhs);
  85        tpi->proto = greh->protocol;
  86
  87        options = (__be32 *)(greh + 1);
  88        if (greh->flags & GRE_CSUM) {
  89                if (skb_checksum_simple_validate(skb)) {
  90                        *csum_err = true;
  91                        return -EINVAL;
  92                }
  93
  94                skb_checksum_try_convert(skb, IPPROTO_GRE, 0,
  95                                         null_compute_pseudo);
  96                options++;
  97        }
  98
  99        if (greh->flags & GRE_KEY) {
 100                tpi->key = *options;
 101                options++;
 102        } else {
 103                tpi->key = 0;
 104        }
 105        if (unlikely(greh->flags & GRE_SEQ)) {
 106                tpi->seq = *options;
 107                options++;
 108        } else {
 109                tpi->seq = 0;
 110        }
 111        /* WCCP version 1 and 2 protocol decoding.
 112         * - Change protocol to IPv4/IPv6
 113         * - When dealing with WCCPv2, Skip extra 4 bytes in GRE header
 114         */
 115        if (greh->flags == 0 && tpi->proto == htons(ETH_P_WCCP)) {
 116                tpi->proto = proto;
 117                if ((*(u8 *)options & 0xF0) != 0x40)
 118                        hdr_len += 4;
 119        }
 120        tpi->hdr_len = hdr_len;
 121        return hdr_len;
 122}
 123EXPORT_SYMBOL(gre_parse_header);
 124
 125static int gre_rcv(struct sk_buff *skb)
 126{
 127        const struct gre_protocol *proto;
 128        u8 ver;
 129        int ret;
 130
 131        if (!pskb_may_pull(skb, 12))
 132                goto drop;
 133
 134        ver = skb->data[1]&0x7f;
 135        if (ver >= GREPROTO_MAX)
 136                goto drop;
 137
 138        rcu_read_lock();
 139        proto = rcu_dereference(gre_proto[ver]);
 140        if (!proto || !proto->handler)
 141                goto drop_unlock;
 142        ret = proto->handler(skb);
 143        rcu_read_unlock();
 144        return ret;
 145
 146drop_unlock:
 147        rcu_read_unlock();
 148drop:
 149        kfree_skb(skb);
 150        return NET_RX_DROP;
 151}
 152
 153static void gre_err(struct sk_buff *skb, u32 info)
 154{
 155        const struct gre_protocol *proto;
 156        const struct iphdr *iph = (const struct iphdr *)skb->data;
 157        u8 ver = skb->data[(iph->ihl<<2) + 1]&0x7f;
 158
 159        if (ver >= GREPROTO_MAX)
 160                return;
 161
 162        rcu_read_lock();
 163        proto = rcu_dereference(gre_proto[ver]);
 164        if (proto && proto->err_handler)
 165                proto->err_handler(skb, info);
 166        rcu_read_unlock();
 167}
 168
 169static const struct net_protocol net_gre_protocol = {
 170        .handler     = gre_rcv,
 171        .err_handler = gre_err,
 172        .netns_ok    = 1,
 173};
 174
 175static int __init gre_init(void)
 176{
 177        pr_info("GRE over IPv4 demultiplexor driver\n");
 178
 179        if (inet_add_protocol(&net_gre_protocol, IPPROTO_GRE) < 0) {
 180                pr_err("can't add protocol\n");
 181                return -EAGAIN;
 182        }
 183        return 0;
 184}
 185
 186static void __exit gre_exit(void)
 187{
 188        inet_del_protocol(&net_gre_protocol, IPPROTO_GRE);
 189}
 190
 191module_init(gre_init);
 192module_exit(gre_exit);
 193
 194MODULE_DESCRIPTION("GRE over IPv4 demultiplexer driver");
 195MODULE_AUTHOR("D. Kozlov (xeb@mail.ru)");
 196MODULE_LICENSE("GPL");
 197